jvmMain.io.nacular.doodle.theme.native.NativeSliderBehavior.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of desktop-jvm Show documentation
Show all versions of desktop-jvm Show documentation
A pure Kotlin, UI framework for the Web
package io.nacular.doodle.theme.native
import io.nacular.doodle.controls.range.Slider
import io.nacular.doodle.controls.theme.range.SliderBehavior
import io.nacular.doodle.core.View
import io.nacular.doodle.drawing.Canvas
import io.nacular.doodle.drawing.impl.CanvasImpl
import io.nacular.doodle.event.PointerEvent
import io.nacular.doodle.focus.FocusManager
import io.nacular.doodle.geometry.Rectangle
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.system.Cursor
import io.nacular.doodle.system.Cursor.Companion.Default
import io.nacular.doodle.theme.native.NativeTheme.WindowDiscovery
import io.nacular.doodle.utils.Orientation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.awt.Dimension
import java.awt.event.FocusEvent
import java.awt.event.FocusListener
import java.awt.event.MouseEvent
import java.awt.event.MouseEvent.MOUSE_DRAGGED
import java.awt.event.MouseEvent.MOUSE_MOVED
import javax.swing.BoundedRangeModel
import javax.swing.JSlider
import javax.swing.event.ChangeEvent
import javax.swing.event.ChangeListener
import kotlin.coroutines.CoroutineContext
import kotlin.math.pow
internal class NativeSliderBehavior(
private val appScope : CoroutineScope,
private val uiDispatcher : CoroutineContext,
private val window : WindowDiscovery,
private val swingGraphicsFactory : SwingGraphicsFactory,
private val focusManager : FocusManager?,
private val nativePointerPreprocessor: NativePointerPreprocessor?
): SliderBehavior where T: Number, T: Comparable {
private inner class ModelAdapter(
private val delegate: Slider,
precision: Int,
private val setValue: (Slider, Double) -> Unit,
private val setRange: (Slider, ClosedRange) -> Unit
): BoundedRangeModel where T: Number, T: Comparable {
private val listeners = mutableListOf()
private val multiplier = 10.0.pow(precision.toDouble())
private var valueAdjusting = false
fun notifyChanged() {
val event = ChangeEvent(this)
listeners.forEach {
it.stateChanged(event)
}
}
override fun getMinimum() = (delegate.range.start.toDouble() * multiplier).toInt()
override fun setMinimum(newMinimum: Int) {
setRange(delegate, newMinimum / multiplier .. delegate.range.endInclusive.toDouble())
}
override fun getMaximum() = (delegate.range.endInclusive.toDouble() * multiplier).toInt()
override fun setMaximum(newMaximum: Int) {
setRange(delegate, delegate.range.start.toDouble() .. newMaximum / multiplier)
}
override fun getValue() = (delegate.value.toDouble() * multiplier).toInt()
override fun setValue(newValue: Int) {
setValue(delegate, newValue / multiplier)
}
override fun setValueIsAdjusting(b: Boolean) {
valueAdjusting = b
}
override fun getValueIsAdjusting() = valueAdjusting
override fun getExtent() = 0
override fun setExtent(newExtent: Int) {
// no-op
}
override fun setRangeProperties(value: Int, extent: Int, min: Int, max: Int, adjusting: Boolean) {
minimum = min
maximum = max
setValue (value )
setExtent(extent)
valueAdjusting = adjusting
}
override fun addChangeListener(listener: ChangeListener) {
listeners += listener
}
override fun removeChangeListener(listener: ChangeListener) {
listeners -= listener
}
}
private open inner class DoubleSlider(
slider: Slider,
precision: Int = 2,
setValue: (Slider, Double) -> Unit,
setRange: (Slider, ClosedRange) -> Unit,
val model: ModelAdapter = ModelAdapter(slider, precision, setValue, setRange)
): JSlider(model) where T: Number, T: Comparable {
init {
this.orientation = when (slider.orientation) {
Orientation.Vertical -> VERTICAL
else -> HORIZONTAL
}
}
fun notifyChange() {
model.notifyChanged()
}
}
private inner class JSliderPeer(slider: Slider): DoubleSlider(slider, setValue = { s,d -> s.set(d) }, setRange = { s,r -> s.set(r) }) {
private val slider: Slider? = slider
init {
focusTraversalKeysEnabled = false
addFocusListener(object: FocusListener {
override fun focusGained(e: FocusEvent?) {
focusManager?.requestFocus(slider)
}
override fun focusLost(e: FocusEvent?) {
focusManager?.clearFocus()
}
})
}
override fun repaint(tm: Long, x: Int, y: Int, width: Int, height: Int) {
slider?.rerender()
}
public fun handleMouseEvent(e: MouseEvent) = when (e.id) {
MOUSE_MOVED, MOUSE_DRAGGED -> super.processMouseMotionEvent(e)
else -> super.processMouseEvent(e)
}
}
private lateinit var nativePeer : JSliderPeer
private var oldCursor : Cursor? = null
private var oldIdealSize : Size? = null
private val focusChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
when (new) {
true -> nativePeer.requestFocusInWindow()
else -> nativePeer.transferFocus ()
}
}
private val enabledChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
nativePeer.isEnabled = new
}
private val focusableChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
nativePeer.isFocusable = new
}
private val boundsChanged: (View, Rectangle, Rectangle) -> Unit = { _,_,new ->
nativePeer.size = new.size.run { Dimension(width.toInt(), height.toInt()) }
}
private val changeListener : (Slider, T, T ) -> Unit = { _,_,_ -> nativePeer.notifyChange() }
private val limitsChangeListener: (Slider, ClosedRange, ClosedRange) -> Unit = { _,_,_ -> nativePeer.notifyChange() }
override fun render(view: Slider, canvas: Canvas) {
nativePeer.paint(swingGraphicsFactory((canvas as CanvasImpl).skiaCanvas))
}
override fun install(view: Slider) {
super.install(view)
nativePeer = JSliderPeer(view)
nativePointerPreprocessor?.set(view, object: NativePointerHandler {
override fun invoke(event: PointerEvent) {
if (event.source == view) {
nativePeer.handleMouseEvent(event.toAwt(nativePeer))
}
}
})
view.apply {
focusChanged += [email protected]
boundsChanged += [email protected]
enabledChanged += [email protected]
focusabilityChanged += [email protected]
changed += [email protected]
limitsChanged += [email protected]
}
appScope.launch(uiDispatcher) {
nativePeer.size = view.size.run { Dimension(view.width.toInt(), view.height.toInt()) }
view.apply {
cursor = Default
idealSize = nativePeer.preferredSize.run { Size(width, height) }
}
window.frameFor(view)?.add(nativePeer)
if (view.hasFocus) {
nativePeer.requestFocusInWindow()
}
}
}
override fun uninstall(view: Slider) {
super.uninstall(view)
view.apply {
cursor = oldCursor
idealSize = oldIdealSize
focusChanged -= [email protected]
boundsChanged -= [email protected]
enabledChanged -= [email protected]
focusabilityChanged -= [email protected]
changed -= [email protected]
limitsChanged -= [email protected]
}
appScope.launch(uiDispatcher) {
window.frameFor(view)?.remove(nativePeer)
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy