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

commonMain.io.nacular.doodle.theme.basic.treecolumns.TreeColumnRow.kt Maven / Gradle / Ivy

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

import io.nacular.doodle.controls.IndexedItem
import io.nacular.doodle.controls.ItemVisualizer
import io.nacular.doodle.controls.SimpleIndexedItem
import io.nacular.doodle.controls.treecolumns.TreeColumns
import io.nacular.doodle.core.View
import io.nacular.doodle.drawing.Canvas
import io.nacular.doodle.drawing.Color
import io.nacular.doodle.drawing.Color.Companion.Black
import io.nacular.doodle.drawing.Color.Companion.White
import io.nacular.doodle.drawing.ColorPaint
import io.nacular.doodle.event.PointerEvent
import io.nacular.doodle.event.PointerListener
import io.nacular.doodle.geometry.ConvexPolygon
import io.nacular.doodle.geometry.Point
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.geometry.rounded
import io.nacular.doodle.layout.Insets
import io.nacular.doodle.layout.constraints.Bounds
import io.nacular.doodle.layout.constraints.ConstraintDslContext
import io.nacular.doodle.layout.constraints.ConstraintLayout
import io.nacular.doodle.layout.constraints.constrain
import io.nacular.doodle.layout.constraints.withSizeInsets
import io.nacular.doodle.system.SystemInputEvent.Modifier.Ctrl
import io.nacular.doodle.system.SystemInputEvent.Modifier.Meta
import io.nacular.doodle.system.SystemInputEvent.Modifier.Shift
import io.nacular.doodle.utils.Path
import kotlin.math.max

/**
 * Created by Nicholas Eddy on 7/25/20.
 */
public abstract class TreeColumnRowIcon: View() {
    public abstract var selected: Boolean
}

public class SimpleTreeColumnRowIcon(private val color: Color = Black, private val selectedColor: Color = White): TreeColumnRowIcon() {
    override var selected: Boolean = false

    override fun render(canvas: Canvas) {
        val centeredRect = bounds.atOrigin

        val path = ConvexPolygon(centeredRect.position,
                                 Point(centeredRect.right, centeredRect.y + centeredRect.height / 2),
                                 Point(centeredRect.x, centeredRect.bottom)).rounded(1.0)

        canvas.transform(transform) {
            path(path, ColorPaint(if (selected) selectedColor else color))
        }
    }
}

public class TreeColumnRow(
                    treeColumns          : TreeColumns,
                    node                 : T,
        public  var path                 : Path,
        private var index                : Int,
        private val itemVisualizer       : ItemVisualizer,
        private val selectionColor       : Color? = Color.Green,
        private val selectionBlurredColor: Color? = selectionColor,
        private val iconFactory          : () -> TreeColumnRowIcon): View() {

    public var insetTop: Double = 1.0

    public var positioner: ConstraintDslContext.(Bounds) -> Unit = { it.centerY eq parent.centerY }
        set(new) {
            if (field == new) {
                return
            }

            field = new

            layout = constrainLayout(children[0])
        }

    private var icon    = null as TreeColumnRowIcon?
    private var content = itemVisualizer.invoke(node, context = SimpleIndexedItem(index, selected = treeColumns.enclosedBySelection(path)))
        private set(new) {
            if (field != new) {
                children.batch {
                    remove(field)
                    field = new
                    add(field)
                }
            }
        }

    // FIXME: Inject
    private val iconWidth   = 9.0
    private val iconSpacing = 4.0
    private var pointerOver = false

    private lateinit var constraintLayout: ConstraintLayout

    private val columnsFocusChanged = { _:View, _:Boolean, _:Boolean ->
        backgroundColor = backgroundColor(treeColumns)
    }

    private val iconConstraints: ConstraintDslContext.(Bounds) -> Unit = {
        it.right   eq parent.right - iconSpacing
        it.width.preserve
        it.centerY eq parent.centerY
    }

    init {
        styleChanged   += { rerender() }
        pointerChanged += object: PointerListener {
            private var pressed = false

            override fun entered(event: PointerEvent) {
                pointerOver = true
            }

            override fun exited(event: PointerEvent) {
                pointerOver = false
            }

            override fun pressed(event: PointerEvent) {
                pressed = true
            }

            override fun released(event: PointerEvent) {
                if (pointerOver && pressed) {
                    setOf(path).also {
                        treeColumns.apply {
                            when {
                                Ctrl in event.modifiers || Meta in event.modifiers -> toggleSelection(it)
                                Shift in event.modifiers && lastSelection != null  -> {
//                                    selectionAnchor?.let { rowFromPath(it) }?.let { anchor ->
//                                        rowFromPath(path)?.let { current ->
//                                            when {
//                                                current < anchor  -> setSelection((current .. anchor ).reversed().toSet())
//                                                anchor  < current -> setSelection((anchor  .. current).           toSet())
//                                            }
//                                        }
//                                    }
                                }
                                else                                               -> setSelection(it)
                            }
                        }
                    }
                }

                pressed = false
            }
        }

        update(treeColumns, node, path, index)
    }

    private fun constrainLayout(view: View) = constrain(view) { content ->
        withSizeInsets(width = iconWidth + 2 * iconSpacing, height = insetTop) {
            positioner(content.withOffset(top = insetTop))
        }
    }

    public fun update(tree: TreeColumns, node: T, path: Path, index: Int) {
        this.path  = path
        this.index = index

        update(itemVisualizer.invoke(node, content, SimpleIndexedItem(index, selected = tree.enclosedBySelection(path))), tree)
    }

    public fun update(content: View, treeColumns: TreeColumns) {
        this.content = content

        idealSize = Size(
                (content.idealSize?.width ?: content.width) + iconWidth + 2 * iconSpacing,
                max(content.idealSize?.height ?: content.height, iconWidth)
        )

        constraintLayout = constrainLayout(content)

        constrainIcon(icon)

        layout = constraintLayout

        if (treeColumns.isLeaf(path)) {
            icon?.let {
                this.children -= it
                constraintLayout.unconstrain(it, iconConstraints)
            }
            icon = null
        } else  {
            icon = icon ?: iconFactory().apply {
                size = Size(iconWidth, iconWidth)

                [email protected] += this

                constrainIcon(this)
            }

            icon?.apply {
                selected = treeColumns.enclosedBySelection(path)
            }
        }

        backgroundColor = backgroundColor(treeColumns)
    }

    private fun backgroundColor(treeColumns: TreeColumns): Color? {
        val selected = treeColumns.selected(path)

        when {
            selected -> treeColumns.focusChanged += columnsFocusChanged
            else     -> treeColumns.focusChanged -= columnsFocusChanged
        }

        return when {
            selected                              -> if (treeColumns.hasFocus) selectionColor else selectionBlurredColor
            treeColumns.enclosedBySelection(path) -> selectionBlurredColor
            else                                  -> null
        }
    }

    override fun render(canvas: Canvas) {
        backgroundColor?.let { canvas.rect(bounds.atOrigin.inset(Insets(top = insetTop)), ColorPaint(it)) }
    }

    private fun constrainIcon(icon: View?) {
        icon?.let {
            constraintLayout.constrain(it, iconConstraints)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy