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

commonMain.io.nacular.doodle.drawing.impl.NativeSlider.kt Maven / Gradle / Ivy

There is a newer version: 0.10.2
Show newest version
package io.nacular.doodle.drawing.impl

import io.nacular.doodle.controls.range.Slider
import io.nacular.doodle.core.View
import io.nacular.doodle.dom.ElementRuler
import io.nacular.doodle.dom.Event
import io.nacular.doodle.dom.HTMLElement
import io.nacular.doodle.dom.HtmlFactory
import io.nacular.doodle.dom.Visible
import io.nacular.doodle.dom.add
import io.nacular.doodle.dom.setBounds
import io.nacular.doodle.dom.setMargin
import io.nacular.doodle.dom.setOrientation
import io.nacular.doodle.dom.setOverflow
import io.nacular.doodle.drawing.Canvas
import io.nacular.doodle.focus.FocusManager
import io.nacular.doodle.geometry.Rectangle
import io.nacular.doodle.geometry.Rectangle.Companion.Empty
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.utils.Orientation.Horizontal
import io.nacular.doodle.utils.Orientation.Vertical

/**
 * Created by Nicholas Eddy on 11/20/18.
 */
internal interface SliderValueAdapter where T: Comparable {
    operator fun get(slider: Slider              ): Float
    operator fun set(slider: Slider, value: Float)
}

internal interface NativeSliderFactory {
    operator fun  invoke(slider: Slider, adapter: SliderValueAdapter): NativeSlider where T: Comparable
}

internal class NativeSliderFactoryImpl internal constructor(
    private val htmlFactory              : HtmlFactory,
    private val elementRuler             : ElementRuler,
    private val nativeEventHandlerFactory: NativeEventHandlerFactory,
    private val focusManager             : FocusManager?
): NativeSliderFactory {

    private val sizeDifference: Size by lazy {
        val slider = htmlFactory.createInput().apply {
            style.position = "initial"
            type           = "range"
        }

        val holder = htmlFactory.create().apply {
            add(slider)
        }

        elementRuler.size(holder).let {
            Size(it.width - defaultSize.width, it.height - defaultSize.height)
        }
    }

    private val defaultSize: Size by lazy {
        elementRuler.size(htmlFactory.createInput().apply {
            type = "range"
        })
    }

    override fun  invoke(slider: Slider, adapter: SliderValueAdapter) where T: Comparable = NativeSlider(
            htmlFactory,
            nativeEventHandlerFactory,
            focusManager,
            defaultSize,
            sizeDifference,
            slider,
            adapter
    )
}

internal class NativeSlider internal constructor(
                htmlFactory   : HtmlFactory,
                handlerFactory: NativeEventHandlerFactory,
    private val focusManager  : FocusManager?,
    private val defaultSize   : Size,
    private val marginSize    : Size,
    private val slider        : Slider,
    private val adapter       : SliderValueAdapter
): NativeEventListener where T: Comparable {

    private val oldSliderHeight = slider.height

    private val nativeEventHandler: NativeEventHandler

    private val changed: (Slider, T, T) -> Unit = { it,_,_ ->
        sliderElement.value = "${adapter[it] * 100}"
    }

    private val styleChanged: (View) -> Unit = {
        sliderElement.step = when {
            slider.snapToTicks && slider.ticks > 1 -> "${100.0 / (slider.ticks - 1)}".take(17) // Hack to avoid rounding issue that prevents final step (i.e. 100.0 / 3)
            else                                   -> "any"
        }
    }

    private val focusChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
        when (new) {
            true -> sliderElement.focus()
            else -> sliderElement.blur ()
        }
    }

    private val enabledChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
        sliderElement.disabled = !new
    }

    private val focusableChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
        sliderElement.tabIndex = if (new) -1 else 0
    }

    private val boundsChanged: (View, Rectangle, Rectangle) -> Unit = { _,_,new ->
        sliderElement.style.setBounds(Rectangle(size = new.size))
    }

    private val sliderElement = htmlFactory.createInput().apply {
        type = "range"
        setOrientation   (slider.orientation)
        style.setMargin  (0.0               )
        style.setOverflow(Visible()         )
    }

    init {
        nativeEventHandler = handlerFactory(sliderElement, this).apply {
            registerFocusListener()
            registerInputListener()
        }

        slider.apply {
            changed             += [email protected]
            styleChanged        += [email protected]
            focusChanged        += [email protected]
            boundsChanged       += [email protected]
            enabledChanged      += [email protected]
            focusabilityChanged += [email protected]

            changed      (this, value, value )
            styleChanged (this               )
            boundsChanged(this, Empty, bounds)

            val size = when (orientation) {
                Horizontal -> Size(defaultSize.width  + marginSize.width,  defaultSize.height + marginSize.height)
                Vertical   -> Size(defaultSize.height + marginSize.height, defaultSize.width  + marginSize.width )
            }

            idealSize   = size
            minimumSize = if (orientation == Horizontal) Size(0.0, size.height) else Size(size.width, 0.0)
        }
    }

    fun discard() {
        nativeEventHandler.apply {
            unregisterFocusListener()
            unregisterInputListener()
        }

        slider.apply {
            changed             -= [email protected]
            styleChanged        -= [email protected]
            focusChanged        -= [email protected]
            boundsChanged       -= [email protected]
            enabledChanged      -= [email protected]
            focusabilityChanged -= [email protected]

            height = oldSliderHeight
        }
    }

    fun render(canvas: Canvas) {
        if (canvas is NativeCanvas) {

            canvas.addData(listOf(sliderElement))

            if (slider.hasFocus) {
                sliderElement.focus()
            }
        }
    }

    override fun onFocusGained(event: Event): Boolean {
        if (!slider.focusable) {
            return false
        }

        focusManager?.requestFocus(slider)

        return true
    }

    override fun onFocusLost(event: Event): Boolean {
        if (slider === focusManager?.focusOwner) {
            focusManager.clearFocus()
        }

        return true
    }

    override fun onInput(event: Event): Boolean {
        sliderElement.value.toDoubleOrNull()?.let {
            adapter[slider] = (it / 100).toFloat()
        }

        return true
    }

    internal val  ClosedRange.size: Double where T: Number, T: Comparable get() = (endInclusive.toDouble() - start.toDouble())
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy