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

commonMain.io.nacular.doodle.theme.basic.table.TableMetaCells.kt Maven / Gradle / Ivy

The newest version!
package io.nacular.doodle.theme.basic.table

import io.nacular.doodle.controls.table.Column
import io.nacular.doodle.core.View
import io.nacular.doodle.core.renderProperty
import io.nacular.doodle.drawing.Canvas
import io.nacular.doodle.drawing.Color
import io.nacular.doodle.drawing.Color.Companion.Gray
import io.nacular.doodle.drawing.Stroke
import io.nacular.doodle.drawing.darker
import io.nacular.doodle.drawing.lighter
import io.nacular.doodle.drawing.paint
import io.nacular.doodle.event.PointerEvent
import io.nacular.doodle.event.PointerListener
import io.nacular.doodle.event.PointerListener.Companion.on
import io.nacular.doodle.event.PointerMotionListener
import io.nacular.doodle.event.PointerMotionListener.Companion.on
import io.nacular.doodle.geometry.Circle
import io.nacular.doodle.geometry.Point
import io.nacular.doodle.geometry.inscribed
import io.nacular.doodle.geometry.map
import io.nacular.doodle.layout.constraints.Bounds
import io.nacular.doodle.layout.constraints.ConstraintDslContext
import io.nacular.doodle.layout.constraints.center
import io.nacular.doodle.layout.constraints.constrain
import io.nacular.doodle.system.Cursor.Companion.EResize
import io.nacular.doodle.system.Cursor.Companion.EWResize
import io.nacular.doodle.system.Cursor.Companion.Grabbing
import io.nacular.doodle.system.Cursor.Companion.WResize
import io.nacular.doodle.theme.basic.ColorMapper
import io.nacular.doodle.utils.ChangeObservers
import io.nacular.doodle.utils.ChangeObserversImpl
import io.nacular.doodle.utils.SortOrder
import io.nacular.doodle.utils.SortOrder.Ascending
import io.nacular.doodle.utils.SortOrder.Descending
import io.nacular.measured.units.Angle.Companion.degrees
import io.nacular.measured.units.times

/**
 * Created by Nicholas Eddy on 5/10/19.
 */
public class TableHeaderCell(private val column: Column<*>, private val fillColor: Color?): View() {

    private var positioner: ConstraintDslContext.(Bounds) -> Unit = column.headerAlignment ?: center; set(new) {
        if (field == new) {
            return
        }

        field = new

        layout = constrain(children[0]) {
            positioner(it)
        }
    }

    private val alignmentChanged: (Column<*>) -> Unit = { col ->
        col.headerAlignment?.let { positioner = it }
    }

    private var disabledColorMapper: ColorMapper = { it.lighter() }

    init {
        var resizing        = false
        var pointerDown     = false
        var initialWidth    = column.width
        var initialPosition = null as Point?
        var moved           = false

        styleChanged   += {         rerender() }
        enabledChanged += { _,_,_ ->rerender() }

        fun newCursor() = when {
            column.width > column.minWidth && column.width < (column.maxWidth ?: Double.MAX_VALUE) -> EWResize
            column.width < (column.maxWidth ?: Double.MAX_VALUE)                                   -> EResize
            else                                                                                   -> WResize
        }

        fun overHandle(pointerLocation: Point) = pointerLocation.x in width - 5.0..width

        fun updateCursor(event: PointerEvent) {
            cursor = when {
                overHandle(toLocal(event.location, event.target)) -> newCursor()
                else                                              -> null
            }
        }

        pointerChanged += on(
            entered  = {
                if (!pointerDown) {
                    updateCursor(it)
                }
            },

            pressed  = {
                pointerDown     = true
                initialPosition = toLocal(it.location, it.target)

                when {
                    overHandle(initialPosition!!) -> {
                        resizing     = true
                        initialWidth = column.width
                    }
                    column.movable                -> backgroundColor = fillColor?.darker()
                    else                          -> {}
                }

                it.consume()
            },

            released = {
                initialPosition = null

                updateCursor(it)

                backgroundColor = null

                if (pointerDown && !resizing) {
                    column.resetPosition()
                }

                if (pointerDown && !(resizing || moved)) {
                    (toggled as ChangeObserversImpl).forEach { it(this@TableHeaderCell) }
                }

                moved     = false
                resizing  = false
                pointerDown = false
            }
        )

        pointerMotionChanged += on (
            moved   = { updateCursor(it) },
            dragged = { event ->
                initialPosition?.let {
                    moved     = true
                    val delta = (toLocal(event.location, event.target) - it).x

                    cursor = when {
                        resizing       -> newCursor().also { column.preferredWidth = initialWidth + delta }
                        column.movable -> null.also { column.moveBy(delta) }
                        else           -> null
                    }

                    event.consume()
                }
            }
        )

        column.header?.let { header ->
            children += header

            layout = constrain(children[0]) {
                positioner(it)
            }
        }
    }

    public val toggled: ChangeObservers by lazy { ChangeObserversImpl(this) }

    internal var sortOrder: SortOrder? by renderProperty(null)

    override fun addedToDisplay() {
        super.addedToDisplay()

        column.alignmentChanged += alignmentChanged
    }

    override fun removedFromDisplay() {
        super.removedFromDisplay()

        column.alignmentChanged -= alignmentChanged
    }

