diff --git a/src/main/java/net/shadowfacts/phycon/mixin/client/TextFieldWidgetAccessor.java b/src/main/java/net/shadowfacts/phycon/mixin/client/TextFieldWidgetAccessor.java new file mode 100644 index 0000000..f6250c7 --- /dev/null +++ b/src/main/java/net/shadowfacts/phycon/mixin/client/TextFieldWidgetAccessor.java @@ -0,0 +1,14 @@ +package net.shadowfacts.phycon.mixin.client; + +import net.minecraft.client.gui.widget.TextFieldWidget; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +/** + * @author shadowfacts + */ +@Mixin(TextFieldWidget.class) +public interface TextFieldWidgetAccessor { + @Accessor("maxLength") + int cacao_getMaxLength(); +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt index c5d8c0e..36fe89e 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt @@ -24,14 +24,15 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title), private val _windows = LinkedList() /** * The list of windows that belong to this screen. + * + * The window at the end of this list is the active window is the only window that will receive input events. + * * This list should never be modified directly, only by using the [addWindow]/[removeWindow] methods. */ override val windows: List = _windows /** - * Adds the given window to this screen's window list. - * By default, the new window is added at the tail of the window list, making it the active window. - * Only the active window will receive input events. + * Adds the given window to this screen's window list at the given position. * * @param window The Window to add to this screen. * @param index The index to insert the window into the window list at. @@ -47,6 +48,9 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title), return window } + /** + * Adds the given window to the end of this screen's window list, making it the active window. + */ override fun addWindow(window: T): T { return addWindow(window, _windows.size) } diff --git a/src/main/kotlin/net/shadowfacts/cacao/Responder.kt b/src/main/kotlin/net/shadowfacts/cacao/Responder.kt index 272b04f..e52c0d8 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/Responder.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/Responder.kt @@ -4,17 +4,42 @@ import net.shadowfacts.cacao.util.KeyModifiers import net.shadowfacts.cacao.window.Window /** + * A responder is an object which participates in the responder chain, a mechanism for propagating indirect events (i.e. + * events for which there is no on-screen position to determine the targeted view, such as key presses) through the + * view hierarchy. + * * @author shadowfacts */ interface Responder { + /** + * The window that this responder is part of. + */ val window: Window? + /** + * Whether this responder is the first responder of its window. + * + * `false` if [window] is null. + * + * @see Window.firstResponder + */ val isFirstResponder: Boolean get() = window?.firstResponder === this + /** + * The next responder in the chain after this. The next responder will receive an event if this responder did not + * accept it. + * + * The next responder may be `null` if this responder is at the end of the chain. + */ val nextResponder: Responder? + /** + * Makes this responder become the window's first responder. + * @throws RuntimeException if [window] is null + * @see Window.firstResponder + */ fun becomeFirstResponder() { if (window == null) { throw RuntimeException("Cannot become first responder while not in Window") @@ -22,8 +47,17 @@ interface Responder { window!!.firstResponder = this } + /** + * Called immediately after this responder has become the window's first responder. + * @see Window.firstResponder + */ fun didBecomeFirstResponder() {} + /** + * Removes this object as the window's first responder. + * @throws RuntimeException if [window] is null + * @see Window.firstResponder + */ fun resignFirstResponder() { if (window == null) { throw RuntimeException("Cannot resign first responder while not in Window") @@ -31,14 +65,36 @@ interface Responder { window!!.firstResponder = null } + /** + * Called immediately before this object is removed as the window's first responder. + * @see Window.firstResponder + */ fun didResignFirstResponder() {} + /** + * Called when a character has been typed. + * + * @param char The character that was typed. + * @param modifiers The key modifiers that were held down when the character was typed. + * @return Whether this responder accepted the event. If `true`, it will not be passed to the next responder. + */ fun charTyped(char: Char, modifiers: KeyModifiers): Boolean { return false } + /** + * Called when a keyboard key is pressed. + * + * If the pressed key is a typed character, [charTyped] will also be called. The order in which the methods are + * invoked is undefined and should not be relied upon. + * + * @param keyCode The integer code of the key that was pressed. + * @param modifiers The key modifiers that were held down when the character was typed. + * @return Whether this responder accepted the event. If `true`, it will not be passed to the next responder. + * @see org.lwjgl.glfw.GLFW for key code constants + */ fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean { return false } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/KeyModifiers.kt b/src/main/kotlin/net/shadowfacts/cacao/util/KeyModifiers.kt index 013dcb3..fb5cf9e 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/KeyModifiers.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/KeyModifiers.kt @@ -3,7 +3,10 @@ package net.shadowfacts.cacao.util import org.lwjgl.glfw.GLFW /** + * A set of modifier keys that are being pressed at given instant. + * * @author shadowfacts + * @param value The integer representation of the pressed modifiers as received from GLFW. */ class KeyModifiers(val value: Int) { @@ -29,4 +32,4 @@ class KeyModifiers(val value: Int) { return (value and mod) == mod } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt index fbed536..81c2e70 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt @@ -29,6 +29,10 @@ open class View(): Responder { */ override var window: Window? = null + /** + * The next responder after this one. + * For views, the next responder is the view's superview. + */ override val nextResponder: Responder? // todo: should the view controller be a Responder? get() = superview @@ -39,7 +43,7 @@ open class View(): Responder { } } /** - * The constraint solver used by the [net.shadowfacts.cacao.Window] this view belongs to. + * The constraint solver used by the [Window] this view belongs to. * Not initialized until [wasAdded] called, using it before that will throw a runtime exception. */ var solver: Solver by solverDelegate @@ -189,7 +193,9 @@ open class View(): Responder { } /** - * Removes the given view from this view's children and removes all constraints associated with it. + * Removes the given view from this view's children and removes all constraints that connect the subview or any of + * its children (recursively) to a view outside of the subview's hierarchy. Constraints internal to the subview's + * hierarchy (e.g., one between the subview and its child) will be left in place. * * @param view The view to removed as a child of this view. * @throws RuntimeException If the given [view] is not a subview of this view. @@ -199,7 +205,6 @@ open class View(): Responder { throw RuntimeException("Cannot remove subview whose superview is not this view") } - _subviews.remove(view) subviewsSortedByZIndex = subviews.sortedBy(View::zIndex) @@ -350,7 +355,13 @@ open class View(): Responder { open fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {} /** - * Called when this view is clicked. May delegate to [subviews]. + * Called when this view is clicked. + * + * The base implementation of this method forwards the click event to the first subview (sorted by [zIndex]) that + * contains the clicked point. Additionally, any subviews of this view that do not contain the clicked point receive + * the [mouseClickedOutside] event. If multiple views contain the point, any after one that returns `true` from this + * method will not receive the event or the click-outside event. + * * If overridden, the super-class method does not have to be called. Intentionally not calling it may be used * to prevent [subviews] from receiving click events. * @@ -373,6 +384,14 @@ open class View(): Responder { return result } + /** + * Called when the mouse was clicked outside this view. + * + * The base implementation of this method simply forwards the event to all of this view's subviews. + * + * @param point The clicked point _in the coordinate space of this view_. + * @param mouseButton The mouse button used to click. + */ open fun mouseClickedOutside(point: Point, mouseButton: MouseButton) { for (view in subviews) { val pointInView = convert(point, to = view) diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt b/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt index 6a513d3..405347f 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt @@ -10,8 +10,8 @@ import net.shadowfacts.cacao.view.View import net.shadowfacts.kiwidsl.dsl /** - * A abstract button class. Cannot be constructed directly, used for creating button implementations with their own logic. - * Use [Button] for a generic no-frills button. + * An abstract button class. Cannot be constructed directly, used for creating button implementations with their own + * logic. Use [Button] for a generic no-frills button. * * @author shadowfacts * @param Impl The type of the concrete implementation of the button. diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/button/Button.kt b/src/main/kotlin/net/shadowfacts/cacao/view/button/Button.kt index b9ddc2f..f390d27 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/button/Button.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/button/Button.kt @@ -8,9 +8,14 @@ import net.shadowfacts.cacao.view.View * @author shadowfacts * @param content The [View] that provides the content of this button. * @param padding The padding between the [content] and the edges of the button. + * @param handler The handler function to invoke when this button is pressed. */ -class Button(content: View, padding: Double = 4.0): AbstractButton