diff --git a/api/Elementa.api b/api/Elementa.api index be070249..75dab717 100644 --- a/api/Elementa.api +++ b/api/Elementa.api @@ -4,6 +4,7 @@ public final class gg/essential/elementa/ElementaVersion : java/lang/Enum { public static final field V1 Lgg/essential/elementa/ElementaVersion; public static final field V10 Lgg/essential/elementa/ElementaVersion; public static final field V11 Lgg/essential/elementa/ElementaVersion; + public static final field V12 Lgg/essential/elementa/ElementaVersion; public static final field V2 Lgg/essential/elementa/ElementaVersion; public static final field V3 Lgg/essential/elementa/ElementaVersion; public static final field V4 Lgg/essential/elementa/ElementaVersion; @@ -89,6 +90,8 @@ public abstract class gg/essential/elementa/UIComponent : java/util/Observable, protected final fun getMousePosition ()Lkotlin/Pair; public final fun getMouseReleaseListeners ()Ljava/util/List; public final fun getMouseScrollListeners ()Ljava/util/List; + public final fun getOnCharTyped ()Ljava/util/List; + public final fun getOnKeyPressed ()Ljava/util/List; public fun getParent ()Lgg/essential/elementa/UIComponent; public fun getRadius ()F public fun getRight ()F @@ -243,7 +246,7 @@ public final class gg/essential/elementa/VanillaFontRenderer : gg/essential/elem public final class gg/essential/elementa/VanillaFontRenderer$Companion { } -public abstract class gg/essential/elementa/WindowScreen : gg/essential/universal/UScreen { +public abstract class gg/essential/elementa/WindowScreen : gg/essential/universal/UScreen, gg/essential/universal/UScreen$InputHandler { public fun ()V public fun (Lgg/essential/elementa/ElementaVersion;)V public fun (Lgg/essential/elementa/ElementaVersion;Z)V @@ -278,6 +281,11 @@ public abstract class gg/essential/elementa/WindowScreen : gg/essential/universa public fun onMouseScrolled (DDDD)V public fun onScreenClose ()V public final fun stopAnimating (Lkotlin/reflect/KMutableProperty0;)V + public fun uCharTyped (I)Z + public fun uKeyPressed (IILgg/essential/universal/UKeyboard$Modifiers;)Z + public fun uMouseClicked (DDILgg/essential/universal/UKeyboard$Modifiers;)Z + public fun uMouseReleased (DDILgg/essential/universal/UKeyboard$Modifiers;)Z + public fun uMouseScrolled (DDDD)Z } public class gg/essential/elementa/components/GradientComponent : gg/essential/elementa/components/UIBlock { @@ -756,6 +764,7 @@ public final class gg/essential/elementa/components/Window : gg/essential/elemen public final fun addFloatingComponent (Lgg/essential/elementa/UIComponent;)V public fun afterInitialization ()V public fun animationFrame ()V + public final fun charTyped (Lgg/essential/elementa/events/UICharEvent;)Z public fun draw (Lgg/essential/universal/UMatrixStack;)V public final fun drawFloatingComponents ()V public final fun drawFloatingComponents (Lgg/essential/universal/UMatrixStack;)V @@ -775,6 +784,7 @@ public final class gg/essential/elementa/components/Window : gg/essential/elemen public fun hitTest (FF)Lgg/essential/elementa/UIComponent; public final fun invalidateCachedConstraints ()V public final fun isAreaVisible (DDDD)Z + public final fun keyPressed (Lgg/essential/elementa/events/UIKeyEvent;)Z public fun keyType (CI)V public fun mouseClick (DDI)V public fun mouseRelease ()V @@ -938,7 +948,8 @@ public abstract class gg/essential/elementa/components/input/AbstractTextInput : public final fun isActive ()Z protected fun isBreakingCharacter (C)Z public final fun onActivate (Lkotlin/jvm/functions/Function1;)Lgg/essential/elementa/components/input/AbstractTextInput; - protected abstract fun onEnterPressed ()V + protected fun onEnterPressed ()V + protected abstract fun onEnterPressed (Lgg/essential/universal/UKeyboard$Modifiers;)V public final fun onUpdate (Lkotlin/jvm/functions/Function1;)Lgg/essential/elementa/components/input/AbstractTextInput; protected fun recalculateAllVisualLines ()V protected abstract fun recalculateDimensions ()V @@ -1119,7 +1130,7 @@ public class gg/essential/elementa/components/input/UITextInput : gg/essential/e protected final fun getPlaceholderWidth ()F public fun getText ()Ljava/lang/String; protected fun getTextForRender ()Ljava/lang/String; - protected fun onEnterPressed ()V + protected fun onEnterPressed (Lgg/essential/universal/UKeyboard$Modifiers;)V protected fun recalculateDimensions ()V protected fun screenPosToVisualPos (FF)Lgg/essential/elementa/components/input/AbstractTextInput$LinePosition; protected fun scrollIntoView (Lgg/essential/elementa/components/input/AbstractTextInput$LinePosition;)V @@ -2717,6 +2728,12 @@ public final class gg/essential/elementa/effects/StencilEffect$Companion { public final fun enableStencil ()V } +public final class gg/essential/elementa/events/UICharEvent { + public fun (I)V + public final fun getCodepoint ()I + public final fun getString ()Ljava/lang/String; +} + public final class gg/essential/elementa/events/UIClickEvent : gg/essential/elementa/events/UIEvent { public fun (FFILgg/essential/elementa/UIComponent;Lgg/essential/elementa/UIComponent;I)V public final fun component1 ()F @@ -2750,6 +2767,16 @@ public abstract class gg/essential/elementa/events/UIEvent { public final fun stopPropagation ()V } +public final class gg/essential/elementa/events/UIKeyEvent { + public fun (IILgg/essential/universal/UKeyboard$Modifiers;)V + public final fun getKey ()I + public final fun getModifiers ()Lgg/essential/universal/UKeyboard$Modifiers; + public final fun getScanCode ()I + public final fun isCopy ()Z + public final fun isCut ()Z + public final fun isPaste ()Z +} + public final class gg/essential/elementa/events/UIScrollEvent : gg/essential/elementa/events/UIEvent { public fun (DDLgg/essential/elementa/UIComponent;Lgg/essential/elementa/UIComponent;)V public fun (DLgg/essential/elementa/UIComponent;Lgg/essential/elementa/UIComponent;)V diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d900edda..81b7e7d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "1.9.25" kotlinx-coroutines = "1.5.2" jetbrains-annotations = "23.0.0" -universalcraft = "466" +universalcraft = "485+feature-em-1645-closing-uscreen-via-esc-on-119-puts-you-in-the-menu-rather" commonmark = "0.17.1" dom4j = "2.1.1" diff --git a/src/main/kotlin/gg/essential/elementa/ElementaVersion.kt b/src/main/kotlin/gg/essential/elementa/ElementaVersion.kt index 7ddf7f1c..e7eeabc6 100644 --- a/src/main/kotlin/gg/essential/elementa/ElementaVersion.kt +++ b/src/main/kotlin/gg/essential/elementa/ElementaVersion.kt @@ -7,6 +7,7 @@ import gg.essential.elementa.components.Window import gg.essential.elementa.constraints.SuperConstraint import gg.essential.elementa.constraints.animation.AnimationComponent import gg.essential.elementa.effects.Effect +import gg.essential.universal.UScreen import gg.essential.universal.render.URenderPipeline import gg.essential.universal.shader.BlendState @@ -204,8 +205,31 @@ enum class ElementaVersion { * * See [UniversalCraft#128](https://github.com/SparkUniverse/UniversalCraft/pull/128) for the underlying changes. */ + @Deprecated(DEPRECATION_MESSAGE) V11, + /** + * Improvements for key press inputs. + * + * WindowScreen now implements [UScreen.InputHandler] which provides new functions for key and mouse inputs that + * follow more modern Minecraft input method conventions, most notably allowing us to mark an input as handled to + * prevent followup execution in Minecraft itself. + * + * As of V12 the key & character input events have been split up and moved to these new methods. + * `keyType(typedChar: Char, keyCode: Int)` in [Window] has been split into: + * - keyPressed(keyEvent: UIKeyEvent) + * - charTyped(charEvent: UICharEvent) + * With the backing list for the new listeners now requiring manual modification to set up. + * E.G. `UIComponent.onKeyPressed.add { }` & `UIComponent.onCharTyped.add { }` replace the old `UIComponent.onKeyType { }` + * + * Also see [gg.essential.elementa.events.UIKeyEvent] & [gg.essential.elementa.events.UICharEvent]. + * + * Note: The other improved inputs provided by [UScreen.InputHandler] have not been implemented by V12. (e.g. mouse inputs) + * + * See [UniversalCraft#125](https://github.com/SparkUniverse/UniversalCraft/pull/125) for the underlying changes. + */ + V12, + ; /** @@ -258,7 +282,9 @@ Be sure to read through all the changes between your current version and your ne internal val v9 = V9 @Suppress("DEPRECATION") internal val v10 = V10 + @Suppress("DEPRECATION") internal val v11 = V11 + internal val v12 = V12 internal val atLeastV9Active: Boolean get() = active >= v9 @@ -266,6 +292,8 @@ Be sure to read through all the changes between your current version and your ne get() = active >= v10 internal val atLeastV11Active: Boolean get() = active >= v11 + internal val atLeastV12Active: Boolean + get() = active >= v12 @PublishedApi internal var active: ElementaVersion = v0 diff --git a/src/main/kotlin/gg/essential/elementa/UIComponent.kt b/src/main/kotlin/gg/essential/elementa/UIComponent.kt index 9f869630..1677e542 100644 --- a/src/main/kotlin/gg/essential/elementa/UIComponent.kt +++ b/src/main/kotlin/gg/essential/elementa/UIComponent.kt @@ -13,7 +13,9 @@ import gg.essential.elementa.dsl.animate import gg.essential.elementa.dsl.toConstraint import gg.essential.elementa.effects.Effect import gg.essential.elementa.effects.ScissorEffect +import gg.essential.elementa.events.UICharEvent import gg.essential.elementa.events.UIClickEvent +import gg.essential.elementa.events.UIKeyEvent import gg.essential.elementa.events.UIScrollEvent import gg.essential.elementa.font.FontProvider import gg.essential.elementa.state.v2.ReferenceHolder @@ -119,7 +121,12 @@ abstract class UIComponent : Observable(), ReferenceHolder { get() = field.also { ownFlags += Flags.RequiresMouseMove } val mouseDragListeners = mutableListOf Unit>() get() = field.also { ownFlags += Flags.RequiresMouseDrag } + @Deprecated("See [ElementaVersion.V12]. Replaced by either the `onKeyPressed` or `onCharTyped` listener lists.") val keyTypedListeners = mutableListOf Unit>() + // Requires ElementaVersion.V12 + val onKeyPressed = mutableListOf Boolean>() + // Requires ElementaVersion.V12 + val onCharTyped = mutableListOf Boolean>() private var currentlyHovered = false private val beforeHideAnimations = mutableListOf Unit>() @@ -836,7 +843,9 @@ abstract class UIComponent : Observable(), ReferenceHolder { this.forEachChild { it.superCall() } } + @Deprecated("See [ElementaVersion.V12]. Listeners are now directly called from [Window].") open fun keyType(typedChar: Char, keyCode: Int) { + @Suppress("DEPRECATION") for (listener in keyTypedListeners) this.listener(typedChar, keyCode) } @@ -1021,11 +1030,15 @@ abstract class UIComponent : Observable(), ReferenceHolder { mouseScrollListeners.add { method.accept(it) } } + @Deprecated("See [ElementaVersion.V12]. Replaced by either the `onKeyPressed` or `onCharTyped` listener lists.") fun onKeyType(method: UIComponent.(typedChar: Char, keyCode: Int) -> Unit) = apply { + @Suppress("DEPRECATION") keyTypedListeners.add(method) } + @Deprecated("See [ElementaVersion.V12]. Replaced by either the `onKeyPressed` or `onCharTyped` listener lists.") fun onKeyTypeConsumer(method: BiConsumer) { + @Suppress("DEPRECATION") keyTypedListeners.add { t: Char, u: Int -> method.accept(t, u) } } diff --git a/src/main/kotlin/gg/essential/elementa/WindowScreen.kt b/src/main/kotlin/gg/essential/elementa/WindowScreen.kt index a50e9cbe..c138522d 100644 --- a/src/main/kotlin/gg/essential/elementa/WindowScreen.kt +++ b/src/main/kotlin/gg/essential/elementa/WindowScreen.kt @@ -2,6 +2,8 @@ package gg.essential.elementa import gg.essential.elementa.components.Window import gg.essential.elementa.constraints.animation.* +import gg.essential.elementa.events.UICharEvent +import gg.essential.elementa.events.UIKeyEvent import gg.essential.universal.UKeyboard import gg.essential.universal.UMatrixStack import gg.essential.universal.UMouse @@ -21,7 +23,7 @@ abstract class WindowScreen @JvmOverloads constructor( private val drawDefaultBackground: Boolean = true, restoreCurrentGuiOnClose: Boolean = false, newGuiScale: Int = -1 -) : UScreen(restoreCurrentGuiOnClose, newGuiScale) { +) : UScreen(restoreCurrentGuiOnClose, newGuiScale), UScreen.InputHandler { val window = Window(version) private var isInitialized = false @@ -35,8 +37,22 @@ abstract class WindowScreen @JvmOverloads constructor( ) : this(ElementaVersion.v0, enableRepeatKeys, drawDefaultBackground, restoreCurrentGuiOnClose, newGuiScale) init { - window.onKeyType { typedChar, keyCode -> - defaultKeyBehavior(typedChar, keyCode) + if (version >= ElementaVersion.v12) { + window.onKeyPressed.add { keyEvent -> + super.uKeyPressed(keyEvent.key, keyEvent.scanCode, keyEvent.modifiers) + } + window.onCharTyped.add { charEvent -> + super.uCharTyped(charEvent.codepoint) + } + } else { + @Suppress("DEPRECATION") + window.onKeyType { typedChar, keyCode -> + defaultKeyBehavior(typedChar, keyCode) + } + + // This will ensure that inputs go to the old `on*(): Unit` input functions + // rather than the V12+ `u*(): Boolean` functions + inputHandler = null } } @@ -60,6 +76,8 @@ abstract class WindowScreen @JvmOverloads constructor( window.draw(matrixStack) } + @Deprecated("See [ElementaVersion.V12]") + @Suppress("DEPRECATION") override fun onMouseClicked(mouseX: Double, mouseY: Double, mouseButton: Int) { super.onMouseClicked(mouseX, mouseY, mouseButton) @@ -79,6 +97,18 @@ abstract class WindowScreen @JvmOverloads constructor( window.mouseClick(adjustedMouseX, adjustedMouseY, mouseButton) } + // Called only when ElementaVersion >= V12 + override fun uMouseClicked(x: Double, y: Double, button: Int, modifiers: UKeyboard.Modifiers): Boolean { + super.uMouseClicked(x, y, button, modifiers) + + // We also need to pass along clicks + window.mouseClick(x, y, button) + + return false // TODO returning implementation, see uKeyPressed() + } + + @Deprecated("See [ElementaVersion.V12]") + @Suppress("DEPRECATION") override fun onMouseReleased(mouseX: Double, mouseY: Double, state: Int) { super.onMouseReleased(mouseX, mouseY, state) @@ -86,6 +116,16 @@ abstract class WindowScreen @JvmOverloads constructor( window.mouseRelease() } + // Called only when ElementaVersion >= V12 + override fun uMouseReleased(x: Double, y: Double, button: Int, modifiers: UKeyboard.Modifiers): Boolean { + super.uMouseReleased(x, y, button, modifiers) + + // We also need to pass along mouse releases + window.mouseRelease() + + return false // TODO returning implementation, see uKeyPressed() + } + @Suppress("DEPRECATION") @Deprecated( "Provided `delta` values have different units depending on Minecraft versions. See ElementaVersion.V11 for details.", @@ -100,6 +140,8 @@ abstract class WindowScreen @JvmOverloads constructor( } } + @Deprecated("See [ElementaVersion.V12]") + @Suppress("DEPRECATION") override fun onMouseScrolled(mouseX: Double, mouseY: Double, deltaHorizontal: Double, deltaVertical: Double) { super.onMouseScrolled(mouseX, mouseY, deltaHorizontal, deltaVertical) @@ -108,11 +150,32 @@ abstract class WindowScreen @JvmOverloads constructor( } } + // Called only when ElementaVersion >= V12 + override fun uMouseScrolled(x: Double, y: Double, scrollX: Double, scrollY: Double): Boolean { + super.uMouseScrolled(x, y, scrollX, scrollY) + + window.mouseScroll(scrollX, scrollY) + + return false // TODO returning implementation, see uKeyPressed() + } + + @Suppress("DEPRECATION") + @Deprecated("Method is not called when ElementaVersion >= V12. See ElementaVersion.V12 for details.") override fun onKeyPressed(keyCode: Int, typedChar: Char, modifiers: UKeyboard.Modifiers?) { // We also need to pass along typed keys window.keyType(typedChar, keyCode) } + // Called only when ElementaVersion >= V12 + override fun uKeyPressed(key: Int, scanCode: Int, modifiers: UKeyboard.Modifiers): Boolean { + return window.keyPressed(UIKeyEvent(key, scanCode, modifiers)) + } + + // Called only when ElementaVersion >= V12 + override fun uCharTyped(codepoint: Int): Boolean { + return window.charTyped(UICharEvent(codepoint)) + } + override fun initScreen(width: Int, height: Int) { window.onWindowResize() @@ -133,7 +196,9 @@ abstract class WindowScreen @JvmOverloads constructor( UKeyboard.allowRepeatEvents(false) } + @Deprecated("[See ElementaVersion.V12] This method is not called when ElementaVersion >= V12.") fun defaultKeyBehavior(typedChar: Char, keyCode: Int) { + @Suppress("DEPRECATION") super.onKeyPressed(keyCode, typedChar, UKeyboard.getModifiers()) } diff --git a/src/main/kotlin/gg/essential/elementa/components/ScrollComponent.kt b/src/main/kotlin/gg/essential/elementa/components/ScrollComponent.kt index f50f698e..8ae3e41a 100644 --- a/src/main/kotlin/gg/essential/elementa/components/ScrollComponent.kt +++ b/src/main/kotlin/gg/essential/elementa/components/ScrollComponent.kt @@ -144,6 +144,7 @@ class ScrollComponent constructor( private val mouseScrollLambda: UIComponent.(UIScrollEvent) -> Unit = { if (Window.of(this).version >= ElementaVersion.v5) { // new behavior + @Suppress("DEPRECATION") // Modifiers not provided for mouse sroll events val scrollDirection = if (!UKeyboard.isShiftKeyDown()) primaryScrollDirection else secondaryScrollDirection if (scrollDirection != null) { if (onScroll(it.delta.toFloat(), isHorizontal = scrollDirection == Direction.Horizontal) || !passthroughScroll) { @@ -157,6 +158,7 @@ class ScrollComponent constructor( } } else { // old behavior + @Suppress("DEPRECATION") // Modifiers not provided for mouse sroll events if (UKeyboard.isShiftKeyDown() && horizontalScrollEnabled) { onScroll(it.delta.toFloat(), isHorizontal = true) } else if (!UKeyboard.isShiftKeyDown() && verticalScrollEnabled) { diff --git a/src/main/kotlin/gg/essential/elementa/components/TreeListComponent.kt b/src/main/kotlin/gg/essential/elementa/components/TreeListComponent.kt index d811e9a2..6a396212 100644 --- a/src/main/kotlin/gg/essential/elementa/components/TreeListComponent.kt +++ b/src/main/kotlin/gg/essential/elementa/components/TreeListComponent.kt @@ -124,6 +124,7 @@ abstract class TreeNode { arrowComponent.onMouseClick { event -> event.stopImmediatePropagation() + @Suppress("DEPRECATION") //TODO pass modifiers when they become available, see [ElementaVersion.V12] val isRecursive = UKeyboard.isShiftKeyDown() if (opened) { diff --git a/src/main/kotlin/gg/essential/elementa/components/Window.kt b/src/main/kotlin/gg/essential/elementa/components/Window.kt index 0f3a0c99..6a54f5d4 100644 --- a/src/main/kotlin/gg/essential/elementa/components/Window.kt +++ b/src/main/kotlin/gg/essential/elementa/components/Window.kt @@ -7,6 +7,8 @@ import gg.essential.elementa.constraints.resolution.ConstraintResolutionGui import gg.essential.elementa.constraints.resolution.ConstraintResolver import gg.essential.elementa.constraints.resolution.ConstraintResolverV2 import gg.essential.elementa.effects.ScissorEffect +import gg.essential.elementa.events.UICharEvent +import gg.essential.elementa.events.UIKeyEvent import gg.essential.elementa.font.FontRenderer import gg.essential.elementa.utils.elementaDev import gg.essential.elementa.utils.requireMainThread @@ -329,6 +331,8 @@ class Window @JvmOverloads constructor( currentMouseButton = -1 } + @Suppress("DEPRECATION") + @Deprecated("See [ElementaVersion.V12].") override fun keyType(typedChar: Char, keyCode: Int) { if (hasErrored && version >= ElementaVersion.v7) { return @@ -347,6 +351,41 @@ class Window @JvmOverloads constructor( } } + // Called only when ElementaVersion >= V12 + fun keyPressed(keyEvent: UIKeyEvent): Boolean { + if (hasErrored) return false + + requireMainThread() + + for (listener in focusedComponent?.onKeyPressed ?: emptyList()) { + if (listener(keyEvent)) return true + } + + + for (listener in onKeyPressed) { + if (listener(keyEvent)) return true + } + + return false + } + + // Called only when ElementaVersion >= V12 + fun charTyped(charEvent: UICharEvent): Boolean { + if (hasErrored) return false + + requireMainThread() + + for (listener in focusedComponent?.onCharTyped ?: emptyList()) { + if (listener(charEvent)) return true + } + + for (listener in onCharTyped) { + if (listener(charEvent)) return true + } + + return false + } + internal var prevDraggedMouseX: Float? = null internal var prevDraggedMouseY: Float? = null diff --git a/src/main/kotlin/gg/essential/elementa/components/input/AbstractTextInput.kt b/src/main/kotlin/gg/essential/elementa/components/input/AbstractTextInput.kt index 9f85f7ee..3c5bccfe 100644 --- a/src/main/kotlin/gg/essential/elementa/components/input/AbstractTextInput.kt +++ b/src/main/kotlin/gg/essential/elementa/components/input/AbstractTextInput.kt @@ -81,37 +81,68 @@ abstract class AbstractTextInput( setHeight(lineHeight.pixels()) - onKeyType { typedChar, keyCode -> - if (!active) return@onKeyType + @Deprecated("See [ElementaVersion.V12]") + fun handleChar(typedChar: Char): Boolean { + if (!active) return false + + if (isAllowedCharacter(typedChar)) { // Most of the ASCII characters + commitTextAddition(typedChar.toString()) + return true + } + return false + } + + // Only used when ElementaVersion >= V12 + fun handleCodePoint(codepoint: Int): Boolean { + if (!active) return false + + // Replicated from old input check + if (codepoint != 167) { + commitTextAddition(String(Character.toChars(codepoint))) + return true + } + return false + } + + // Return value only used when ElementaVersion >= V12 + fun handleKeyCode(keyCode: Int, modifiers: UKeyboard.Modifiers): Boolean { + if (!active) return false if (keyCode == UKeyboard.KEY_ESCAPE) { releaseWindowFocus() - } else if (UKeyboard.isKeyComboCtrlA(keyCode)) { + return true + } else if (keyCode == UKeyboard.KEY_A && modifiers.isOnlyPlatformModifierActive()) { selectAll() - } else if (UKeyboard.isKeyComboCtrlC(keyCode) && hasSelection()) { + return true + } else if (keyCode == UKeyboard.KEY_C && modifiers.isOnlyPlatformModifierActive() && hasSelection()) { copySelection() - } else if (UKeyboard.isKeyComboCtrlX(keyCode) && hasSelection()) { + return true + } else if (keyCode == UKeyboard.KEY_X && modifiers.isOnlyPlatformModifierActive() && hasSelection()) { copySelection() deleteSelection() - } else if (UKeyboard.isKeyComboCtrlV(keyCode)) { + return true + } else if (keyCode == UKeyboard.KEY_V && modifiers.isOnlyPlatformModifierActive()) { commitTextAddition(UDesktop.getClipboardString()) - } else if (UKeyboard.isKeyComboCtrlZ(keyCode)) { + return true + } else if (keyCode == UKeyboard.KEY_Z && modifiers.isOnlyPlatformModifierActive()) { if (undoStack.isEmpty()) - return@onKeyType + return false val operationToUndo = undoStack.pop() operationToUndo.undo() redoStack.push(operationToUndo) - } else if (UKeyboard.isKeyComboCtrlShiftZ(keyCode) || UKeyboard.isKeyComboCtrlY(keyCode)) { + return true + } else if ((keyCode == UKeyboard.KEY_Z && modifiers.isPlatformModifierActive() && modifiers.isShift && !modifiers.isAlt) + || (keyCode == UKeyboard.KEY_Y && modifiers.isOnlyPlatformModifierActive()) + ) { if (redoStack.isEmpty()) - return@onKeyType + return false val operationToRedo = redoStack.pop() operationToRedo.redo() undoStack.push(operationToRedo) - } else if (isAllowedCharacter(typedChar)) { // Most of the ASCII characters - commitTextAddition(typedChar.toString()) + return true } else if (keyCode == UKeyboard.KEY_LEFT) { - val holdingShift = UKeyboard.isShiftKeyDown() - val holdingCtrl = UKeyboard.isCtrlKeyDown() + val holdingShift = modifiers.isShift + val holdingCtrl = modifiers.isCtrl val newCursorPosition = when { holdingCtrl -> getNearestWordBoundary(cursor, Direction.Left) @@ -121,14 +152,15 @@ abstract class AbstractTextInput( if (!holdingShift) { setCursorPosition(newCursorPosition) - return@onKeyType + return true } cursor = newCursorPosition cursorNeedsRefocus = true + return true } else if (keyCode == UKeyboard.KEY_RIGHT) { - val holdingShift = UKeyboard.isShiftKeyDown() - val holdingCtrl = UKeyboard.isCtrlKeyDown() + val holdingShift = modifiers.isShift + val holdingCtrl = modifiers.isCtrl val newCursorPosition = when { holdingCtrl -> getNearestWordBoundary(cursor, Direction.Right) @@ -138,11 +170,12 @@ abstract class AbstractTextInput( if (!holdingShift) { setCursorPosition(newCursorPosition) - return@onKeyType + return true } cursor = newCursorPosition cursorNeedsRefocus = true + return true } else if (keyCode == UKeyboard.KEY_UP) { val newVisualPos = if (cursor.line == 0) { LinePosition(0, 0, isVisual = true) @@ -151,12 +184,13 @@ abstract class AbstractTextInput( screenPosToVisualPos(currX, currY - lineHeight) } - if (UKeyboard.isShiftKeyDown()) { + if (modifiers.isShift) { cursor = newVisualPos cursorNeedsRefocus = true } else { setCursorPosition(newVisualPos) } + return true } else if (keyCode == UKeyboard.KEY_DOWN) { val newVisualPos = if (cursor.line == visualLines.lastIndex) { LinePosition(visualLines.lastIndex, visualLines.last().length, isVisual = true) @@ -165,55 +199,73 @@ abstract class AbstractTextInput( screenPosToVisualPos(currX, currY + lineHeight) } - if (UKeyboard.isShiftKeyDown()) { + if (modifiers.isShift) { cursor = newVisualPos cursorNeedsRefocus = true } else { setCursorPosition(newVisualPos) } + return true } else if (keyCode == UKeyboard.KEY_BACKSPACE) { if (hasSelection()) { deleteSelection() } else if (!cursor.isAtAbsoluteStart) { - val startPos = if (UKeyboard.isCtrlKeyDown()) { + val startPos = if (modifiers.isCtrl) { getNearestWordBoundary(cursor, Direction.Left) } else cursor.offsetColumn(-1).toTextualPos() val endPos = cursor.toTextualPos() commitTextRemoval(startPos, endPos, selectAfterUndo = false) } + return true } else if (keyCode == UKeyboard.KEY_DELETE) { if (hasSelection()) { deleteSelection() } else if (!cursor.isAtAbsoluteEnd) { val startPos = cursor.toTextualPos() - val endPos = if (UKeyboard.isCtrlKeyDown()) { + val endPos = if (modifiers.isCtrl) { getNearestWordBoundary(cursor, Direction.Right) } else cursor.offsetColumn(1).toTextualPos() commitTextRemoval(startPos, endPos, selectAfterUndo = false) } + return true } else if (keyCode == UKeyboard.KEY_HOME) { - if (UKeyboard.isShiftKeyDown()) { + if (modifiers.isShift) { cursor = cursor.withColumn(0) cursorNeedsRefocus = true } else { setCursorPosition(cursor.withColumn(0)) } + return true } else if (keyCode == UKeyboard.KEY_END) { cursor.withColumn(visualLines[cursor.line].length).also { - if (UKeyboard.isShiftKeyDown()) { + if (modifiers.isShift) { cursor = it cursorNeedsRefocus = true } else { setCursorPosition(it) } } + return true } else if (keyCode == UKeyboard.KEY_ENTER) { // Enter - onEnterPressed() + onEnterPressed(modifiers) + return true } + + return false } + // Pre ElementaVersion.V12 function + @Suppress("DEPRECATION") + onKeyType { char, key -> + handleKeyCode(key, UKeyboard.getKeyModifiers()) || handleChar(char) + } + + // ElementaVersion.V12+ functions + onCharTyped.add { charEvent -> handleCodePoint(charEvent.codepoint) } + onKeyPressed.add { keyEvent -> handleKeyCode(keyEvent.key, keyEvent.modifiers) } + onMouseScroll { val heightDifference = getHeight() - visualLines.size * lineHeight if (heightDifference > 0) @@ -378,7 +430,9 @@ abstract class AbstractTextInput( protected abstract fun screenPosToVisualPos(x: Float, y: Float): LinePosition protected abstract fun recalculateDimensions() protected abstract fun textToLines(text: String): List - protected abstract fun onEnterPressed() + @Deprecated("Inconsistent with key input event modifiers.", replaceWith = ReplaceWith("onEnterPressed(modifiers)")) + protected open fun onEnterPressed() = onEnterPressed(UKeyboard.getKeyModifiers()) + protected abstract fun onEnterPressed(modifiers: UKeyboard.Modifiers) fun setActive(isActive: Boolean) = apply { active = isActive diff --git a/src/main/kotlin/gg/essential/elementa/components/input/UIMultilineTextInput.kt b/src/main/kotlin/gg/essential/elementa/components/input/UIMultilineTextInput.kt index 41670ecd..1fda414a 100644 --- a/src/main/kotlin/gg/essential/elementa/components/input/UIMultilineTextInput.kt +++ b/src/main/kotlin/gg/essential/elementa/components/input/UIMultilineTextInput.kt @@ -63,8 +63,8 @@ class UIMultilineTextInput @JvmOverloads constructor( setHeight((lineHeight * visualLines.size).pixels().coerceAtMost(maxHeight!!)) } - override fun onEnterPressed() { - if (UKeyboard.isShiftKeyDown()) { + override fun onEnterPressed(modifiers: UKeyboard.Modifiers) { + if (modifiers.isShift) { commitTextAddition("\n") updateAction(getText()) } else { diff --git a/src/main/kotlin/gg/essential/elementa/components/input/UITextInput.kt b/src/main/kotlin/gg/essential/elementa/components/input/UITextInput.kt index 6bac59d1..2ceae62f 100644 --- a/src/main/kotlin/gg/essential/elementa/components/input/UITextInput.kt +++ b/src/main/kotlin/gg/essential/elementa/components/input/UITextInput.kt @@ -2,6 +2,7 @@ package gg.essential.elementa.components.input import gg.essential.elementa.constraints.WidthConstraint import gg.essential.elementa.dsl.* +import gg.essential.universal.UKeyboard import gg.essential.universal.UMatrixStack import java.awt.Color @@ -102,7 +103,7 @@ open class UITextInput @JvmOverloads constructor( return listOf(text) } - override fun onEnterPressed() { + override fun onEnterPressed(modifiers: UKeyboard.Modifiers) { activateAction(getText()) } diff --git a/src/main/kotlin/gg/essential/elementa/components/inspector/InspectorNode.kt b/src/main/kotlin/gg/essential/elementa/components/inspector/InspectorNode.kt index 71876b8e..de67dfbc 100644 --- a/src/main/kotlin/gg/essential/elementa/components/inspector/InspectorNode.kt +++ b/src/main/kotlin/gg/essential/elementa/components/inspector/InspectorNode.kt @@ -50,6 +50,7 @@ class InspectorNode(private val inspector: Inspector, val targetComponent: UICom event.stopImmediatePropagation() toggleSelection() }.onMouseScroll { event -> + @Suppress("DEPRECATION") // Modifiers not provided for mouse sroll events if (!UKeyboard.isShiftKeyDown()) return@onMouseScroll event.stopImmediatePropagation() inspector.scrollSource(this@InspectorNode, event.delta > 0) diff --git a/src/main/kotlin/gg/essential/elementa/events/UIKeyboardEvents.kt b/src/main/kotlin/gg/essential/elementa/events/UIKeyboardEvents.kt new file mode 100644 index 00000000..d003e07e --- /dev/null +++ b/src/main/kotlin/gg/essential/elementa/events/UIKeyboardEvents.kt @@ -0,0 +1,30 @@ +package gg.essential.elementa.events + +import gg.essential.universal.UKeyboard + +/** + * Represents a key typing event whose propagation can be stopped from continuing on to Minecraft + * + * Requires ElementaVersion.V12 + */ +class UIKeyEvent( + val key: Int, + val scanCode: Int, + val modifiers: UKeyboard.Modifiers, +) { + val isCopy get() = key == UKeyboard.KEY_C && modifiers.isOnlyPlatformModifierActive() + val isCut get() = key == UKeyboard.KEY_X && modifiers.isOnlyPlatformModifierActive() + val isPaste get() = key == UKeyboard.KEY_V && modifiers.isOnlyPlatformModifierActive() +} + + +/** + * Represents a character typing event whose propagation can be stopped from continuing on to Minecraft + * + * Requires ElementaVersion.V12 + */ +class UICharEvent( + val codepoint: Int, +) { + val string = String(Character.toChars(codepoint)) +} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/elementa/markdown/MarkdownComponent.kt b/src/main/kotlin/gg/essential/elementa/markdown/MarkdownComponent.kt index 4193a417..f9437aab 100644 --- a/src/main/kotlin/gg/essential/elementa/markdown/MarkdownComponent.kt +++ b/src/main/kotlin/gg/essential/elementa/markdown/MarkdownComponent.kt @@ -110,11 +110,22 @@ class MarkdownComponent( grabWindowFocus() } + // Pre ElementaVersion.V12 function + @Suppress("DEPRECATION") onKeyType { _, keyCode -> if (selection != null && keyCode == UKeyboard.KEY_C && UKeyboard.isCtrlKeyDown()) { UDesktop.setClipboardString(drawables.selectedText(UKeyboard.isShiftKeyDown())) } } + + // ElementaVersion.V12+ function + onKeyPressed.add { keyEvent -> + if (selection != null && keyEvent.isCopy) { + UDesktop.setClipboardString(drawables.selectedText(keyEvent.modifiers.isShift)) + return@add true + } + return@add false + } } configState.onSetValue { reparse()