    override fun render(canvas: Canvas) {
        val x = width - lineThickness / 2

        backgroundColor?.let {
            canvas.rect(bounds.atOrigin, (if (enabled) it else disabledColorMapper(it)).paint)
        }

        val strokeColor = (fillColor?.inverted ?: Gray).let { if (enabled) it else disabledColorMapper(it) }

        canvas.line(Point(x, lineIndent), Point(x, height - lineIndent), Stroke(strokeColor, lineThickness))

        when (sortOrder) {
            Ascending  -> canvas.poly(ascending.map  { it + Point(width - iconCircle.radius - iconHeight, (height - iconHeight) / 2) - Point(iconCircle.center.x, ascendingTop    ) }, fill = strokeColor.paint)
            Descending -> canvas.poly(descending.map { it + Point(width - iconCircle.radius - iconHeight, (height + iconHeight) / 2) - Point(iconCircle.center.x, descendingBottom) }, fill = strokeColor.paint)
            else       -> {}
        }
    }

    private companion object {
        private const val lineIndent    = 3.0
        private const val lineThickness = 1.0

        private val iconCircle = Circle(center = Point(10, 10), radius = 5.0)

        private val ascending        = iconCircle.inscribed(3)!!
        private val descending       = iconCircle.inscribed(3, rotation = 180 * degrees)!!
        private val ascendingTop     = ascending.points.minBy  { it.y }.y
        private val descendingBottom = descending.points.maxBy { it.y }.y
        private val iconHeight       = ascending.points.maxBy  { it.y }.y - ascendingTop
    }
}

public class TableFooterCell(private val column: Column<*>, private val fillColor: Color?): View() {

    private var positioner: ConstraintDslContext.(Bounds) -> Unit = column.footerAlignment ?: center
        set(new) {
            if (field == new) {
                return
            }

            field = new

            layout = constrain(children[0]) {
                positioner(it)
            }
        }

    private val alignmentChanged: (Column<*>) -> Unit = { col ->
        col.footerAlignment?.let { positioner = it }
    }

    private var disabledColorMapper: ColorMapper = { it.lighter() }

    init {
        var resizing        = false
        var pointerDown     = false
        var initialWidth    = column.width
        var initialPosition = null as Point?
        var moved           = false

        styleChanged += {
            rerender()
        }

        fun newCursor() = when {
            column.width > column.minWidth && column.width < (column.maxWidth ?: Double.MAX_VALUE) -> EWResize
            column.width < (column.maxWidth ?: Double.MAX_VALUE)                                   -> EResize
            else                                                                                   -> WResize
        }

        fun overHandle(pointerLocation: Point) = pointerLocation.x in width - 5.0..width

        fun updateCursor(event: PointerEvent) {
            cursor = when {
                overHandle(toLocal(event.location, event.target)) -> newCursor()
                else                                              -> null
            }
        }

        pointerChanged += object: PointerListener {
            override fun entered(event: PointerEvent) {
                if (!pointerDown) {
                    updateCursor(event)
                }
            }

            override fun pressed(event: PointerEvent) {
                pointerDown     = true
                initialPosition = toLocal(event.location, event.target)

                if (overHandle(initialPosition!!)) {
                    resizing     = true
                    initialWidth = column.width
                } else {
                    backgroundColor = fillColor?.darker()
                }

                event.consume()
            }

            override fun released(event: PointerEvent) {
                initialPosition = null

                updateCursor(event)

                backgroundColor = null

                if (pointerDown && !resizing) {
                    column.resetPosition()
                }

                if (pointerDown && !(resizing || moved)) {
                    (toggled as ChangeObserversImpl).forEach { it(this@TableFooterCell) }
                }

                moved     = false
                resizing  = false
                pointerDown = false
            }
        }

        pointerMotionChanged += object : PointerMotionListener {
            override fun moved(event: PointerEvent) {
                updateCursor(event)
            }

            override fun dragged(event: PointerEvent) {
                initialPosition?.let {
                    moved     = true
                    val delta = (toLocal(event.location, event.target) - it).x

                    cursor = if (resizing) {
                        column.preferredWidth = initialWidth + delta

                        newCursor()
                    } else {
                        column.moveBy(delta)

                        Grabbing
                    }

                    event.consume()
                }
            }
        }

        column.footer?.let { footer ->
            children += footer

            layout = constrain(children[0]) {
                positioner(it)
            }
        }
    }

    public val toggled: ChangeObservers by lazy { ChangeObserversImpl(this) }

    override fun addedToDisplay() {
        super.addedToDisplay()

        column.alignmentChanged += alignmentChanged
    }

    override fun removedFromDisplay() {
        super.removedFromDisplay()

        column.alignmentChanged -= alignmentChanged
    }

    override fun render(canvas: Canvas) {
        val x = width - lineThickness / 2

        backgroundColor?.let {
            canvas.rect(bounds.atOrigin, (if (enabled) it else disabledColorMapper(it)).paint)
        }

        val strokeColor = (fillColor?.inverted ?: Gray).let { if (enabled) it else disabledColorMapper(it) }

        canvas.line(Point(x, lineIndent), Point(x, height - lineIndent), Stroke(strokeColor, lineThickness))
    }

    private companion object {
        private const val lineIndent    = 3.0
        private const val lineThickness = 1.0
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy