Cacao: More docs
This commit is contained in:
parent
500ad94442
commit
2c19b8456b
|
@ -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();
|
||||||
|
}
|
|
@ -24,14 +24,15 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
|
||||||
private val _windows = LinkedList<Window>()
|
private val _windows = LinkedList<Window>()
|
||||||
/**
|
/**
|
||||||
* The list of windows that belong to this screen.
|
* 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.
|
* This list should never be modified directly, only by using the [addWindow]/[removeWindow] methods.
|
||||||
*/
|
*/
|
||||||
override val windows: List<Window> = _windows
|
override val windows: List<Window> = _windows
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the given window to this screen's window list.
|
* Adds the given window to this screen's window list at the given position.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* @param window The Window to add to this screen.
|
* @param window The Window to add to this screen.
|
||||||
* @param index The index to insert the window into the window list at.
|
* @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
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given window to the end of this screen's window list, making it the active window.
|
||||||
|
*/
|
||||||
override fun <T : Window> addWindow(window: T): T {
|
override fun <T : Window> addWindow(window: T): T {
|
||||||
return addWindow(window, _windows.size)
|
return addWindow(window, _windows.size)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,42 @@ import net.shadowfacts.cacao.util.KeyModifiers
|
||||||
import net.shadowfacts.cacao.window.Window
|
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
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
interface Responder {
|
interface Responder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The window that this responder is part of.
|
||||||
|
*/
|
||||||
val window: Window?
|
val window: Window?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this responder is the first responder of its window.
|
||||||
|
*
|
||||||
|
* `false` if [window] is null.
|
||||||
|
*
|
||||||
|
* @see Window.firstResponder
|
||||||
|
*/
|
||||||
val isFirstResponder: Boolean
|
val isFirstResponder: Boolean
|
||||||
get() = window?.firstResponder === this
|
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?
|
val nextResponder: Responder?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes this responder become the window's first responder.
|
||||||
|
* @throws RuntimeException if [window] is null
|
||||||
|
* @see Window.firstResponder
|
||||||
|
*/
|
||||||
fun becomeFirstResponder() {
|
fun becomeFirstResponder() {
|
||||||
if (window == null) {
|
if (window == null) {
|
||||||
throw RuntimeException("Cannot become first responder while not in Window")
|
throw RuntimeException("Cannot become first responder while not in Window")
|
||||||
|
@ -22,8 +47,17 @@ interface Responder {
|
||||||
window!!.firstResponder = this
|
window!!.firstResponder = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called immediately after this responder has become the window's first responder.
|
||||||
|
* @see Window.firstResponder
|
||||||
|
*/
|
||||||
fun didBecomeFirstResponder() {}
|
fun didBecomeFirstResponder() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes this object as the window's first responder.
|
||||||
|
* @throws RuntimeException if [window] is null
|
||||||
|
* @see Window.firstResponder
|
||||||
|
*/
|
||||||
fun resignFirstResponder() {
|
fun resignFirstResponder() {
|
||||||
if (window == null) {
|
if (window == null) {
|
||||||
throw RuntimeException("Cannot resign first responder while not in Window")
|
throw RuntimeException("Cannot resign first responder while not in Window")
|
||||||
|
@ -31,12 +65,34 @@ interface Responder {
|
||||||
window!!.firstResponder = null
|
window!!.firstResponder = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called immediately before this object is removed as the window's first responder.
|
||||||
|
* @see Window.firstResponder
|
||||||
|
*/
|
||||||
fun didResignFirstResponder() {}
|
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 {
|
fun charTyped(char: Char, modifiers: KeyModifiers): Boolean {
|
||||||
return false
|
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 {
|
fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@ package net.shadowfacts.cacao.util
|
||||||
import org.lwjgl.glfw.GLFW
|
import org.lwjgl.glfw.GLFW
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A set of modifier keys that are being pressed at given instant.
|
||||||
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
|
* @param value The integer representation of the pressed modifiers as received from GLFW.
|
||||||
*/
|
*/
|
||||||
class KeyModifiers(val value: Int) {
|
class KeyModifiers(val value: Int) {
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,10 @@ open class View(): Responder {
|
||||||
*/
|
*/
|
||||||
override var window: Window? = null
|
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?
|
override val nextResponder: Responder?
|
||||||
// todo: should the view controller be a Responder?
|
// todo: should the view controller be a Responder?
|
||||||
get() = superview
|
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.
|
* Not initialized until [wasAdded] called, using it before that will throw a runtime exception.
|
||||||
*/
|
*/
|
||||||
var solver: Solver by solverDelegate
|
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.
|
* @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.
|
* @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")
|
throw RuntimeException("Cannot remove subview whose superview is not this view")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_subviews.remove(view)
|
_subviews.remove(view)
|
||||||
subviewsSortedByZIndex = subviews.sortedBy(View::zIndex)
|
subviewsSortedByZIndex = subviews.sortedBy(View::zIndex)
|
||||||
|
|
||||||
|
@ -350,7 +355,13 @@ open class View(): Responder {
|
||||||
open fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {}
|
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
|
* 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.
|
* to prevent [subviews] from receiving click events.
|
||||||
*
|
*
|
||||||
|
@ -373,6 +384,14 @@ open class View(): Responder {
|
||||||
return result
|
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) {
|
open fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
||||||
for (view in subviews) {
|
for (view in subviews) {
|
||||||
val pointInView = convert(point, to = view)
|
val pointInView = convert(point, to = view)
|
||||||
|
|
|
@ -10,8 +10,8 @@ import net.shadowfacts.cacao.view.View
|
||||||
import net.shadowfacts.kiwidsl.dsl
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A abstract button class. Cannot be constructed directly, used for creating button implementations with their own logic.
|
* An abstract button class. Cannot be constructed directly, used for creating button implementations with their own
|
||||||
* Use [Button] for a generic no-frills button.
|
* logic. Use [Button] for a generic no-frills button.
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
* @param Impl The type of the concrete implementation of the button.
|
* @param Impl The type of the concrete implementation of the button.
|
||||||
|
|
|
@ -8,9 +8,14 @@ import net.shadowfacts.cacao.view.View
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
* @param content The [View] that provides the content of this button.
|
* @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 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<Button>(content, padding) {
|
class Button(
|
||||||
constructor(content: View, padding: Double = 4.0, handler: (Button) -> Unit): this(content, padding) {
|
content: View,
|
||||||
|
padding: Double = 4.0,
|
||||||
|
handler: ((Button) -> Unit)? = null
|
||||||
|
): AbstractButton<Button>(content, padding) {
|
||||||
|
init {
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import net.shadowfacts.cacao.view.Label
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
* @param initialValue The initial enum value for this button.
|
* @param initialValue The initial enum value for this button.
|
||||||
* @param localizer A function that takes an enum value and converts into a string for the button's label.
|
* @param localizer A function that takes an enum value and converts into a [Text] for the button's label.
|
||||||
*/
|
*/
|
||||||
class EnumButton<E: Enum<E>>(
|
class EnumButton<E: Enum<E>>(
|
||||||
initialValue: E,
|
initialValue: E,
|
||||||
|
|
|
@ -10,26 +10,58 @@ import net.shadowfacts.cacao.util.KeyModifiers
|
||||||
import net.shadowfacts.cacao.util.MouseButton
|
import net.shadowfacts.cacao.util.MouseButton
|
||||||
import net.shadowfacts.cacao.util.RenderHelper
|
import net.shadowfacts.cacao.util.RenderHelper
|
||||||
import net.shadowfacts.cacao.view.View
|
import net.shadowfacts.cacao.view.View
|
||||||
|
import net.shadowfacts.phycon.mixin.client.TextFieldWidgetAccessor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An abstract text field class. Cannot be constructed directly, use for creating other text fields with more specific
|
||||||
|
* behavior. Use [TextField] for a plain text field.
|
||||||
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
|
* @param Impl The type of the concrete implementation of the text field. Used to allow the [handler] to receive the
|
||||||
|
* the exact type of text field.
|
||||||
|
* @param initialText The initial value of the text field.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
|
abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
|
||||||
initialText: String
|
initialText: String
|
||||||
): View() {
|
): View() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that is invoked when the text in this text field changes.
|
||||||
|
*/
|
||||||
var handler: ((Impl) -> Unit)? = null
|
var handler: ((Impl) -> Unit)? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the text field is disabled.
|
||||||
|
* Disabled text fields cannot be interacted with.
|
||||||
|
*/
|
||||||
var disabled = false
|
var disabled = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this text field is focused (i.e. [isFirstResponder]) and receives key events.
|
||||||
|
*/
|
||||||
val focused: Boolean
|
val focused: Boolean
|
||||||
get() = isFirstResponder
|
get() = isFirstResponder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current text of this text field.
|
||||||
|
*/
|
||||||
var text: String
|
var text: String
|
||||||
get() = minecraftWidget.text
|
get() = minecraftWidget.text
|
||||||
set(value) {
|
set(value) {
|
||||||
minecraftWidget.text = value
|
minecraftWidget.text = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of text that this text field can hold.
|
||||||
|
*
|
||||||
|
* Defaults to the Minecraft text field's maximum length (currently 32, subject to change).
|
||||||
|
*/
|
||||||
|
var maxLength: Int
|
||||||
|
get() = (minecraftWidget as TextFieldWidgetAccessor).cacao_getMaxLength()
|
||||||
|
set(value) {
|
||||||
|
minecraftWidget.setMaxLength(value)
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var originInWindow: Point
|
private lateinit var originInWindow: Point
|
||||||
private var minecraftWidget = ProxyWidget()
|
private var minecraftWidget = ProxyWidget()
|
||||||
|
|
||||||
|
@ -38,6 +70,9 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
|
||||||
minecraftWidget.setTextPredicate { this.validate(it) }
|
minecraftWidget.setTextPredicate { this.validate(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function used by subclasses to determine whether a proposed value is acceptable for this field.
|
||||||
|
*/
|
||||||
abstract fun validate(proposedText: String): Boolean
|
abstract fun validate(proposedText: String): Boolean
|
||||||
|
|
||||||
override fun didLayout() {
|
override fun didLayout() {
|
||||||
|
@ -118,7 +153,7 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: label for the TextFieldWidget?
|
// todo: label for the TextFieldWidget?
|
||||||
class ProxyWidget: TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 0, 0, LiteralText("")) {
|
private class ProxyWidget: TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 0, 0, LiteralText("")) {
|
||||||
// AbstractButtonWidget.height is protected
|
// AbstractButtonWidget.height is protected
|
||||||
fun setHeight(height: Int) {
|
fun setHeight(height: Int) {
|
||||||
this.height = height
|
this.height = height
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
package net.shadowfacts.cacao.view.textfield
|
package net.shadowfacts.cacao.view.textfield
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A simple, no-frills text field. This text field accepts any value.
|
||||||
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
|
* @param initialText The initial value of this text field.
|
||||||
|
* @param handler A function that is invoked when the value of the text field changes.
|
||||||
*/
|
*/
|
||||||
class TextField(initialText: String): AbstractTextField<TextField>(initialText) {
|
class TextField(
|
||||||
constructor(initialText: String, handler: (TextField) -> Unit): this(initialText) {
|
initialText: String,
|
||||||
|
handler: ((TextField) -> Unit)? = null
|
||||||
|
): AbstractTextField<TextField>(initialText) {
|
||||||
|
init {
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import net.shadowfacts.cacao.geometry.Axis
|
||||||
import net.shadowfacts.cacao.geometry.Point
|
import net.shadowfacts.cacao.geometry.Point
|
||||||
import net.shadowfacts.cacao.geometry.Rect
|
import net.shadowfacts.cacao.geometry.Rect
|
||||||
import net.shadowfacts.cacao.geometry.Size
|
import net.shadowfacts.cacao.geometry.Size
|
||||||
import net.shadowfacts.cacao.util.Color
|
|
||||||
import net.shadowfacts.cacao.util.MouseButton
|
import net.shadowfacts.cacao.util.MouseButton
|
||||||
import net.shadowfacts.cacao.util.texture.NinePatchTexture
|
import net.shadowfacts.cacao.util.texture.NinePatchTexture
|
||||||
import net.shadowfacts.cacao.util.texture.Texture
|
import net.shadowfacts.cacao.util.texture.Texture
|
||||||
|
@ -17,22 +16,69 @@ import net.shadowfacts.cacao.view.TextureView
|
||||||
import net.shadowfacts.cacao.view.View
|
import net.shadowfacts.cacao.view.View
|
||||||
import net.shadowfacts.cacao.view.button.AbstractButton
|
import net.shadowfacts.cacao.view.button.AbstractButton
|
||||||
import net.shadowfacts.kiwidsl.dsl
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
|
import java.lang.RuntimeException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A tab view controller is divided into two sections: a tab bar at the top, and a content view at the bottom.
|
||||||
|
*
|
||||||
|
* The tab bar contains a tab button for each of the tabs in the VC and the content view contains the view of the
|
||||||
|
* active tab's view controller.
|
||||||
|
*
|
||||||
|
* The active tab's view controller is also added as a child of the tab view controller.
|
||||||
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
|
* @param T The type of the tab objects this view controller uses.
|
||||||
|
* @param tabs The list of tabs in this controller.
|
||||||
|
* @param initialTab The tab that is initially selected when the controller is first created.
|
||||||
|
* @param onTabChange A function invoked immediately after the active tab has changed (and the content view has been
|
||||||
|
* updated).
|
||||||
*/
|
*/
|
||||||
class TabViewController<T: TabViewController.Tab>(
|
class TabViewController<T: TabViewController.Tab>(
|
||||||
val tabs: List<T>,
|
val tabs: List<T>,
|
||||||
initalTab: T = tabs.first()
|
initialTab: T = tabs.first(),
|
||||||
|
val onTabChange: ((T) -> Unit)? = null
|
||||||
): ViewController() {
|
): ViewController() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Tab interface defines the requirements for tab objects that can be used with this view controller.
|
||||||
|
*
|
||||||
|
* This is an interface to allow for tab objects to carry additional data. A simple implementation is provided.
|
||||||
|
* @see SimpleTab
|
||||||
|
*/
|
||||||
interface Tab {
|
interface Tab {
|
||||||
|
/**
|
||||||
|
* The view displayed on the button for this tab.
|
||||||
|
*/
|
||||||
val tabView: View
|
val tabView: View
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tooltip displayed when the button for this tab is hovered. `null` if no tooltip should be shown.
|
||||||
|
*/
|
||||||
val tooltip: Text?
|
val tooltip: Text?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view controller used as content when this tab is active. When switching tabs, the returned content VC
|
||||||
|
* may be reused or created from scratch each time.
|
||||||
|
*/
|
||||||
val controller: ViewController
|
val controller: ViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentTab: T = initalTab
|
/**
|
||||||
|
* A simple [Tab] implementation that provides the minimum necessary information.
|
||||||
|
* @param tabView The view to display on the tab's button.
|
||||||
|
* @param tooltip The tooltip to display when the tab's button is hovered (or `null`, if none).
|
||||||
|
* @param controller The content view controller for this tab.
|
||||||
|
*/
|
||||||
|
class SimpleTab(
|
||||||
|
override val tabView: View,
|
||||||
|
override val tooltip: Text? = null,
|
||||||
|
override val controller: ViewController,
|
||||||
|
): Tab
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently selected tab.
|
||||||
|
*/
|
||||||
|
var currentTab: T = initialTab
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private lateinit var tabButtons: List<TabButton<T>>
|
private lateinit var tabButtons: List<TabButton<T>>
|
||||||
|
@ -57,7 +103,7 @@ class TabViewController<T: TabViewController.Tab>(
|
||||||
|
|
||||||
tabButtons = tabs.mapIndexed { index, tab ->
|
tabButtons = tabs.mapIndexed { index, tab ->
|
||||||
val btn = TabButton(tab)
|
val btn = TabButton(tab)
|
||||||
btn.handler = this::selectTab
|
btn.handler = { selectTab(it.tab) }
|
||||||
if (tab == currentTab) {
|
if (tab == currentTab) {
|
||||||
btn.setSelected(true)
|
btn.setSelected(true)
|
||||||
}
|
}
|
||||||
|
@ -91,16 +137,30 @@ class TabViewController<T: TabViewController.Tab>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectTab(button: TabButton<T>) {
|
/**
|
||||||
|
* Sets the provided tab as the currently active tab for this controller. Updates the state of tab bar buttons and
|
||||||
|
* swaps the content view controller.
|
||||||
|
*
|
||||||
|
* After the tab and the content are changed, [onTabChange] is invoked.
|
||||||
|
*
|
||||||
|
* @throws RuntimeException If the provided tab was not passed in as part of the [tabs] list.
|
||||||
|
*/
|
||||||
|
fun selectTab(tab: T) {
|
||||||
|
if (!tabs.contains(tab)) {
|
||||||
|
throw RuntimeException("Cannot activate tab not in TabViewController.tabs")
|
||||||
|
}
|
||||||
|
|
||||||
val oldTab = currentTab
|
val oldTab = currentTab
|
||||||
currentTab = button.tab
|
currentTab = tab
|
||||||
|
|
||||||
// todo: unselect old button
|
tabButtons.forEach {
|
||||||
tabButtons.forEach { it.setSelected(false) }
|
it.setSelected(it.tab === tab)
|
||||||
|
}
|
||||||
oldTab.controller.removeFromParent()
|
oldTab.controller.removeFromParent()
|
||||||
|
|
||||||
button.setSelected(true)
|
|
||||||
embedChild(currentTab.controller, tabVCContainer)
|
embedChild(currentTab.controller, tabVCContainer)
|
||||||
|
|
||||||
|
onTabChange?.invoke(currentTab)
|
||||||
|
|
||||||
// todo: setNeedsLayout
|
// todo: setNeedsLayout
|
||||||
window!!.layout()
|
window!!.layout()
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,9 @@ abstract class ViewController {
|
||||||
view = View()
|
view = View()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the view for this controller has already been loaded.
|
||||||
|
*/
|
||||||
val isViewLoaded: Boolean
|
val isViewLoaded: Boolean
|
||||||
get() = ::view.isInitialized
|
get() = ::view.isInitialized
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,18 @@ open class Window(
|
||||||
*/
|
*/
|
||||||
val centerYAnchor = Variable("centerY")
|
val centerYAnchor = Variable("centerY")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first responder of the a window is the first object that receives indirect events (e.g., keypresses).
|
||||||
|
*
|
||||||
|
* When an indirect event is received by the window, it is given to the first responder. If the first responder does
|
||||||
|
* not accept it (i.e. returns `false` from the appropriate method), the event will be passed to that responder's
|
||||||
|
* [Responder.nextResponder], and so on.
|
||||||
|
*
|
||||||
|
* The following is the order of events when setting this property:
|
||||||
|
* 1. The old first responder (if any) has [Responder.didResignFirstResponder] invoked.
|
||||||
|
* 2. The value of the field is updated.
|
||||||
|
* 3. The new value (if any) has [Responder.didBecomeFirstResponder] invoked.
|
||||||
|
*/
|
||||||
var firstResponder: Responder? = null
|
var firstResponder: Responder? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field?.didResignFirstResponder()
|
field?.didResignFirstResponder()
|
||||||
|
@ -183,6 +195,13 @@ open class Window(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a tooltip containing the given lines at the mouse pointer location.
|
||||||
|
*
|
||||||
|
* Implementation note: the tooltip is not drawn immediately, it is done after the window is done drawing all of its
|
||||||
|
* views. This is done to prevent other views from being drawn in front of the tooltip. Additionally, more than one
|
||||||
|
* tooltip cannot be drawn in a frame as they would appear at the same position.
|
||||||
|
*/
|
||||||
fun drawTooltip(text: List<Text>) {
|
fun drawTooltip(text: List<Text>) {
|
||||||
if (currentDeferredTooltip != null) {
|
if (currentDeferredTooltip != null) {
|
||||||
throw RuntimeException("Deferred tooltip already registered for current frame")
|
throw RuntimeException("Deferred tooltip already registered for current frame")
|
||||||
|
@ -199,6 +218,7 @@ open class Window(
|
||||||
* @return Whether the mouse click was handled by a view.
|
* @return Whether the mouse click was handled by a view.
|
||||||
*/
|
*/
|
||||||
fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
|
// todo: isn't this always true?
|
||||||
if (point in viewController.view.frame) {
|
if (point in viewController.view.frame) {
|
||||||
val mouseInView = Point(point.x - viewController.view.frame.left, point.y - viewController.view.frame.top)
|
val mouseInView = Point(point.x - viewController.view.frame.left, point.y - viewController.view.frame.top)
|
||||||
return viewController.view.mouseClicked(mouseInView, mouseButton)
|
return viewController.view.mouseClicked(mouseInView, mouseButton)
|
||||||
|
|
|
@ -24,78 +24,78 @@ import net.shadowfacts.kiwidsl.dsl
|
||||||
class TestCacaoScreen: CacaoScreen() {
|
class TestCacaoScreen: CacaoScreen() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val viewController = object: ViewController() {
|
|
||||||
override fun loadView() {
|
|
||||||
view = View()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
val stack = view.addSubview(StackView(Axis.VERTICAL, StackView.Distribution.CENTER, spacing = 4.0)).apply {
|
|
||||||
backgroundColor = Color.WHITE
|
|
||||||
}
|
|
||||||
val birch = stack.addArrangedSubview(TextureView(Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16))).apply {
|
|
||||||
intrinsicContentSize = Size(50.0, 50.0)
|
|
||||||
}
|
|
||||||
val ninePatch = stack.addArrangedSubview(NinePatchView(NinePatchTexture.PANEL_BG)).apply {
|
|
||||||
intrinsicContentSize = Size(75.0, 100.0)
|
|
||||||
}
|
|
||||||
val red = stack.addArrangedSubview(View()).apply {
|
|
||||||
intrinsicContentSize = Size(50.0, 50.0)
|
|
||||||
backgroundColor = Color.RED
|
|
||||||
}
|
|
||||||
|
|
||||||
val label = Label(LiteralText("Test"), wrappingMode = Label.WrappingMode.NO_WRAP).apply {
|
|
||||||
// textColor = Color.BLACK
|
|
||||||
}
|
|
||||||
// stack.addArrangedSubview(label)
|
|
||||||
val button = red.addSubview(Button(label))
|
|
||||||
|
|
||||||
val field = TextField("Test") {
|
|
||||||
println("new value: ${it.text}")
|
|
||||||
}
|
|
||||||
stack.addArrangedSubview(field)
|
|
||||||
|
|
||||||
view.solver.dsl {
|
|
||||||
stack.topAnchor equalTo 0
|
|
||||||
stack.centerXAnchor equalTo window!!.centerXAnchor
|
|
||||||
stack.widthAnchor equalTo 100
|
|
||||||
|
|
||||||
|
|
||||||
button.centerXAnchor equalTo red.centerXAnchor
|
|
||||||
button.centerYAnchor equalTo red.centerYAnchor
|
|
||||||
// label.heightAnchor equalTo 9
|
|
||||||
button.heightAnchor equalTo 20
|
|
||||||
|
|
||||||
field.widthAnchor equalTo stack.widthAnchor
|
|
||||||
field.heightAnchor equalTo 20
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addWindow(Window(viewController))
|
|
||||||
|
|
||||||
// val viewController = object: ViewController() {
|
// val viewController = object: ViewController() {
|
||||||
|
// override fun loadView() {
|
||||||
|
// view = View()
|
||||||
|
// }
|
||||||
|
//
|
||||||
// override fun viewDidLoad() {
|
// override fun viewDidLoad() {
|
||||||
// super.viewDidLoad()
|
// super.viewDidLoad()
|
||||||
//
|
//
|
||||||
// val tabs = arrayOf(
|
// val stack = view.addSubview(StackView(Axis.VERTICAL, StackView.Distribution.CENTER, spacing = 4.0)).apply {
|
||||||
// Tab(Label("A"), AViewController()),
|
// backgroundColor = Color.WHITE
|
||||||
// Tab(Label("B"), BViewController()),
|
// }
|
||||||
// )
|
// val birch = stack.addArrangedSubview(TextureView(Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16))).apply {
|
||||||
// val tabVC = TabViewController(tabs)
|
// intrinsicContentSize = Size(50.0, 50.0)
|
||||||
// embedChild(tabVC, pinEdges = false)
|
// }
|
||||||
|
// val ninePatch = stack.addArrangedSubview(NinePatchView(NinePatchTexture.PANEL_BG)).apply {
|
||||||
|
// intrinsicContentSize = Size(75.0, 100.0)
|
||||||
|
// }
|
||||||
|
// val red = stack.addArrangedSubview(View()).apply {
|
||||||
|
// intrinsicContentSize = Size(50.0, 50.0)
|
||||||
|
// backgroundColor = Color.RED
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val label = Label(LiteralText("Test"), wrappingMode = Label.WrappingMode.NO_WRAP).apply {
|
||||||
|
//// textColor = Color.BLACK
|
||||||
|
// }
|
||||||
|
//// stack.addArrangedSubview(label)
|
||||||
|
// val button = red.addSubview(Button(label))
|
||||||
|
//
|
||||||
|
// val field = TextField("Test") {
|
||||||
|
// println("new value: ${it.text}")
|
||||||
|
// }
|
||||||
|
// stack.addArrangedSubview(field)
|
||||||
//
|
//
|
||||||
// view.solver.dsl {
|
// view.solver.dsl {
|
||||||
// tabVC.view.centerXAnchor equalTo view.centerXAnchor
|
// stack.topAnchor equalTo 0
|
||||||
// tabVC.view.centerYAnchor equalTo view.centerYAnchor
|
// stack.centerXAnchor equalTo window!!.centerXAnchor
|
||||||
// tabVC.view.widthAnchor equalTo 200
|
// stack.widthAnchor equalTo 100
|
||||||
// tabVC.view.heightAnchor equalTo 150
|
//
|
||||||
|
//
|
||||||
|
// button.centerXAnchor equalTo red.centerXAnchor
|
||||||
|
// button.centerYAnchor equalTo red.centerYAnchor
|
||||||
|
//// label.heightAnchor equalTo 9
|
||||||
|
// button.heightAnchor equalTo 20
|
||||||
|
//
|
||||||
|
// field.widthAnchor equalTo stack.widthAnchor
|
||||||
|
// field.heightAnchor equalTo 20
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// addWindow(Window(viewController))
|
// addWindow(Window(viewController))
|
||||||
|
|
||||||
|
val viewController = object: ViewController() {
|
||||||
|
override fun viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
val tabs = listOf(
|
||||||
|
Tab(Label("A"), AViewController(), LiteralText("Tab A")),
|
||||||
|
Tab(Label("B"), BViewController(), LiteralText("Tab B")),
|
||||||
|
)
|
||||||
|
val tabVC = TabViewController(tabs)
|
||||||
|
embedChild(tabVC, pinEdges = false)
|
||||||
|
|
||||||
|
view.solver.dsl {
|
||||||
|
tabVC.view.centerXAnchor equalTo view.centerXAnchor
|
||||||
|
tabVC.view.centerYAnchor equalTo view.centerYAnchor
|
||||||
|
tabVC.view.widthAnchor equalTo 200
|
||||||
|
tabVC.view.heightAnchor equalTo 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addWindow(Window(viewController))
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Tab(
|
data class Tab(
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
package net.shadowfacts.phycon.screen.console
|
package net.shadowfacts.phycon.screen.console
|
||||||
|
|
||||||
import net.minecraft.text.Text
|
|
||||||
import net.minecraft.text.TranslatableText
|
import net.minecraft.text.TranslatableText
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.shadowfacts.cacao.CacaoScreen
|
import net.shadowfacts.cacao.CacaoScreen
|
||||||
import net.shadowfacts.cacao.geometry.Rect
|
|
||||||
import net.shadowfacts.cacao.geometry.Size
|
import net.shadowfacts.cacao.geometry.Size
|
||||||
import net.shadowfacts.cacao.util.Color
|
import net.shadowfacts.cacao.util.Color
|
||||||
import net.shadowfacts.cacao.util.texture.Texture
|
import net.shadowfacts.cacao.util.texture.Texture
|
||||||
import net.shadowfacts.cacao.view.Label
|
import net.shadowfacts.cacao.view.Label
|
||||||
import net.shadowfacts.cacao.view.TextureView
|
import net.shadowfacts.cacao.view.TextureView
|
||||||
import net.shadowfacts.cacao.view.View
|
|
||||||
import net.shadowfacts.cacao.viewcontroller.TabViewController
|
import net.shadowfacts.cacao.viewcontroller.TabViewController
|
||||||
import net.shadowfacts.cacao.viewcontroller.ViewController
|
import net.shadowfacts.cacao.viewcontroller.ViewController
|
||||||
import net.shadowfacts.cacao.window.Window
|
import net.shadowfacts.cacao.window.Window
|
||||||
import net.shadowfacts.kiwidsl.dsl
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
|
||||||
import net.shadowfacts.phycon.network.DeviceBlockEntity
|
import net.shadowfacts.phycon.network.DeviceBlockEntity
|
||||||
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
|
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
|
||||||
import net.shadowfacts.phycon.network.component.ActivationController
|
import net.shadowfacts.phycon.network.component.ActivationController
|
||||||
|
@ -28,18 +24,18 @@ class DeviceConsoleScreen(
|
||||||
val device: DeviceBlockEntity,
|
val device: DeviceBlockEntity,
|
||||||
): CacaoScreen(TranslatableText("item.phycon.console")) {
|
): CacaoScreen(TranslatableText("item.phycon.console")) {
|
||||||
|
|
||||||
private val tabController: TabViewController<Tab>
|
private val tabController: TabViewController<TabViewController.SimpleTab>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val tabs = mutableListOf(
|
val tabs = mutableListOf(
|
||||||
Tab(
|
TabViewController.SimpleTab(
|
||||||
Label("IP").apply { textColor = Color.TEXT },
|
Label("IP").apply { textColor = Color.TEXT },
|
||||||
TranslatableText("gui.phycon.console.details"),
|
TranslatableText("gui.phycon.console.details"),
|
||||||
DeviceDetailsViewController(device)
|
DeviceDetailsViewController(device)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (device is ActivationController.ActivatableDevice) {
|
if (device is ActivationController.ActivatableDevice) {
|
||||||
tabs.add(Tab(
|
tabs.add(TabViewController.SimpleTab(
|
||||||
TextureView(Texture(Identifier("textures/item/ender_pearl.png"), 0, 0, 16, 16)).apply {
|
TextureView(Texture(Identifier("textures/item/ender_pearl.png"), 0, 0, 16, 16)).apply {
|
||||||
intrinsicContentSize = Size(16.0, 16.0)
|
intrinsicContentSize = Size(16.0, 16.0)
|
||||||
},
|
},
|
||||||
|
@ -48,7 +44,7 @@ class DeviceConsoleScreen(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
if (device is RedstoneControllerBlockEntity) {
|
if (device is RedstoneControllerBlockEntity) {
|
||||||
tabs.add(Tab(
|
tabs.add(TabViewController.SimpleTab(
|
||||||
TextureView(Texture(Identifier("textures/block/redstone_torch.png"), 0, 0, 16, 16)).apply {
|
TextureView(Texture(Identifier("textures/block/redstone_torch.png"), 0, 0, 16, 16)).apply {
|
||||||
intrinsicContentSize = Size(16.0, 16.0)
|
intrinsicContentSize = Size(16.0, 16.0)
|
||||||
},
|
},
|
||||||
|
@ -85,10 +81,4 @@ class DeviceConsoleScreen(
|
||||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Tab(
|
|
||||||
override val tabView: View,
|
|
||||||
override val tooltip: Text?,
|
|
||||||
override val controller: ViewController,
|
|
||||||
): TabViewController.Tab
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
"package": "net.shadowfacts.phycon.mixin.client",
|
"package": "net.shadowfacts.phycon.mixin.client",
|
||||||
"compatibilityLevel": "JAVA_8",
|
"compatibilityLevel": "JAVA_8",
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"MixinHandledScreen"
|
"MixinHandledScreen",
|
||||||
|
"TextFieldWidgetAccessor"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|
Loading…
Reference in New Issue