All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmMain.io.nacular.doodle.theme.native.NativeSliderBehavior.kt Maven / Gradle / Ivy

There is a newer version: 0.10.4
Show newest version
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