Cacao: More docs

This commit is contained in:
Shadowfacts 2021-02-28 12:19:09 -05:00
parent 500ad94442
commit 2c19b8456b
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
16 changed files with 322 additions and 105 deletions

View File

@ -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();
}

View File

@ -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)
} }

View File

@ -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,14 +65,36 @@ 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
} }
} }

View File

@ -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,4 +32,4 @@ class KeyModifiers(val value: Int) {
return (value and mod) == mod return (value and mod) == mod
} }
} }

View File

@ -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)

View File

@ -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.

View File

@ -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
} }
} }

View File

@ -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,

View File

@ -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

View File

@ -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
} }

View File

@ -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()
} }

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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
} }

View File

@ -3,9 +3,10 @@
"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
} }
} }