diff --git a/orx-panel/src/main/kotlin/org/openrndr/panel/ControlManager.kt b/orx-panel/src/main/kotlin/org/openrndr/panel/ControlManager.kt index ba3aa934..b9980043 100644 --- a/orx-panel/src/main/kotlin/org/openrndr/panel/ControlManager.kt +++ b/orx-panel/src/main/kotlin/org/openrndr/panel/ControlManager.kt @@ -6,6 +6,7 @@ import org.openrndr.color.ColorRGBa import org.openrndr.draw.* import org.openrndr.math.Matrix44 import org.openrndr.math.Vector2 +import org.openrndr.math.mod_ import org.openrndr.panel.elements.* import org.openrndr.panel.layout.Layouter import org.openrndr.panel.style.* @@ -95,7 +96,11 @@ class ControlManager : Extension { val dropInput = DropInput() + + + inner class KeyboardInput { + private var lastTarget: Element? = null var target: Element? = null set(value) { if (value != field) { @@ -104,6 +109,9 @@ class ControlManager : Extension { value?.keyboard?.focusGained?.trigger(FocusEvent()) field = value field?.pseudoClasses?.add(ElementPseudoClass("active")) + value?.let { + lastTarget = it + } } } @@ -118,6 +126,26 @@ class ControlManager : Extension { } checkForManualRedraw() } + + if (!event.propagationCancelled) { + if (event.key == KEY_TAB) { + val focusableControls = body?.findAllVisible { it.handlesKeyboardFocus } ?: emptyList() + + val index = target?.let { focusableControls.indexOf(it) } ?: lastTarget?.let { focusableControls.indexOf(it) } ?: -1 + if (focusableControls.isNotEmpty()) { + + target = if (target != null) { + if (KeyModifier.SHIFT in event.modifiers) { + focusableControls[(index - 1).mod_(focusableControls.size)] + } else { + focusableControls[(index + 1).mod_(focusableControls.size)] + } + } else { + lastTarget ?: focusableControls[0] + } + } + } + } } fun release(event: KeyEvent) { @@ -210,7 +238,6 @@ class ControlManager : Extension { element.children.forEach { traverse(it, depth + 1) } } - if (!event.propagationCancelled && event.position in element.screenArea && element.computedStyle.display != Display.NONE) { candidates.add(Pair(element, depth)) } diff --git a/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Button.kt b/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Button.kt index eea989fd..6327dd17 100644 --- a/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Button.kt +++ b/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Button.kt @@ -12,6 +12,7 @@ import kotlin.math.round class Button : Element(ElementType("button")) { + override val handlesKeyboardFocus = true var label: String = "OK" class ButtonEvent(val source: Button) diff --git a/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Element.kt b/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Element.kt index 2fd85f3a..d6e6a930 100644 --- a/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Element.kt +++ b/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Element.kt @@ -10,7 +10,9 @@ import org.openrndr.math.Vector2 import org.openrndr.panel.collections.ObservableCopyOnWriteArrayList import org.openrndr.panel.collections.ObservableHashSet import org.openrndr.panel.style.CompoundSelector +import org.openrndr.panel.style.Display import org.openrndr.panel.style.StyleSheet +import org.openrndr.panel.style.display import org.openrndr.shape.Rectangle import java.util.* @@ -27,6 +29,7 @@ open class Element(val type: ElementType) { var scrollTop = 0.0 open val handlesDoubleClick = false + open val handlesKeyboardFocus = false open val widthHint: Double? get() { @@ -223,9 +226,34 @@ open class Element(val type: ElementType) { else -> p.children[index + 1] } } - } + fun findNext(premise: (Element) -> Boolean) : Element? { + return parent?.let { p -> + val index = p.children.indexOf(this) + val siblingCount = p.children.size + for (i in index + 1 until siblingCount) { + if (premise(p.children[i])) { + return p.children[i] + } + } + return null + } + } + + fun findPrevious(premise: (Element) -> Boolean) : Element? { + return parent?.let { p -> + val index = p.children.indexOf(this) + for (i in index-1 downTo 0) { + if (premise(p.children[i])) { + return p.children[i] + } + } + return null + } + } + + fun move(steps: Int) { parent?.let { p -> if (steps != 0) { @@ -283,7 +311,34 @@ fun Element.enable() { fun Element.isDisabled(): Boolean = disabled in pseudoClasses +fun Element.findAll(predicate: (Element) -> Boolean) : List { + val results = mutableListOf() + visit { + if (predicate(this)) { + results.add(this) + } + } + return results +} + +fun Element.findAllVisible(predicate: (Element) -> Boolean) : List { + val results = mutableListOf() + visitVisible { + if (predicate(this)) { + results.add(this) + } + } + return results +} + fun Element.visit(function: Element.() -> Unit) { this.function() children.forEach { it.visit(function) } +} + +fun Element.visitVisible(function: Element.() -> Unit) { + if (this.computedStyle.display != Display.NONE) { + this.function() + children.forEach { it.visitVisible(function) } + } } \ No newline at end of file diff --git a/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Slider.kt b/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Slider.kt index 917aff86..1609cadd 100644 --- a/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Slider.kt +++ b/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Slider.kt @@ -24,6 +24,9 @@ data class Range(val min: Double, val max: Double) { } class Slider : Element(ElementType("slider")) { + + override val handlesKeyboardFocus = true + var label = "" var precision = 3 var value: Double