From 5aeb6e5d9bf3f3f01a713f68914c5a2880c766cf Mon Sep 17 00:00:00 2001 From: sen Date: Fri, 15 Jul 2022 15:03:28 -0700 Subject: [PATCH] Revert "Cleanup and bug fixes" This reverts commit b8103a26d384e4854807d0fdd3f5f31d12b83811. --- .../net/prismclient/aether/ui/Aether.kt | 51 ++-- .../net/prismclient/aether/ui/Timings.kt | 83 ------ .../aether/ui/callback/UICoreCallback.kt | 17 ++ .../aether/ui/component/UIComponent.kt | 80 ++--- .../impl/selection/UISelectableController.kt | 13 - .../aether/ui/component/type/UILabel.kt | 6 +- .../ui/component/type/color/UIColorCursor.kt | 3 + .../ui/component/type/color/UIColorPicker.kt | 10 + .../type/color/UIColorSwatchSheet.kt | 16 + .../aether/ui/component/type/image/UIImage.kt | 44 +-- .../ui/component/type/image/UIImageSheet.kt | 28 ++ .../component/type/input/button/UIButton.kt | 9 +- .../component/type/input/button/UICheckbox.kt | 39 +++ .../type/input/button/UIIconButton.kt | 3 + .../type/input/button/UIImageButton.kt | 23 ++ .../type/input/button/UISelectableButton.kt | 37 +++ .../component/type/input/slider/UISlider.kt | 26 +- .../type/input/slider/UISliderSheet.kt | 28 ++ .../type/input/textfield/UITextField.kt | 56 +--- .../type/input/textfield/UITextFieldSheet.kt | 48 +++ .../ui/component/type/layout/UIContainer.kt | 275 ------------------ .../ui/component/type/layout/UIFrame.kt | 85 +----- .../ui/component/type/layout/UIGridLayout.kt | 9 - .../type/layout/{ => auto}/UIAutoLayout.kt | 40 ++- .../type/layout/auto/UIAutoLayoutSheet.kt | 16 + .../type/layout/container/UIContainer.kt | 173 +++++++++++ .../type/layout/grid/UIGridLayout.kt | 155 ++++++++++ .../type/layout/{ => list}/UIListLayout.kt | 11 +- .../type/layout/styles/UIContainerSheet.kt | 102 +++++++ .../type/layout/styles/UIFrameSheet.kt | 53 ++++ .../component/type/layout/{ => tab}/UITab.kt | 2 +- .../type/other/{ => progress}/UIProgress.kt | 24 +- .../type/other/progress/UIProgressSheet.kt | 21 ++ .../ui/component/util/enums/UIAlignment.kt | 2 +- .../ui/component/util/enums/UIOverflow.kt | 8 + .../prismclient/aether/ui/dsl/UIAssetDSL.kt | 4 +- .../aether/ui/dsl/UIComponentDSL.kt | 105 +++---- .../prismclient/aether/ui/dsl/UIPathDSL.kt | 21 +- .../aether/ui/dsl/UIRendererDSL.kt | 14 +- .../aether/ui/renderer/UIProvider.kt | 7 +- .../aether/ui/renderer/UIRenderer.kt | 13 + .../impl/background/UIGradientBackground.kt | 18 +- .../ui/renderer/impl/scrollbar/UIScrollbar.kt | 25 +- .../aether/ui/style/UIStyleSheet.kt | 21 +- .../aether/ui/style/util/UIAnchorPoint.kt | 3 +- .../prismclient/aether/ui/util/Shorthands.kt | 76 +++-- .../aether/ui/util/UIAetherLogger.kt | 29 +- .../net/prismclient/aether/ui/util/Util.kt | 31 -- .../ui/util/interfaces/UITriConsumer.kt | 23 ++ src/test/kotlin/Renderer.kt | 67 ++++- src/test/kotlin/Runner.kt | 13 +- src/test/kotlin/examples/Animations.kt | 59 +++- src/test/kotlin/examples/AutoLayouts.kt | 110 +++++++ src/test/kotlin/examples/Default.kt | 61 +++- src/test/kotlin/examples/components/Chart.kt | 6 +- src/test/kotlin/examples/deps/Generic.kt | 9 +- 56 files changed, 1365 insertions(+), 946 deletions(-) delete mode 100644 src/main/kotlin/net/prismclient/aether/ui/Timings.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/callback/UICoreCallback.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorCursor.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorPicker.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorSwatchSheet.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImageSheet.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UICheckbox.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIIconButton.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIImageButton.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UISelectableButton.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISliderSheet.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextFieldSheet.kt delete mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIContainer.kt delete mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIGridLayout.kt rename src/main/kotlin/net/prismclient/aether/ui/component/type/layout/{ => auto}/UIAutoLayout.kt (84%) create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayoutSheet.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/layout/container/UIContainer.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/layout/grid/UIGridLayout.kt rename src/main/kotlin/net/prismclient/aether/ui/component/type/layout/{ => list}/UIListLayout.kt (88%) create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIContainerSheet.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIFrameSheet.kt rename src/main/kotlin/net/prismclient/aether/ui/component/type/layout/{ => tab}/UITab.kt (53%) rename src/main/kotlin/net/prismclient/aether/ui/component/type/other/{ => progress}/UIProgress.kt (58%) create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgressSheet.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIOverflow.kt delete mode 100644 src/main/kotlin/net/prismclient/aether/ui/util/Util.kt create mode 100644 src/main/kotlin/net/prismclient/aether/ui/util/interfaces/UITriConsumer.kt create mode 100644 src/test/kotlin/examples/AutoLayouts.kt diff --git a/src/main/kotlin/net/prismclient/aether/ui/Aether.kt b/src/main/kotlin/net/prismclient/aether/ui/Aether.kt index a9a0fba..bc7ee23 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/Aether.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/Aether.kt @@ -4,8 +4,8 @@ import net.prismclient.aether.ui.Aether.Properties import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.component.controller.UIController import net.prismclient.aether.ui.component.type.layout.UIFrame -import net.prismclient.aether.ui.component.type.layout.UIContainer -import net.prismclient.aether.ui.component.type.layout.UIContainerSheet +import net.prismclient.aether.ui.component.type.layout.container.UIContainer +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet import net.prismclient.aether.ui.event.input.UIMouseEvent import net.prismclient.aether.ui.renderer.UIProvider import net.prismclient.aether.ui.renderer.UIRenderer @@ -33,10 +33,9 @@ import java.util.function.Consumer * functions [Properties.updateSize] and [Properties.updateMouse] to update the values without * invoking the [update], and [mouseMoved] functions. * - * [Aether Documentation](https://aether.prismclient.net/getting-started) - * * @author sen * @since 1.0 + * @see UICore documentation * @see UIProvider */ open class Aether(renderer: UIRenderer) { @@ -109,14 +108,12 @@ open class Aether(renderer: UIRenderer) { open fun render() { renderer { if (activeScreen != null) { - timings.onFrameRenderStart() beginFrame(width, height, devicePxRatio) for (i in 0 until components!!.size) { val component = components!![i] if (component.visible) component.render() } endFrame() - timings.onFrameRenderEnd() } } } @@ -126,7 +123,7 @@ open class Aether(renderer: UIRenderer) { * eligibility to be focused or bubbled. The [Properties.mouseX] and [Properties.mouseY] * properties can be found in [Aether.Properties]. */ - open fun mouseMoved(mouseX: Float, mouseY: Float) { + fun mouseMoved(mouseX: Float, mouseY: Float) { updateMouse(mouseX, mouseY) mouseMoveListeners?.forEach { it.value.run() } if (activeScreen != null) for (i in 0 until components!!.size) components!![i].mouseMoved(mouseX, mouseY) @@ -142,10 +139,10 @@ open class Aether(renderer: UIRenderer) { * * @see mouseScrolled */ - open fun mouseChanged(mouseButton: Int, isRelease: Boolean) { + fun mouseChanged(mouseButton: Int, isRelease: Boolean) { if (isRelease) { mouseReleasedListeners?.forEach { it.value.run() } - components?.forEach { it.mouseReleased(it.getMouseX(), it.getMouseY()) } + components?.forEach { it.mouseReleased(mouseX, mouseY) } return } @@ -185,7 +182,7 @@ open class Aether(renderer: UIRenderer) { return if (component != null) { component.focus() - component.mousePressed(UIMouseEvent(component.getMouseX(), component.getMouseY(), mouseButton, clickCount)) + component.mousePressed(UIMouseEvent(mouseX, mouseY, mouseButton, clickCount)) true } else false } @@ -214,7 +211,7 @@ open class Aether(renderer: UIRenderer) { i++ } c?.focus() - c?.mousePressed(UIMouseEvent(c.getMouseX(), c.getMouseY(), mouseButton, clickCount)) + c?.mousePressed(UIMouseEvent(mouseX, mouseY, mouseButton, clickCount)) } /** @@ -223,7 +220,7 @@ open class Aether(renderer: UIRenderer) { * @param character The key which was pressed or '\u0000' * @see updateModifierKey To update keys such as Shift, Alt, Tab etc... */ - open fun keyPressed(character: Char) { + fun keyPressed(character: Char) { keyPressListeners?.forEach { it.value.accept(character) } (focusedComponent as? UIComponent<*>)?.keyPressed(character) } @@ -233,7 +230,6 @@ open class Aether(renderer: UIRenderer) { * of their eligibility to be focused or bubbled. */ open fun mouseScrolled(scrollAmount: Float) { - if (scrollAmount == 0f) return tryFocus() mouseScrollListeners?.forEach { it.value.accept(scrollAmount) } components?.forEach { it.mouseScrolled(mouseX, mouseY, scrollAmount) } @@ -245,20 +241,20 @@ open class Aether(renderer: UIRenderer) { * as de-focusing the focused component and adding listeners to input. */ companion object Properties { - - val timings: Timings = Timings() - @JvmStatic var debug: Boolean = true @JvmStatic lateinit var instance: Aether + protected set @JvmStatic lateinit var renderer: UIRenderer + protected set @JvmStatic var activeScreen: UIScreen? = null + protected set /** * The focused component (if applicable). @@ -269,36 +265,42 @@ open class Aether(renderer: UIRenderer) { */ @JvmStatic var focusedComponent: UIFocusable? = null + protected set /** * The width of the screen. It can be set via [update] */ @JvmStatic var width: Float = 0f + protected set /** * The width of the screen. It can be set via [update] */ @JvmStatic var height: Float = 0f + protected set /** * The device pixel ratio. It can be set via [update]. It is the equivalent of content scale. */ @JvmStatic var devicePxRatio: Float = 1f + protected set /** * The x position of the mouse relative to the screen */ @JvmStatic var mouseX: Float = 0f + protected set /** * The y position of the mouse relative to the screen */ @JvmStatic var mouseY: Float = 0f + protected set /** * Invoked whenever the layout needs to be updated. This can be when the screen @@ -306,42 +308,49 @@ open class Aether(renderer: UIRenderer) { */ @JvmStatic var updateListeners: HashMap? = null + protected set /** * The listeners for then the mouse is moved. Invoked prior to components. */ @JvmStatic var mouseMoveListeners: HashMap? = null + protected set /** * Invoked when the mouse is pressed. Invoked prior to components. */ @JvmStatic var mousePressedListeners: HashMap? = null + protected set /** * Invoked when the mouse is released. Invoked prior to components. */ @JvmStatic var mouseReleasedListeners: HashMap? = null + protected set /** * Invoked when a key is pressed. Invoked prior to components. */ @JvmStatic var keyPressListeners: HashMap>? = null + protected set /** * Invoked when the mouse is scrolled. Invoked prior to components. */ @JvmStatic var mouseScrollListeners: HashMap>? = null + protected set /** * Invoked when the screen is deleted. This is used to deallocate listeners added to UICore. */ @JvmStatic var deallocationListeners: HashMap? = null + protected set /** * The list of modifier keys. The value is if the key is pressed @@ -493,7 +502,13 @@ open class Aether(renderer: UIRenderer) { * Focuses the component. Please use [UIComponent.focus] instead. */ @JvmStatic - fun focus(component: T) where T : UIComponent<*>, T : UIFocusable { + fun focus(component: UIFocusable) { + // Check if the given value is a valid instance of UIComponent + try { + component as UIComponent<*> + } catch (castException: ClassCastException) { + throw RuntimeException("When trying to focus, the provided value is not an instance of UIComponent. Make sure you are only using the UIFocus interface to focus UIComponents.") + } focusedComponent = component } @@ -533,7 +548,7 @@ open class Aether(renderer: UIRenderer) { for (i in 0 until instance.frames!!.size) { // UIContainers are what control scrolling, so // if it is not an instance of it, skip and continue - val container = instance.frames!![i] as? UIContainer ?: continue + val container = instance.frames!![i] as? UIContainer<*> ?: continue if (container.isMouseInsideBounds() && container.expandedHeight > 0f && container.style.overflowY != UIContainerSheet.Overflow.None) { // Iterate through the frame to see if there are more // containers with it. If there are, it will pass true diff --git a/src/main/kotlin/net/prismclient/aether/ui/Timings.kt b/src/main/kotlin/net/prismclient/aether/ui/Timings.kt deleted file mode 100644 index 9218a08..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/Timings.kt +++ /dev/null @@ -1,83 +0,0 @@ -package net.prismclient.aether.ui - -class Timings { - - /** - * Time when the current frame render started in milliseconds - */ - var frameRenderStartTime = 0L - private set - - /** - * Time when the last frame render started in milliseconds - */ - var lastFrameRenderStartTime = 0L - private set - - /** - * Time when the current frame render ended in milliseconds - */ - var frameRenderEndTime = 0L - private set - - /** - * Time when the last frame render ended in milliseconds - */ - var lastFrameRenderEndTime = 0L - private set - - /** - * The delta time (frame render start - frame render end) of the current frame in milliseconds - */ - val frameRenderDeltaTime - get() = frameRenderEndTime - frameRenderStartTime - - /** - * The delta time (frame render start - frame render end) of the current frame in seconds - */ - val deltaFrameRenderTimeSeconds - get() = frameRenderDeltaTime / 1000.0 - - /** - * The delta time (frame render start - frame render end) of the last frame in milliseconds - */ - val lastFrameRenderDeltaTime - get() = lastFrameRenderEndTime - lastFrameRenderStartTime - - /** - * The delta time (frame render start - frame render end) of the last frame in seconds - */ - val lastFrameRenderDeltaTimeSeconds - get() = lastFrameRenderDeltaTime / 1000.0 - - /** - * The approximate amount of renders the last frame would have made in a second - */ - val lastFrameRate - get() = 1000.0 / lastFrameRenderDeltaTime - - /** - * The approximate amount of renders the current frame would have made in a second - */ - val frameRate - get() = (1000 / (frameRenderDeltaTime + 1)).toInt() - - /** - * Set current & last render start times - */ - fun onFrameRenderStart() { - lastFrameRenderStartTime = frameRenderStartTime - - frameRenderStartTime = System.currentTimeMillis() - } - - /** - * Set current & last render end times - */ - fun onFrameRenderEnd() { - lastFrameRenderEndTime = frameRenderEndTime - - frameRenderEndTime = System.currentTimeMillis() - } - -} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/callback/UICoreCallback.kt b/src/main/kotlin/net/prismclient/aether/ui/callback/UICoreCallback.kt new file mode 100644 index 0000000..0751f94 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/callback/UICoreCallback.kt @@ -0,0 +1,17 @@ +package net.prismclient.aether.ui.callback + +/** + * [UICoreCallback] is an interface for getting data that + * is not available to aether by default. + * + * @author sen + * @since 5/13/2022 + */ +interface UICoreCallback { + /** + * Returns the color of the pixel at the given position + * + * @return RGB(A) formatted int + */ + fun getPixelColor(x: Float, y: Float): Int +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/UIComponent.kt b/src/main/kotlin/net/prismclient/aether/ui/component/UIComponent.kt index c6b0a79..c721c96 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/UIComponent.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/UIComponent.kt @@ -2,10 +2,10 @@ package net.prismclient.aether.ui.component import net.prismclient.aether.ui.Aether import net.prismclient.aether.ui.animation.UIAnimation -import net.prismclient.aether.ui.component.type.layout.UIContainer -import net.prismclient.aether.ui.component.type.layout.UIContainerSheet import net.prismclient.aether.ui.component.type.layout.UIFrame -import net.prismclient.aether.ui.component.type.layout.UIFrameSheet +import net.prismclient.aether.ui.component.type.layout.container.UIContainer +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet +import net.prismclient.aether.ui.component.type.layout.styles.UIFrameSheet import net.prismclient.aether.ui.event.input.UIMouseEvent import net.prismclient.aether.ui.renderer.UIProvider import net.prismclient.aether.ui.renderer.impl.background.UIBackground @@ -37,7 +37,7 @@ import java.util.function.Consumer * @since 1.0 */ @Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "LeakingThis") -abstract class UIComponent { +abstract class UIComponent(style: String?) { /** * The style of the component. */ @@ -193,6 +193,10 @@ abstract class UIComponent { var mouseScrollListeners: HashMap, Float>>? = null protected set + init { + applyStyle(style) + } + /** * Attempts to apply the style to the component. If the style is * empty, or null, a NullPointerException will be thrown when the @@ -204,7 +208,8 @@ abstract class UIComponent { * @throws InvalidStyleSheetException If the style is not a valid style sheet of the given component */ open fun applyStyle(style: String?) { - if (style.isNullOrEmpty()) return + if (style.isNullOrEmpty()) + return // Attempt to apply the style provided to the component. // Throw a InvalidStyleException if the style is not valid. @@ -241,10 +246,8 @@ abstract class UIComponent { * might request for this method to be invoked. */ open fun update() { - if (!this::style.isInitialized) { - println("creating style...") - style = createsStyle() - } + if (!this::style.isInitialized) + throw UninitializedStyleSheetException(this) calculateBounds() // Update the size, then the anchor, and then the position @@ -333,7 +336,8 @@ abstract class UIComponent { if (animations != null) { animations!!.forEach { it.value.update() } animations!!.entries.removeIf { it.value.isCompleted } - if (animations!!.isEmpty()) animations = null + if (animations!!.isEmpty()) + animations = null } } @@ -361,17 +365,7 @@ abstract class UIComponent { */ abstract fun renderComponent() - /** - * Used to create a new instance of the style sheet provided, [T]. - */ - abstract fun createsStyle(): T - - /** - * Returns true if [style] is intialized. - */ - open fun hasStyle(): Boolean = this::style.isInitialized - - // -- Input -- // + /** Input **/ /** * Invoked when the mouse moves @@ -437,7 +431,7 @@ abstract class UIComponent { mouseScrollListeners?.forEach { it.value.accept(this, scrollAmount) } } - // -- Event -- // + /** Event **/ /** * Invoked once on the initialization of the component. @@ -607,28 +601,36 @@ abstract class UIComponent { open fun getMouseY(): Float = Aether.mouseY - getParentYOffset() /** - * Returns the actual x position of this component rendered on screen. FBOs change the point of origin - * back to 0, so the values that the component has might not reflect it's actual position on screen. + * Returns the offset of the parent on the x-axis. It incorporates for [UIFrame] and [UIContainer] scroll offsets. */ - open fun getParentXOffset(): Float = if (parent is UIFrame) { - ((if ((parent!!.style as UIFrameSheet).useFBO) { - parent!!.relX - } else 0f) + parent!!.getParentXOffset()) - if (parent is UIContainer) { - (parent!!.style as UIContainerSheet).horizontalScrollbar.value * (parent as UIContainer).expandedWidth + open fun getParentXOffset(): Float { + if (parent == null) return 0f + + return if (parent is UIFrame) { + val clipContent = ((parent as UIFrame).style as UIFrameSheet).clipContent + return (if (clipContent) { + parent!!.relX + } else 0f) + parent!!.getParentXOffset() - if (parent is UIContainer) { + (parent!!.style as UIContainerSheet).horizontalScrollbar.value * (parent as UIContainer).expandedWidth + } else 0f } else 0f - } else 0f + } /** - * Returns the actual y position of this component rendered on screen. FBOs change the point of origin - * back to 0, so the values that the component has might not reflect it's actual position on screen. + * Returns the offset of the parent on the y-axis. It incorporates for [UIFrame] and [UIContainer] scrolling offsets */ - open fun getParentYOffset(): Float = if (parent is UIFrame) { - ((if ((parent!!.style as UIFrameSheet).useFBO) { - parent!!.relY - } else 0f) + parent!!.getParentYOffset()) - if (parent is UIContainer) { - (parent!!.style as UIContainerSheet).verticalScrollbar.value * (parent as UIContainer).expandedHeight + open fun getParentYOffset(): Float { + if (parent == null) return 0f + + return if (parent is UIFrame) { + val clipContent = ((parent as UIFrame).style as UIFrameSheet).clipContent + return (if (clipContent) { + parent!!.relY + } else 0f) + parent!!.getParentYOffset() - if (parent is UIContainer) { + (parent!!.style as UIContainerSheet).verticalScrollbar.value * (parent as UIContainer).expandedHeight + } else 0f } else 0f - } else 0f + } /** * Shorthand for calculating the x or width of this component @@ -656,7 +658,7 @@ abstract class UIComponent { open fun focus() { if (this is UIFocusable) { Aether.focus(this) - focusListeners?.forEach { it.value.accept(this, true) } + focusListeners?.forEach { it.value.accept(this as UIComponent, true) } requestUpdate() } } diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/controller/impl/selection/UISelectableController.kt b/src/main/kotlin/net/prismclient/aether/ui/component/controller/impl/selection/UISelectableController.kt index b0dd7de..0d224f0 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/controller/impl/selection/UISelectableController.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/controller/impl/selection/UISelectableController.kt @@ -2,7 +2,6 @@ package net.prismclient.aether.ui.component.controller.impl.selection import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.component.controller.UIController -import net.prismclient.aether.ui.style.UIStyleSheet import java.util.function.Consumer import kotlin.reflect.KClass @@ -58,20 +57,8 @@ class UISelectableController>(filter: KClass) : UIControll } it.update() } - - selectedComponent = component } - /** - * Returns true if the selected component is at index [index] - */ - fun isSelected(index: Int) = selectedComponent == components[index] - - /** - * Returns true if the selected component is [component] - */ - fun > isSelected(component: O) = selectedComponent == component - /** * The action that is preformed when a component is selected */ diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/UILabel.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/UILabel.kt index fac02bd..dfa4489 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/UILabel.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/UILabel.kt @@ -7,12 +7,10 @@ import net.prismclient.aether.ui.style.UIStyleSheet * [UILabel] is a component which draws a label, or string on screen. * * @author sen - * @since 1.0 + * @since 5/15/2022 */ -class UILabel(var text: String) : UIComponent() { +class UILabel(var text: String, style: String?) : UIComponent(style) { override fun renderComponent() { style.font?.render(text) } - - override fun createsStyle(): UIStyleSheet = UIStyleSheet() } \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorCursor.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorCursor.kt new file mode 100644 index 0000000..699c807 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorCursor.kt @@ -0,0 +1,3 @@ +package net.prismclient.aether.ui.component.type.color + +class UIColorCursor \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorPicker.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorPicker.kt new file mode 100644 index 0000000..5c056bb --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorPicker.kt @@ -0,0 +1,10 @@ +package net.prismclient.aether.ui.component.type.color + +import net.prismclient.aether.ui.component.UIComponent +import net.prismclient.aether.ui.style.UIStyleSheet + +class UIColorPicker(style: String?) : UIComponent(style) { + override fun renderComponent() { + + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorSwatchSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorSwatchSheet.kt new file mode 100644 index 0000000..7b82376 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorSwatchSheet.kt @@ -0,0 +1,16 @@ +package net.prismclient.aether.ui.component.type.color + +import net.prismclient.aether.ui.style.UIStyleSheet +import net.prismclient.aether.ui.util.extensions.setAlpha + +class UIColorSwatchSheet(name: String) : UIStyleSheet(name) { + var swatchColor = -1 + set(value) { + field = value.setAlpha(255) + } + + override fun copy(): UIColorSwatchSheet = UIColorSwatchSheet(name).also { + it.apply(this) + it.swatchColor = swatchColor + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImage.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImage.kt index 3421eda..85131de 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImage.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImage.kt @@ -4,21 +4,16 @@ import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.dsl.UIAssetDSL import net.prismclient.aether.ui.renderer.UIProvider import net.prismclient.aether.ui.renderer.image.UIImageData -import net.prismclient.aether.ui.renderer.impl.property.UIRadius -import net.prismclient.aether.ui.style.UIStyleSheet -import net.prismclient.aether.ui.util.UIColor import net.prismclient.aether.ui.util.extensions.renderer -import net.prismclient.aether.ui.util.name /** - * [UIImage] is the default component for rendering images to a screen. It accepts - * the [name] of the image that is to be rendered onto the screen. Alternatively, the - * image can also be loaded with the alternative constructor from a resource file. + * [UIImage] is the default implementation for rendering images on screen. It accepts` + * an image * * @author sen - * @since 1.0 + * @since 5/20/2022 */ -class UIImage(name: String) : UIComponent() { +class UIImage(name: String, style: String?) : UIComponent(style) { var image: String = name set(value) { field = value @@ -29,9 +24,12 @@ class UIImage(name: String) : UIComponent() { /** * Loads am image or svg from the specified location with a given name */ - constructor(name: String, location: String) : this( - name - ) { UIAssetDSL.image(name, location) } + constructor(name: String, location: String, style: String?) : this( + name, + style + ) { + UIAssetDSL.image(name, location) + } init { activeImage = UIProvider.getImage(image) @@ -39,7 +37,7 @@ class UIImage(name: String) : UIComponent() { override fun renderComponent() { renderer { - color(style.imageColor?.rgba ?: -1) + color(style.imageColor) renderImage( image, x, y, width, height, style.imageRadius?.topLeft ?: 0f, @@ -49,24 +47,4 @@ class UIImage(name: String) : UIComponent() { ) } } - - override fun createsStyle(): UIImageSheet = UIImageSheet() -} - -class UIImageSheet : UIStyleSheet() { - /** - * The color of the image. The default value is RGBA(255, 255, 255) - */ - var imageColor: UIColor? = null - - /** - * The radius of the image. - */ - var imageRadius: UIRadius? = null - - override fun copy() = UIImageSheet().name(name).also { - it.apply(this) - it.imageColor = imageColor - it.imageRadius = imageRadius?.copy() - } } \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImageSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImageSheet.kt new file mode 100644 index 0000000..e63bada --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImageSheet.kt @@ -0,0 +1,28 @@ +package net.prismclient.aether.ui.component.type.image + +import net.prismclient.aether.ui.renderer.impl.property.UIRadius +import net.prismclient.aether.ui.style.UIStyleSheet + +/** + * [UIImageSheet] is the sheet implementation for [UIImage]. + * + * @author sen + * @since 5/25/2022 + */ +class UIImageSheet(name: String) : UIStyleSheet(name) { + /** + * The color of the image. Use -1 (WHITE) for the normal color. + */ + var imageColor = -1 + + /** + * The radius of the image. + */ + var imageRadius: UIRadius? = null + + override fun copy() = UIImageSheet(name).also { + it.apply(this) + it.imageColor = imageColor + it.imageRadius = imageRadius?.copy() + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIButton.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIButton.kt index 0c0082a..0d522f9 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIButton.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIButton.kt @@ -4,15 +4,14 @@ import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.style.UIStyleSheet /** - * [UIButton] is the default implementation of [UIComponent]. It renders the given text to the font. + * [UIButton] is a simple class, which is used to create a button. * * @author sen - * @since 1.0 + * @since 5/16/2022 + * @param T The stylesheet (used for inheritance) leave as UIStyleSheet. */ -open class UIButton(open var text: String) : UIComponent() { +open class UIButton(open var text: String, style: String?) : UIComponent(style) { override fun renderComponent() { style.font?.render(text) } - - override fun createsStyle(): UIStyleSheet = UIStyleSheet() } \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UICheckbox.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UICheckbox.kt new file mode 100644 index 0000000..61193d6 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UICheckbox.kt @@ -0,0 +1,39 @@ +package net.prismclient.aether.ui.component.type.input.button + +import net.prismclient.aether.ui.component.type.image.UIImage +import net.prismclient.aether.ui.dsl.UIComponentDSL +import net.prismclient.aether.ui.style.UIStyleSheet + +open class UICheckbox( + checked: Boolean = false, + var selectedImageName: String = "checkbox", + var deselectedImageName: String = "", + var imageStyle: String, + style: String? +) : UISelectableButton(checked, "", style) { + lateinit var selectedImage: UIImage + lateinit var deselectedImage: UIImage + + init { + onCheckChange { _, isSelected -> + if (isSelected) { + selectedImage.visible = true + deselectedImage.visible = false + } else { + selectedImage.visible = false + deselectedImage.visible = true + } + } + } + + override fun initialize() { + selectedImage = UIImage(selectedImageName, imageStyle) + deselectedImage = UIImage(deselectedImageName, imageStyle) + if (deselectedImageName.isEmpty()) // Make the deselected image invisible + deselectedImage.style.imageColor = 0 + UIComponentDSL.pushComponent(selectedImage) + UIComponentDSL.pushComponent(deselectedImage) + } + + override fun renderComponent() {} +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIIconButton.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIIconButton.kt new file mode 100644 index 0000000..c09b6b0 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIIconButton.kt @@ -0,0 +1,3 @@ +package net.prismclient.aether.ui.component.type.input.button + +class UIIconButton \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIImageButton.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIImageButton.kt new file mode 100644 index 0000000..a8f64f4 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIImageButton.kt @@ -0,0 +1,23 @@ +package net.prismclient.aether.ui.component.type.input.button + +import net.prismclient.aether.ui.component.type.image.UIImage +import net.prismclient.aether.ui.style.UIStyleSheet +import net.prismclient.aether.ui.util.ucreate + +/** + * Like [UIButton], but with an image, or icon. + * + * @author sen + * @since 5/9/2022 + */ +class UIImageButton(private val imageName: String, private val imageStyle: String, text: String, style: String?) : + UIButton(text, style) { + lateinit var image: UIImage + + override fun initialize() { + ucreate { + image = image(imageName, style = imageStyle) + } + super.initialize() + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UISelectableButton.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UISelectableButton.kt new file mode 100644 index 0000000..00d91cc --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UISelectableButton.kt @@ -0,0 +1,37 @@ +package net.prismclient.aether.ui.component.type.input.button + +import net.prismclient.aether.ui.style.UIStyleSheet +import java.util.function.BiConsumer + +/** + * [UISelectableButton] is like a [UIButton], except with the ability + * to be selected. To do an action when the button selected, use [onCheckChange] + * which passes this component, and a boolean indicating whether the component + * is selected or not. + * + * @author sen + * @since 5/24/2022 + */ +open class UISelectableButton(checked: Boolean = false, text: String, style: String?) : + UIButton(text, style) { + var checked = checked + set(value) { + field = value + checkListeners?.forEach { it.accept(this, checked) } + } + var checkListeners: MutableList, Boolean>>? = null + + init { + onMousePressed { + if (isMouseInside()) { + this.checked = !this.checked + } + } + } + + open fun onCheckChange(block: BiConsumer, Boolean>): UISelectableButton { + checkListeners = checkListeners ?: mutableListOf() + checkListeners!!.add(block) + return this + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISlider.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISlider.kt index 40dfe84..b1737bf 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISlider.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISlider.kt @@ -2,9 +2,6 @@ package net.prismclient.aether.ui.component.type.input.slider import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.event.input.UIMouseEvent -import net.prismclient.aether.ui.style.UIStyleSheet -import net.prismclient.aether.ui.util.Block -import net.prismclient.aether.ui.util.name import java.util.function.BiConsumer import kotlin.math.roundToInt @@ -20,15 +17,16 @@ import kotlin.math.roundToInt * @see UISliderShape */ open class UISlider( - value: Double, var range: ClosedFloatingPointRange, var step: Double -) : UIComponent() { + value: Double, var range: ClosedFloatingPointRange, var step: Double, style: String? +) : UIComponent(style) { /** * The value of this slider. */ var value: Double = 0.0 set(value) { val different = value != field - field = ((value / step.coerceAtLeast(Double.MIN_VALUE)).roundToInt() * step).coerceAtLeast(range.start) + field = ((value / step.coerceAtLeast(Double.MIN_VALUE)).roundToInt() * step) + .coerceAtLeast(range.start) .coerceAtMost(range.endInclusive) // normalizedValue = value / (range.endInclusive - range.start) if (different) valueChangeListeners?.forEach { it.value.accept(this, value) } @@ -112,20 +110,4 @@ open class UISlider( valueChangeListeners = valueChangeListeners ?: hashMapOf() valueChangeListeners!![eventName] = event } - - override fun createsStyle(): UISliderSheet = UISliderSheet() -} - -class UISliderSheet : UIStyleSheet() { - /** - * The slider shape. The [UISliderShape.x] dictates the offset of the slider. - */ - var control: UISliderShape = UISliderShape() - - inline fun control(block: Block) = control.block() - - override fun copy(): UISliderSheet = UISliderSheet().name(name).also { - it.apply(this) - it.control = control.copy() - } } \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISliderSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISliderSheet.kt new file mode 100644 index 0000000..a3efd8e --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISliderSheet.kt @@ -0,0 +1,28 @@ +package net.prismclient.aether.ui.component.type.input.slider + +import net.prismclient.aether.ui.style.UIStyleSheet +import net.prismclient.aether.ui.util.Block + +/** + * [UISliderSheet] is the corresponding sheet to [UISlider]. It contains the slider shape + * which is the shape which is moved to control the value of the slider + * + * @author sen + * @since 1.0 + * @see UISlider + * @see UISliderShape + * @see UISliderSheet.control The slider shape. + */ +class UISliderSheet @JvmOverloads constructor(name: String = "") : UIStyleSheet(name) { + /** + * The slider shape. The [UISliderShape.x] dictates the offset of the slider. + */ + var control: UISliderShape = UISliderShape() + + inline fun control(block: Block) = control.block() + + override fun copy(): UISliderSheet = UISliderSheet(name).also { + it.apply(this) + it.control = control.copy() + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextField.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextField.kt index 020a740..1f85f76 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextField.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextField.kt @@ -1,19 +1,12 @@ package net.prismclient.aether.ui.component.type.input.textfield import net.prismclient.aether.ui.Aether -import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.component.type.input.button.UIButton -import net.prismclient.aether.ui.component.type.input.textfield.caret.UICaret import net.prismclient.aether.ui.event.input.UIMouseEvent import net.prismclient.aether.ui.renderer.impl.font.UIFont -import net.prismclient.aether.ui.style.UIStyleSheet -import net.prismclient.aether.ui.util.UIColor -import net.prismclient.aether.ui.util.extensions.em -import net.prismclient.aether.ui.util.extensions.px import net.prismclient.aether.ui.util.extensions.renderer import net.prismclient.aether.ui.util.input.UIModifierKey import net.prismclient.aether.ui.util.interfaces.UIFocusable -import net.prismclient.aether.ui.util.name import net.prismclient.aether.ui.util.warn import java.lang.Integer.max import java.lang.Integer.min @@ -27,11 +20,12 @@ import java.util.function.Consumer * length of the overall text. * * @author sen - * @since 1.0 - * @see UITextField.Filters pre-made text filters. + * @since 6/6/2022 + * @see UITextField.filter Pre-made text filters. */ -open class UITextField(text: String, var placeholder: String? = null, var filter: TextFilter) : UIComponent(), UIFocusable { - var text: String = text +open class UITextField(text: String, var placeholder: String? = null, var filter: TextFilter, style: String?) : + UIButton(text, style), UIFocusable { + override var text: String = text set(value) { field = value textChangedListener?.forEach { it.value.accept(this) } @@ -52,7 +46,7 @@ open class UITextField(text: String, var placeholder: String? = null, var filter /** Blink **/ protected var timeSinceLastBlink: Long = 0L - protected var blink: Boolean = true + protected var blink: Boolean = false init { Aether.onModifierKeyChange(this.toString()) { key, value -> @@ -117,7 +111,7 @@ open class UITextField(text: String, var placeholder: String? = null, var filter //style.caret.offsetY = font.cachedY + boundsOf(style.font!!.cachedText)[1] - y if (blink && isFocused()) style.caret.render() - if (style.blinkRate > 0 && (timeSinceLastBlink + style.blinkRate <= System.currentTimeMillis())) { + if (timeSinceLastBlink + style.blinkRate <= System.currentTimeMillis()) { blink = !blink timeSinceLastBlink = System.currentTimeMillis() } @@ -237,8 +231,6 @@ open class UITextField(text: String, var placeholder: String? = null, var filter textChangedListener!![eventName] = event } - override fun createsStyle(): UITextFieldSheet = UITextFieldSheet() - /** * [TextFilter] holds a string which is compared to the input character. If the character * is found within the string, it will be added to the text field, else it will not. Furthermore, @@ -267,38 +259,4 @@ open class UITextField(text: String, var placeholder: String? = null, var filter @JvmStatic val hex = TextFilter("#ABCDEFabcdef0123456789") } -} - -class UITextFieldSheet : UIStyleSheet() { - /** - * The caret shape which is drawn to display the caret. - */ - var caret: UICaret = UICaret().apply { - this.width = px(2) - this.height = em(1) - } - - /** - * The rate at which the caret blinks at. 0 = never - */ - var blinkRate: Long = 500L - - /** - * The color of the text when the text field is not focused - */ - var placeholderColor: UIColor? = null - - /** - * Creates a caret DSL block. - */ - inline fun caret(block: UICaret.() -> Unit) { - block.invoke(caret) - } - - override fun copy(): UITextFieldSheet = UITextFieldSheet().name(name).also { - it.apply(this) - it.caret = caret.copy() - it.blinkRate = blinkRate - it.placeholderColor = placeholderColor - } } \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextFieldSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextFieldSheet.kt new file mode 100644 index 0000000..0ae3407 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextFieldSheet.kt @@ -0,0 +1,48 @@ +package net.prismclient.aether.ui.component.type.input.textfield + +import net.prismclient.aether.ui.component.type.input.textfield.caret.UICaret +import net.prismclient.aether.ui.style.UIStyleSheet +import net.prismclient.aether.ui.util.UIColor +import net.prismclient.aether.ui.util.extensions.em +import net.prismclient.aether.ui.util.extensions.px + +/** + * The corresponding style sheet for text fields. It contains basic styling information + * such as the placeholder color. It also contains caret controls. + * + * @author sen + * @since 5/11/2022 + */ +class UITextFieldSheet(name: String) : UIStyleSheet(name) { + /** + * The caret shape which is drawn to display the caret. + */ + var caret: UICaret = UICaret().apply { + this.width = px(2) + this.height = em(1) + } + + /** + * The rate at which the caret blinks at. 0 = never + */ + var blinkRate: Long = 500L + + /** + * The color of the text when the text field is not focused + */ + var placeholderColor: UIColor? = null + + /** + * Creates a caret DSL block. + */ + inline fun caret(block: UICaret.() -> Unit) { + block.invoke(caret) + } + + override fun copy(): UITextFieldSheet = UITextFieldSheet(name).also { + it.apply(this) + it.caret = caret.copy() + it.blinkRate = blinkRate + it.placeholderColor = placeholderColor + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIContainer.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIContainer.kt deleted file mode 100644 index 5459dd0..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIContainer.kt +++ /dev/null @@ -1,275 +0,0 @@ -package net.prismclient.aether.ui.component.type.layout - -import net.prismclient.aether.ui.component.UIComponent -import net.prismclient.aether.ui.component.util.interfaces.UILayout -import net.prismclient.aether.ui.event.input.UIMouseEvent -import net.prismclient.aether.ui.renderer.impl.scrollbar.UIScrollbar -import net.prismclient.aether.ui.style.UIStyleSheet -import net.prismclient.aether.ui.util.extensions.renderer -import net.prismclient.aether.ui.util.interfaces.UIFocusable - -/** - * [UIContainer] is the default implementation for [UIFrame]. It introduces scrollbars which automatically - * resize to content being added/removed. It is considered to be a [UIFocusable], so when the mouse is scrolled - * within the container the focused component will become this. - * - * @author sen - * @since 1.0 - */ -open class UIContainer : UIFrame(), UIFocusable, UILayout { - /** - * How sensitive the scrolling will be - */ - var scrollSensitivity: Float = 10f - - /** - * The expanded width determined by components that leave the component bounds - */ - var expandedWidth = 0f - protected set - - /** - * The expanded height determined by components that leave the components bounds - */ - var expandedHeight = 0f - protected set - - override fun update() { - super.update() - updateLayout() - - // Calculate the distance of the components - // and find the largest of them on both axes - var w = 0f - var h = 0f - - for (i in 0 until components.size) { - val c = components[i] - w = (c.relX + c.relWidth + c.marginRight).coerceAtLeast(w) - h = (c.relY + c.relHeight + c.marginBottom).coerceAtLeast(h) - } - - val x = if (style.useFBO) 0f else relX - val y = if (style.useFBO) 0f else relY - - expandedWidth = (w - relWidth - x).coerceAtLeast(0f) - expandedHeight = (h - relHeight - y).coerceAtLeast(0f) - - updateScrollbar() - } - - override fun updateLayout() { - components.forEach { it.update() } - } - - open fun updateScrollbar() { - style.verticalScrollbar.update(this) - style.horizontalScrollbar.update(this) - } - - open fun renderScrollbar() { - style.verticalScrollbar.render() - style.horizontalScrollbar.render() - } - - override fun renderContent() { - if (style.useFBO && (requiresUpdate || !style.optimizeRenderer)) { - renderer { - if (fbo == null) updateFBO() - fbo!!.renderToFramebuffer { - translate( - -(style.horizontalScrollbar.value * expandedWidth), - -(style.verticalScrollbar.value * expandedHeight) - ) { - components.forEach(UIComponent<*>::render) - } - } - } - requiresUpdate = false - } - } - - override fun renderComponent() { - if (style.useFBO) { - renderer { - path { - imagePattern(fbo!!.imagePattern, relX, relY, relWidth, relHeight, 0f, 1f) - rect(relX, relY, relWidth, relHeight) - }.fillPaint() - } - } else { - renderer { - if (style.clipContent) { - scissor(relX, relY, relWidth, relHeight) { - translate( - -(style.horizontalScrollbar.value * expandedWidth), - -(style.verticalScrollbar.value * expandedHeight) - ) { - components.forEach(UIComponent<*>::render) - } - } - } else { - translate( - -(style.horizontalScrollbar.value * expandedWidth), - -(style.verticalScrollbar.value * expandedHeight) - ) { - components.forEach(UIComponent<*>::render) - } - } - } - } - } - - override fun render() { - super.render() - renderScrollbar() - } - - override fun updateAnimation() { - if (animations != null) { - animations!!.forEach { it.value.update() } - animations!!.entries.removeIf { it.value.isCompleted } - if (animations!!.isEmpty()) - animations = null - updateLayout() - components.forEach { it.requestUpdate() } - } - } - - override fun mousePressed(event: UIMouseEvent) { - super.mousePressed(event) - if (style.verticalScrollbar.mousePressed() || style.horizontalScrollbar.mousePressed()) focus() - } - - override fun mouseReleased(mouseX: Float, mouseY: Float) { - super.mouseReleased( - mouseX - (style.horizontalScrollbar.value * expandedWidth), - mouseY - (style.verticalScrollbar.value * expandedHeight) - ) - style.verticalScrollbar.release() - style.horizontalScrollbar.release() - } - - override fun mouseMoved(mouseX: Float, mouseY: Float) { - super.mouseMoved( - mouseX - (style.horizontalScrollbar.value * expandedWidth), - mouseY - (style.verticalScrollbar.value * expandedHeight) - ) - style.verticalScrollbar.mouseMoved() - style.horizontalScrollbar.mouseMoved() - if (style.verticalScrollbar.selected || style.horizontalScrollbar.selected) - requestUpdate() - } - - override fun mouseScrolled(mouseX: Float, mouseY: Float, scrollAmount: Float) { - if (isFocused()) { - style.verticalScrollbar.value -= ((scrollAmount * scrollSensitivity) / style.verticalScrollbar.cachedHeight) - mouseMoved(mouseX, mouseY) - } - super.mouseScrolled(mouseX, mouseY, scrollAmount) - } - - override fun createsStyle(): T = UIContainerSheet() as T -} - -open class UIContainerSheet : UIFrameSheet() { - /** - * Describes when to introduce the scrollbar - * - * @see Overflow - */ - var overflowX: Overflow = Overflow.Auto - - /** - * Describes when to introduce the scrollbar - * - * @see Overflow - */ - var overflowY: Overflow = Overflow.Auto - - var scrollBehaviour: ScrollBehaviour = ScrollBehaviour.Fixed - - var verticalScrollbar: UIScrollbar = UIScrollbar(UIScrollbar.Scrollbar.Vertical) - var horizontalScrollbar: UIScrollbar = UIScrollbar(UIScrollbar.Scrollbar.Horizontal) - - /** - * Creates a DSL block for the vertical scrollbar - */ - inline fun verticalScrollbar(block: UIScrollbar.() -> Unit) = verticalScrollbar.block() - - /** - * Creates a DSL block for the horizontal scrollbar - */ - inline fun horizontalScrollbar(block: UIScrollbar.() -> Unit) = horizontalScrollbar.block() - - /** - * [Overflow] defines what the vertical, and horizontal scrollbars - * are supposed to do when content leaves the screen. Check the enum - * documentation for more information. - */ - enum class Overflow { - /** - * Does not introduce a scrollbar on the given axis - */ - None, - - /** - * Creates a scrollbar on the given axis regardless if content leaves the window - */ - Always, - - /** - * Like scroll, but only adds the scrollbar on the given axis if content leaves the window - */ - Auto - } - - /** - * [ScrollBehaviour] defines how the encompassing container behaves when the mouse is scrolled. - */ - enum class ScrollBehaviour { - /** - * Clamps the content - */ - Fixed, - - /** - * Introduces elasticity to the encompassing container. - */ - Elastic - } - - override fun apply(sheet: UIStyleSheet): UIContainerSheet { - // Override the default apply function because - // this is an inheritable class. - this.immutableStyle = sheet.immutableStyle - this.name = sheet.name - - this.background = sheet.background?.copy() - this.font = sheet.font?.copy() - - this.x = sheet.x?.copy() - this.y = sheet.y?.copy() - this.width = sheet.width?.copy() - this.height = sheet.height?.copy() - - this.padding = sheet.padding?.copy() - this.margin = sheet.margin?.copy() - this.anchor = sheet.anchor?.copy() - this.clipContent = sheet.clipContent - - // Frame properties - if (sheet is UIContainerSheet) { - this.useFBO = sheet.useFBO - this.optimizeRenderer = sheet.optimizeRenderer - this.overflowX = sheet.overflowX - this.overflowY = sheet.overflowY - this.verticalScrollbar = sheet.verticalScrollbar.copy() - this.horizontalScrollbar = sheet.horizontalScrollbar.copy() - } - - return this - } - - override fun copy(): UIContainerSheet = UIContainerSheet().apply(this) -} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIFrame.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIFrame.kt index 6e30a6e..73d348d 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIFrame.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIFrame.kt @@ -2,13 +2,12 @@ package net.prismclient.aether.ui.component.type.layout import net.prismclient.aether.ui.Aether import net.prismclient.aether.ui.component.UIComponent +import net.prismclient.aether.ui.component.type.layout.styles.UIFrameSheet import net.prismclient.aether.ui.dsl.UIRendererDSL import net.prismclient.aether.ui.event.input.UIMouseEvent import net.prismclient.aether.ui.renderer.other.UIContentFBO -import net.prismclient.aether.ui.style.UIStyleSheet import net.prismclient.aether.ui.util.extensions.renderer import net.prismclient.aether.ui.util.interfaces.UIFocusable -import net.prismclient.aether.ui.util.name import net.prismclient.aether.ui.util.warn /** @@ -28,7 +27,7 @@ import net.prismclient.aether.ui.util.warn * @author sen * @since 1.0 */ -abstract class UIFrame : UIComponent(), UIFocusable { +abstract class UIFrame(style: String?) : UIComponent(style), UIFocusable { /** * The components of this frame. */ @@ -60,16 +59,7 @@ abstract class UIFrame : UIComponent(), UIFocusable { * frame has been updated, but prior to the first render. */ open fun updateFBO() { - // Deallocate any existing framebuffer - if (fbo != null) { - Aether.renderer.deleteFBO(fbo!!) - fbo = null - } - if (style.useFBO) { - if ((fbo != null && (fbo!!.width != relWidth || fbo!!.height != relHeight)) || fbo == null) { - fbo = Aether.renderer.createFBO(relWidth, relHeight) - } - } + if (style.useFBO) fbo = Aether.renderer.createFBO(relWidth, relHeight) } /** @@ -103,10 +93,11 @@ abstract class UIFrame : UIComponent(), UIFocusable { components.forEach(UIComponent<*>::render) } } + requiresUpdate = false } - requiresUpdate = false } + override fun render() { updateAnimation() style.background?.render() @@ -115,12 +106,17 @@ abstract class UIFrame : UIComponent(), UIFocusable { override fun renderComponent() { if (style.useFBO) { - renderer { - path { - imagePattern(fbo!!.imagePattern, relX, relY, relWidth, relHeight, 0f, 1f) - rect(relX, relY, relWidth, relHeight) - }.fillPaint() - } + Aether.renderer.renderFbo( + fbo!!, + relX, + relY, + relWidth, + relHeight, + style.background?.radius?.topLeft ?: 0f, + style.background?.radius?.topRight ?: 0f, + style.background?.radius?.bottomLeft ?: 0f, + style.background?.radius?.bottomRight ?: 0f + ) } else { if (style.clipContent) UIRendererDSL.scissor(relX, relY, relWidth, relHeight) { components.forEach(UIComponent<*>::render) @@ -166,53 +162,4 @@ abstract class UIFrame : UIComponent(), UIFocusable { components.forEach { it.mouseScrolled(mouseX, mouseY, scrollAmount) } requestUpdate() } - - override fun createsStyle(): T = UIFrameSheet() as T -} - -open class UIFrameSheet : UIStyleSheet() { - /** - * If true, the frame will use an FBO to render content. - */ - var useFBO: Boolean = false - - /** - * If true certain optimizations will be applied when - * rendering. This only works with [useFBO] as true. - */ - var optimizeRenderer: Boolean = false - set(value) { - if (!useFBO && value) warn("attempted to set optimizeRenderer when useFBO is false. ($name)") - field = value - } - - override fun apply(sheet: UIStyleSheet): UIFrameSheet { - // Override the default apply function because - // this is an inheritable class. - this.immutableStyle = sheet.immutableStyle - this.name = sheet.name - - this.background = sheet.background?.copy() - this.font = sheet.font?.copy() - - this.x = sheet.x?.copy() - this.y = sheet.y?.copy() - this.width = sheet.width?.copy() - this.height = sheet.height?.copy() - - this.padding = sheet.padding?.copy() - this.margin = sheet.margin?.copy() - this.anchor = sheet.anchor?.copy() - this.clipContent = sheet.clipContent - - // Frame properties - if (sheet is UIFrameSheet) { - this.useFBO = sheet.useFBO - this.optimizeRenderer = sheet.optimizeRenderer - } - - return this - } - - override fun copy(): UIFrameSheet = UIFrameSheet().name(name).apply(this) } \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIGridLayout.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIGridLayout.kt deleted file mode 100644 index 2c39452..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIGridLayout.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.prismclient.aether.ui.component.type.layout - -/** - * - * - * @author sen - * @since 1.3 - */ -class UIGridLayout \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIAutoLayout.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayout.kt similarity index 84% rename from src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIAutoLayout.kt rename to src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayout.kt index a731d21..f29b036 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIAutoLayout.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayout.kt @@ -1,12 +1,13 @@ -package net.prismclient.aether.ui.component.type.layout +package net.prismclient.aether.ui.component.type.layout.auto import net.prismclient.aether.ui.component.UIComponent +import net.prismclient.aether.ui.component.type.layout.list.UIListLayout +import net.prismclient.aether.ui.component.type.layout.styles.UIFrameSheet import net.prismclient.aether.ui.component.util.enums.UIAlignment import net.prismclient.aether.ui.component.util.enums.UIAlignment.* import net.prismclient.aether.ui.renderer.UIProvider import net.prismclient.aether.ui.renderer.impl.property.UIPadding import net.prismclient.aether.ui.util.interfaces.UICopy -import net.prismclient.aether.ui.util.name /** * [UIAutoLayout] is a layout which is designed to mimic the behavior of Figma's auto @@ -26,8 +27,10 @@ import net.prismclient.aether.ui.util.name * @author sen * @since 1.1 */ -class UIAutoLayout @JvmOverloads constructor(listDirection: ListDirection = ListDirection.Horizontal) : - UIListLayout(listDirection, ListOrder.Forward), UICopy { +class UIAutoLayout @JvmOverloads constructor( + listDirection: ListDirection = ListDirection.Horizontal, + style: String? +) : UIListLayout(listDirection, ListOrder.Forward, style), UICopy { /** * Defines how the width should be sized. [ResizingMode.Hug] resizes based on the components and * the padding and spacing properties, and [ResizingMode.Fixed] acts like a normal component. @@ -64,14 +67,6 @@ class UIAutoLayout @JvmOverloads constructor(listDirection: ListDirection = List */ var componentAlignment: UIAlignment = TOPLEFT - /** - * Sets the [verticalResizing] and [horizontalResizing] to [vertical] and [horizontal] respectively. - */ - fun resize(vertical: ResizingMode, horizontal: ResizingMode) { - verticalResizing = vertical - horizontalResizing = horizontal - } - override fun updateLayout() { if (components.isEmpty()) return @@ -87,27 +82,28 @@ class UIAutoLayout @JvmOverloads constructor(listDirection: ListDirection = List var h = 0f for (i in components.indices) { - val component = components[i] if (horizontalResizing == ResizingMode.Hug) { w = if (listDirection == ListDirection.Horizontal) { - w + component.relWidth + component.marginLeft + component.marginRight + if (i < components.size - 1) spacing else 0f + w + components[i].relWidth + if (i < components.size - 1) spacing else 0f } else { - (component.relWidth + component.marginLeft + component.marginRight).coerceAtLeast(w) + components[i].relWidth.coerceAtLeast(w) } } if (verticalResizing == ResizingMode.Hug) { h = if (listDirection == ListDirection.Vertical) { - h + component.relHeight + component.marginTop + component.marginBottom + if (i < components.size - 1) spacing else 0f + h + components[i].relHeight + if (i < components.size - 1) spacing else 0f } else { - (component.relHeight + component.marginTop + component.marginBottom).coerceAtLeast(h) + components[i].relHeight.coerceAtLeast(h) } } } // Adjust the width and/or height of the component based on the calculated // size, and ensure that the size is at least the size prior to this. - if (horizontalResizing == ResizingMode.Hug) width = (w + left + right).coerceAtLeast(width) - if (verticalResizing == ResizingMode.Hug) height = (h + top + bottom).coerceAtLeast(height) + if (horizontalResizing == ResizingMode.Hug) + width = (w + left + right).coerceAtLeast(width) + if (verticalResizing == ResizingMode.Hug) + height = (h + top + bottom).coerceAtLeast(height) // Update calculateBounds() @@ -144,7 +140,7 @@ class UIAutoLayout @JvmOverloads constructor(listDirection: ListDirection = List MIDDLELEFT, CENTER, MIDDLERIGHT -> (height - c.height - top - bottom) / 2f BOTTOMLEFT, BOTTOMCENTER, BOTTOMRIGHT -> (height - c.height - left - right) else -> 0f - } + c.marginTop - c.marginBottom + } x += c.width + spacing } else if (listDirection == ListDirection.Vertical) { c.x = x + when (componentAlignment) { @@ -152,7 +148,7 @@ class UIAutoLayout @JvmOverloads constructor(listDirection: ListDirection = List TOPCENTER, CENTER, BOTTOMCENTER -> (width - c.width - left - right) / 2f TOPRIGHT, MIDDLERIGHT, BOTTOMRIGHT -> (width - c.width - left - right) else -> 0f - } + c.marginLeft - c.marginRight + } c.y = y y += c.height + spacing } @@ -168,7 +164,7 @@ class UIAutoLayout @JvmOverloads constructor(listDirection: ListDirection = List /** * Copy the properties of this layout to a new one (excluding components). */ - override fun copy(): UIAutoLayout = UIAutoLayout(listDirection).also { + override fun copy(): UIAutoLayout = UIAutoLayout(listDirection, style.name).also { // UIAutoLayout properties it.horizontalResizing = horizontalResizing it.verticalResizing = verticalResizing diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayoutSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayoutSheet.kt new file mode 100644 index 0000000..7197cc7 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayoutSheet.kt @@ -0,0 +1,16 @@ +package net.prismclient.aether.ui.component.type.layout.auto + +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet + +/** + * [UIAutoLayoutSheet] is the corresponding style sheet to [UIAutoLayout]. See + * the field documentation for more information. + * + * @author sen + * @since 1.0 + */ +class UIAutoLayoutSheet(name: String) : UIContainerSheet(name) { + override fun copy(): UIAutoLayoutSheet = UIAutoLayoutSheet(name).also { + it.apply(this) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/container/UIContainer.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/container/UIContainer.kt new file mode 100644 index 0000000..315d789 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/container/UIContainer.kt @@ -0,0 +1,173 @@ +package net.prismclient.aether.ui.component.type.layout.container + +import net.prismclient.aether.ui.Aether +import net.prismclient.aether.ui.component.UIComponent +import net.prismclient.aether.ui.component.type.layout.UIFrame +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet +import net.prismclient.aether.ui.component.util.interfaces.UILayout +import net.prismclient.aether.ui.event.input.UIMouseEvent +import net.prismclient.aether.ui.util.extensions.renderer +import net.prismclient.aether.ui.util.interfaces.UIFocusable + +/** + * [UIContainer] is the default implementation for [UIFrame]. It introduces + * scrollbars which automatically resize to content being added/removed. It is + * considered to be a [UIFocusable], so when the mouse is scrolled within the + * container the focused component will become this. + * + * + * + * @author sen + * @since 5/12/2022 + */ +open class UIContainer(style: String?) : UIFrame(style), UIFocusable, UILayout { + /** + * How sensitive the scrolling will be + */ + var scrollSensitivity: Float = 10f + + /** + * The expanded width determined by components that leave the component bounds + */ + var expandedWidth = 0f + protected set + + /** + * The expanded height determined by components that leave the components bounds + */ + var expandedHeight = 0f + protected set + + override fun update() { + super.update() + updateLayout() + + // Calculate the distance of the components + // and find the largest of them on both axes + var w = 0f + var h = 0f + + for (i in 0 until components.size) { + val c = components[i] + w = (c.relX + c.relWidth + c.marginRight).coerceAtLeast(w) + h = (c.relY + c.relHeight + c.marginBottom).coerceAtLeast(h) + } + + val x = if (style.useFBO) 0f else relX + val y = if (style.useFBO) 0f else relY + + expandedWidth = (w - relWidth - x).coerceAtLeast(0f) + expandedHeight = (h - relHeight - y).coerceAtLeast(0f) + + updateScrollbar() + } + + override fun updateLayout() { + components.forEach { it.update() } + } + + open fun updateScrollbar() { + style.verticalScrollbar.update(this) + style.horizontalScrollbar.update(this) + } + + open fun renderScrollbar() { + style.verticalScrollbar.render() + style.horizontalScrollbar.render() + } + + override fun renderContent() { + if (style.useFBO && (requiresUpdate || !style.optimizeRenderer)) { + if (fbo == null) { + updateFBO() + } + renderer { + fbo!!.renderToFramebuffer { + translate( + -(style.horizontalScrollbar.value * expandedWidth), + -(style.verticalScrollbar.value * expandedHeight) + ) { + components.forEach(UIComponent<*>::render) + } + } + } + requiresUpdate = false + } + } + + override fun renderComponent() { + if (style.useFBO) { + Aether.renderer.renderFbo( + fbo!!, + relX, + relY, + relWidth, + relHeight, + style.background?.radius?.topLeft ?: 0f, + style.background?.radius?.topRight ?: 0f, + style.background?.radius?.bottomLeft ?: 0f, + style.background?.radius?.bottomRight ?: 0f + ) + } else { + renderer { + translate( + -(style.horizontalScrollbar.value * expandedWidth), + -(style.verticalScrollbar.value * expandedHeight) + ) { + if (style.clipContent) scissor(relX, relY, relWidth, relHeight) { + components.forEach(UIComponent<*>::render) + } + else components.forEach(UIComponent<*>::render) + } + } + } + } + + override fun render() { + super.render() + renderScrollbar() + } + + override fun updateAnimation() { + if (animations != null) { + animations!!.forEach { it.value.update() } + animations!!.entries.removeIf { it.value.isCompleted } + if (animations!!.isEmpty()) + animations = null + updateLayout() + } + } + + override fun mousePressed(event: UIMouseEvent) { + super.mousePressed(event) + if (style.verticalScrollbar.mousePressed() || style.horizontalScrollbar.mousePressed()) focus() + } + + override fun mouseReleased(mouseX: Float, mouseY: Float) { + super.mouseReleased( + mouseX - (style.horizontalScrollbar.value * expandedWidth), + mouseY - (style.verticalScrollbar.value * expandedHeight) + ) + style.verticalScrollbar.release() + style.horizontalScrollbar.release() + } + + override fun mouseMoved(mouseX: Float, mouseY: Float) { + super.mouseMoved( + mouseX - (style.horizontalScrollbar.value * expandedWidth), + mouseY - (style.verticalScrollbar.value * expandedHeight) + ) + style.verticalScrollbar.mouseMoved() + style.horizontalScrollbar.mouseMoved() + if (style.verticalScrollbar.selected || style.horizontalScrollbar.selected) + requestUpdate() + } + + override fun mouseScrolled(mouseX: Float, mouseY: Float, scrollAmount: Float) { + if (isFocused()) { + style.verticalScrollbar.value -= ((scrollAmount * scrollSensitivity) / style.verticalScrollbar.cachedHeight) + mouseMoved(mouseX, mouseY) + } + super.mouseScrolled(mouseX, mouseY, scrollAmount) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/grid/UIGridLayout.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/grid/UIGridLayout.kt new file mode 100644 index 0000000..624d639 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/grid/UIGridLayout.kt @@ -0,0 +1,155 @@ +package net.prismclient.aether.ui.component.type.layout.grid + +class UIGridLayout + +//open class UIGridLayoutComponent(style: String?) : UIContainer(style) { +// protected var columns: Array = arrayOf() +// protected var rows: Array = arrayOf() +// +// var columnSpacing: UIPixel = px(0) +// var rowSpacing: UIPixel = px(0) +// +// var retainSpace = true /* If there is a missing component the grid will take the previous row's spacing to fill */ +// +// fun width(): Float = width +// +// fun height(): Float = height +// +// override fun updateLayout() { +// var i = 0 +// +// val leftoverWidth = FloatArray(rows.size) { 0f } +// var leftoverHeight = 0f +// val heights = FloatArray(rows.size) { 0f } +// var fractionWidthValue = 0f +// var fractionHeightValue = 0f +// var widthAutoCount = 0 +// var heightAutoCount = 0 +// +// var hoffset = 0f +// for ((r, row) in rows.withIndex()) { +// var woffset = 0f +// for ((c, column) in columns.withIndex()) { +// val child = children.getOrNull(i).also { +// it?.updateSize() +// it?.overrided = true +// } +// var w = when (column.type) { +// INITIAL -> width(i) +// PIXELS -> column.value +// RELATIVE -> column.value * width() +// FRACTION -> { +// if (r == 0) { fractionWidthValue += column.value } +// width(i) +// } +// AUTO -> { +// if (r == 0) { widthAutoCount++ } +// width(i) +// } +// else -> throw UnsupportedOperationException() +// } +// var h = when (row.type) { +// INITIAL -> height(i) +// PIXELS -> row.value +// RELATIVE -> row.value * height() +// FRACTION, AUTO -> height(i) +// else -> throw UnsupportedOperationException() +// } +// +// if (child != null) { +// if (heights[r] < h) { +// heights[r] = h +// } +// +// child.x = x + woffset +// child.y = y// + hoffset +// child.width = w +// child.height = h +// w = child.relWidth +// } +// +// woffset += w + if (c < columns.size - 1) columnSpacing.calculate(width) else 0f +// +// i++ +// } +// if (row.type == AUTO) { +// heightAutoCount++ +// } else if (row.type == FRACTION) { +// fractionHeightValue += row.value +// } +// leftoverWidth[r] = width() - woffset +// hoffset += heights[r] + if (r < rows.size - 2) rowSpacing.calculate(height) else 0f +// } +// leftoverHeight = height() - hoffset +// +// i = 0 +// var hp = 0f +// +// for ((r, row) in rows.withIndex()) { +// val widthFractionPush = leftoverWidth[r] / max(1f, fractionWidthValue) +// val heightFractionPush = leftoverHeight / max(1f, fractionHeightValue) +// var totalHeight = 0f +// for (column in columns) { +// val child = children.getOrNull(i) +// +// if (child != null) { +// child.height = heights[r] +// +// var wpush = 0f +// var hpush = 0f +// +// if (column.type == AUTO) { +// if (fractionWidthValue < 1f) { +// wpush = (leftoverWidth[r] - (widthFractionPush * fractionWidthValue)) / widthAutoCount +// } +// } else if (column.type == FRACTION) { +// wpush = widthFractionPush * column.value +// } +// +// if (row.type == AUTO) { +// if (fractionHeightValue < 1f) { +// hpush = (leftoverHeight / heightAutoCount) +// } +// } else if (row.type == FRACTION) { +// hpush = heightFractionPush * row.value//leftoverHeight * row.value +// } +// +// if (wpush != 0f || hpush != 0f) { +// child.y += hp +// child.width += wpush +// child.height += hpush +// totalHeight = child.height +// +// +// for (j in i + 1 until (columns.size * (r + 1))) { +// (children.getOrNull(j) ?: break).x += wpush +// } +// } +// } +// i++ +// } +// hp += totalHeight + if (r < rows.size - 1) rowSpacing.calculate(height) else 0f +// } +// children.forEach(UIComponent::update) +// } +// +// private fun width(index: Int) = children.getOrNull(index)?.width ?: 0f +// +// private fun height(index: Int) = children.getOrNull(index)?.height ?: 0f +// +// fun column(vararg units: UIPixel) { +// columns = arrayOf(*units) +// } +// +// fun row(vararg units: UIPixel) { +// rows = arrayOf(*units) +// } +// +// fun acolumn(units: Array) { +// columns = units +// } +// +// fun arow(units: Array) { +// rows = units +// } +//} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIListLayout.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/list/UIListLayout.kt similarity index 88% rename from src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIListLayout.kt rename to src/main/kotlin/net/prismclient/aether/ui/component/type/layout/list/UIListLayout.kt index 1b6a63f..692b393 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIListLayout.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/list/UIListLayout.kt @@ -1,7 +1,9 @@ -package net.prismclient.aether.ui.component.type.layout +package net.prismclient.aether.ui.component.type.layout.list -import net.prismclient.aether.ui.component.type.layout.UIListLayout.ListOrder.Backwards -import net.prismclient.aether.ui.component.type.layout.UIListLayout.ListOrder.Forward +import net.prismclient.aether.ui.component.type.layout.container.UIContainer +import net.prismclient.aether.ui.component.type.layout.list.UIListLayout.ListOrder.Backwards +import net.prismclient.aether.ui.component.type.layout.list.UIListLayout.ListOrder.Forward +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet import net.prismclient.aether.ui.unit.UIUnit /** @@ -14,7 +16,8 @@ import net.prismclient.aether.ui.unit.UIUnit open class UIListLayout @JvmOverloads constructor( var listDirection: ListDirection = ListDirection.Vertical, var listOrder: ListOrder = Forward, -) : UIContainer() { + style: String? +) : UIContainer(style) { /** * The spacing between each component in the layout. */ diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIContainerSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIContainerSheet.kt new file mode 100644 index 0000000..210ac5e --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIContainerSheet.kt @@ -0,0 +1,102 @@ +package net.prismclient.aether.ui.component.type.layout.styles + +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet.Overflow +import net.prismclient.aether.ui.renderer.impl.scrollbar.UIScrollbar +import net.prismclient.aether.ui.style.UIStyleSheet + +/** + * [UIContainerSheet] is the corresponding style sheet for containers. It provides + * properties for the scrollbar and when to introduce them + * + * @author sen + * @since 5/12/2022 + * @see UIScrollbar + * @see Overflow + */ +open class UIContainerSheet @JvmOverloads constructor(name: String = "") : UIFrameSheet(name) { + /** + * Describes when to introduce the scrollbar + * + * @see Overflow + */ + var overflowX: Overflow = Overflow.Auto + + /** + * Describes when to introduce the scrollbar + * + * @see Overflow + */ + var overflowY: Overflow = Overflow.Auto + + var verticalScrollbar: UIScrollbar = UIScrollbar(UIScrollbar.Scrollbar.Vertical) + var horizontalScrollbar: UIScrollbar = UIScrollbar(UIScrollbar.Scrollbar.Horizontal) + + /** + * Creates a DSL block for the vertical scrollbar + */ + inline fun verticalScrollbar(block: UIScrollbar.() -> Unit) = verticalScrollbar.block() + + /** + * Creates a DSL block for the horizontal scrollbar + */ + inline fun horizontalScrollbar(block: UIScrollbar.() -> Unit) = horizontalScrollbar.block() + + /** + * [Overflow] defines what the vertical, and horizontal scrollbars + * are supposed to do when content leaves the screen. Check the enum + * documentation for more information. + * + * @author sen + * @since 5/12/2022 + */ + enum class Overflow { + /** + * Does not introduce a scrollbar on the given axis + */ + None, + + /** + * Creates a scrollbar on the given axis regardless if content leaves the window + */ + Always, + + /** + * Like scroll, but only adds the scrollbar on the given axis if content leaves the window + */ + Auto + } + + override fun apply(sheet: UIStyleSheet): UIContainerSheet { + // Override the default apply function because + // this is an inheritable class. + this.immutableStyle = sheet.immutableStyle + this.name = sheet.name + + this.background = sheet.background?.copy() + this.font = sheet.font?.copy() + + this.x = sheet.x?.copy() + this.y = sheet.y?.copy() + this.width = sheet.width?.copy() + this.height = sheet.height?.copy() + + this.padding = sheet.padding?.copy() + this.margin = sheet.margin?.copy() + this.anchor = sheet.anchor?.copy() + this.clipContent = sheet.clipContent + + // Frame properties + if (sheet is UIContainerSheet) { + this.useFBO = sheet.useFBO + this.optimizeRenderer = sheet.optimizeRenderer + this.overflowX = sheet.overflowX + this.overflowY = sheet.overflowY + this.verticalScrollbar = sheet.verticalScrollbar.copy() + this.horizontalScrollbar = sheet.horizontalScrollbar.copy() + } + + return this + } + + override fun copy(): UIContainerSheet = UIContainerSheet(name).apply(this) +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIFrameSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIFrameSheet.kt new file mode 100644 index 0000000..ff31030 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIFrameSheet.kt @@ -0,0 +1,53 @@ +package net.prismclient.aether.ui.component.type.layout.styles + +import net.prismclient.aether.ui.style.UIStyleSheet + +/** + * [UIFrameSheet] is the corresponding style sheet for frames. See field + * documentation for more information. + * + * @author sen + * @since 1.0 + */ +open class UIFrameSheet(name: String) : UIStyleSheet(name) { + /** + * If true, the frame will use an FBO to render content. + */ + var useFBO: Boolean = false + + /** + * If true certain optimizations will be applied when + * rendering. This only works with [useFBO] as true. + */ + var optimizeRenderer: Boolean = true + + override fun apply(sheet: UIStyleSheet): UIFrameSheet { + // Override the default apply function because + // this is an inheritable class. + this.immutableStyle = sheet.immutableStyle + this.name = sheet.name + + this.background = sheet.background?.copy() + this.font = sheet.font?.copy() + + this.x = sheet.x?.copy() + this.y = sheet.y?.copy() + this.width = sheet.width?.copy() + this.height = sheet.height?.copy() + + this.padding = sheet.padding?.copy() + this.margin = sheet.margin?.copy() + this.anchor = sheet.anchor?.copy() + this.clipContent = sheet.clipContent + + // Frame properties + if (sheet is UIFrameSheet) { + this.useFBO = sheet.useFBO + this.optimizeRenderer = sheet.optimizeRenderer + } + + return this + } + + override fun copy(): UIFrameSheet = UIFrameSheet(name).apply(this) +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UITab.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/tab/UITab.kt similarity index 53% rename from src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UITab.kt rename to src/main/kotlin/net/prismclient/aether/ui/component/type/layout/tab/UITab.kt index c47a21e..2b1a715 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UITab.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/tab/UITab.kt @@ -1,3 +1,3 @@ -package net.prismclient.aether.ui.component.type.layout +package net.prismclient.aether.ui.component.type.layout.tab class UITab // https://chakra-ui.com/docs/components/disclosure/tabs \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/other/UIProgress.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgress.kt similarity index 58% rename from src/main/kotlin/net/prismclient/aether/ui/component/type/other/UIProgress.kt rename to src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgress.kt index 69bd0d2..583f357 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/other/UIProgress.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgress.kt @@ -1,10 +1,7 @@ -package net.prismclient.aether.ui.component.type.other +package net.prismclient.aether.ui.component.type.other.progress import net.prismclient.aether.ui.component.UIComponent -import net.prismclient.aether.ui.style.UIStyleSheet -import net.prismclient.aether.ui.util.UIColor import net.prismclient.aether.ui.util.extensions.renderer -import net.prismclient.aether.ui.util.name /** * [UIProgress] is a component which displays the distance between the x and width @@ -15,27 +12,14 @@ import net.prismclient.aether.ui.util.name * To configure the color see [UIProgressSheet.progressColor]. * * @author sen - * @since 1.0 + * @since 6/23/2022 */ -class UIProgress @JvmOverloads constructor(var progress: Float = 0f) : UIComponent() { +class UIProgress @JvmOverloads constructor(var progress: Float = 0f, style: String?) : + UIComponent(style) { override fun renderComponent() { renderer { color(style.progressColor) rect(relX, relY, relWidth * progress, relHeight, style.background?.radius) } } - - override fun createsStyle(): UIProgressSheet = UIProgressSheet() -} - -class UIProgressSheet : UIStyleSheet() { - /** - * The color of the actual progress bar. - */ - var progressColor: UIColor? = null - - override fun copy() = UIProgressSheet().name(name).also { - it.apply(this) - it.progressColor = progressColor - } } \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgressSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgressSheet.kt new file mode 100644 index 0000000..44b73e9 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgressSheet.kt @@ -0,0 +1,21 @@ +package net.prismclient.aether.ui.component.type.other.progress + +import net.prismclient.aether.ui.style.UIStyleSheet + +/** + * [UIProgressSheet] is the corresponding sheet to [UIProgress]. + * + * @author sen + * @since 6/23/2022 + */ +class UIProgressSheet(name: String) : UIStyleSheet(name) { + /** + * The color of the progress + */ + var progressColor = -1 + + override fun copy() = UIProgressSheet(name).also { + it.apply(this) + it.progressColor = progressColor + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIAlignment.kt b/src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIAlignment.kt index 12aaeb6..62f9ea8 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIAlignment.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIAlignment.kt @@ -7,7 +7,7 @@ package net.prismclient.aether.ui.component.util.enums * @author sen * @since 1.0 */ -@Suppress("Unused", "SpellCheckingInspection") +@Suppress("unused", "SpellCheckingInspection") enum class UIAlignment { CUSTOM, TOPLEFT, TOPCENTER, TOPRIGHT, diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIOverflow.kt b/src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIOverflow.kt new file mode 100644 index 0000000..d1037de --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIOverflow.kt @@ -0,0 +1,8 @@ +package net.prismclient.aether.ui.component.util.enums + +enum class UIOverflow { + VISIBLE, + HIDDEN, + SCROLL, + AUTO +} \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIAssetDSL.kt b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIAssetDSL.kt index 83125fe..73a338b 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIAssetDSL.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIAssetDSL.kt @@ -108,12 +108,12 @@ object UIAssetDSL { svgScale: Float = Aether.devicePxRatio ): Int { val file = Aether.javaClass.getResource(folderLocation) ?: run { - error("Failed to bulk load [$folderLocation] as the file was null.") + warn("Failed to bulk load [$folderLocation] as the file was null.") return 0 } return internalBulkLoad(File(file.toURI()), deep, appendedString, imageFlags, svgScale).also { - inform("Bulk loaded $it files.") + warn("Bulk loaded $it files.") } } diff --git a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIComponentDSL.kt b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIComponentDSL.kt index ded9ef4..f4649bd 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIComponentDSL.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIComponentDSL.kt @@ -7,14 +7,15 @@ import net.prismclient.aether.ui.component.controller.impl.selection.UISelectabl import net.prismclient.aether.ui.component.type.UILabel import net.prismclient.aether.ui.component.type.image.UIImage import net.prismclient.aether.ui.component.type.input.button.UIButton +import net.prismclient.aether.ui.component.type.input.button.UICheckbox import net.prismclient.aether.ui.component.type.input.slider.UISlider import net.prismclient.aether.ui.component.type.layout.UIFrame -import net.prismclient.aether.ui.component.type.layout.UIAutoLayout -import net.prismclient.aether.ui.component.type.layout.UIContainer -import net.prismclient.aether.ui.component.type.layout.UIListLayout -import net.prismclient.aether.ui.component.type.layout.UIContainerSheet +import net.prismclient.aether.ui.component.type.layout.auto.UIAutoLayout +import net.prismclient.aether.ui.component.type.layout.container.UIContainer +import net.prismclient.aether.ui.component.type.layout.list.UIListLayout +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet import net.prismclient.aether.ui.dsl.UIComponentDSL.activeStyle -import net.prismclient.aether.ui.util.Block +import net.prismclient.aether.ui.style.UIStyleSheet import net.prismclient.aether.ui.util.interfaces.UIDependable import java.util.* @@ -135,9 +136,8 @@ object UIComponentDSL { * * @return T The component */ - inline fun > component(component: T, style: String?, block: Block): T { + inline fun > component(component: T, block: T.() -> Unit): T { pushComponent(component) - component.applyStyle(style) component.block() component.initialize() popComponent(component) @@ -151,7 +151,7 @@ object UIComponentDSL { * * @see ignore */ - inline fun , T : UIComponent<*>> controller(controller: C, block: Block) { + inline fun , T : UIComponent<*>> controller(controller: C, block: C.() -> Unit) { Aether.instance.controllers!!.add(controller) activeController = controller controller.block() @@ -161,7 +161,7 @@ object UIComponentDSL { /** * Creates a block where the [activeController] is ignored. */ - inline fun ignore(block: Block): UIComponentDSL { + inline fun ignore(block: UIComponentDSL.() -> Unit): UIComponentDSL { ignoreController = true block(this) ignoreController = false @@ -171,7 +171,7 @@ object UIComponentDSL { /** * Creates a block where the style is set to the given value. */ - inline fun style(styleName: String, block: Block) { + inline fun style(styleName: String, block: UIComponentDSL.() -> Unit) { // Technically this function supports nesting, soooooo the documentation // is "technically" wrong but whatever. In the case of controllers, it // doesn't actually make sense to have the ability to nest them. @@ -187,7 +187,8 @@ object UIComponentDSL { */ fun include(dependable: UIDependable) = dependable.load() - fun getActiveComponent(): UIComponent<*>? = if (componentStack.isNullOrEmpty()) null else componentStack!!.peek() + fun getActiveComponent(): UIComponent<*>? = + if (componentStack.isNullOrEmpty()) null else componentStack!!.peek() fun getActiveFrame(): UIFrame<*>? = if (frameStack.isNullOrEmpty()) null else frameStack!!.peek() @@ -206,8 +207,8 @@ object UIComponentDSL { * @see label */ @JvmOverloads - inline fun text(text: String, style: String? = activeStyle, block: Block = {}) = - component(UILabel(text), style, block) + inline fun text(text: String, style: String? = activeStyle, block: UILabel.() -> Unit = {}) = + component(UILabel(text, style), block) /** * An alternative to [text]. Creates a [UILabel] with the provided [text]. @@ -215,14 +216,27 @@ object UIComponentDSL { * @see text */ @JvmOverloads - inline fun label(text: String, style: String? = activeStyle, block: Block = {}) = - component(UILabel(text), style, block) + inline fun label(text: String, style: String? = activeStyle, block: UILabel.() -> Unit = {}) = + component(UILabel(text, style), block) /** * Creates a [UIButton] with the provided [text], like a label. */ - inline fun button(text: String, style: String? = activeStyle, block: Block = {}) = - component(UIButton(text), style, block) + inline fun button(text: String, style: String? = activeStyle, block: UIButton.() -> Unit = {}) = + component(UIButton(text, style), block) + + /** + * Creates a [UICheckbox] from the given [selectedImageName], [deselectedImageName] and [imageStyle]. + */ + @JvmOverloads + inline fun checkbox( + checked: Boolean = false, + selectedImageName: String = "checkbox", + deselectedImageName: String = "", + imageStyle: String, + style: String? = activeStyle, + block: UICheckbox.() -> Unit + ) = component(UICheckbox(checked, selectedImageName, deselectedImageName, imageStyle, style), block) /** * Creates a [UISlider] with the given [value] which stays within the [range] and steps by the @@ -234,22 +248,22 @@ object UIComponentDSL { range: ClosedFloatingPointRange, step: Number, style: String? = activeStyle, - block: Block = {} - ) = component(UISlider(value.toDouble(), range, step.toDouble()), style, block) + block: UISlider.() -> Unit = {} + ) = component(UISlider(value.toDouble(), range, step.toDouble(), style), block) /** * Creates a [UIImage] with the [imageName] as the image to be rendered. */ @JvmOverloads - inline fun image(imageName: String, style: String? = activeStyle, block: Block = {}) = - component(UIImage(imageName), style, block) + inline fun image(imageName: String, style: String? = activeStyle, block: UIImage.() -> Unit = {}) = + component(UIImage(imageName, style), block) /** * Creates a [UIContainer]. Anything within the block will be added to the component list within this. */ @JvmOverloads - inline fun container(style: String? = activeStyle, block: Block> = {}) = - component(UIContainer(), style, block) + inline fun container(style: String? = activeStyle, block: UIContainer.() -> Unit = {}) = + component(UIContainer(style), block) /** * Creates a [UIListLayout] with the given [listDirection], which defines the direction that it @@ -261,50 +275,14 @@ object UIComponentDSL { listDirection: UIListLayout.ListDirection, listOrder: UIListLayout.ListOrder = UIListLayout.ListOrder.Forward, style: String? = activeStyle, - block: Block = {} - ) = component(UIListLayout(listDirection, listOrder), style, block) - - /** - * Creates a vertical list via [list]. - * - * @see list - */ - inline fun verticalList( - listOrder: UIListLayout.ListOrder = UIListLayout.ListOrder.Forward, - style: String? = activeStyle, - block: Block = {} - ) = list(UIListLayout.ListDirection.Vertical, listOrder, style, block) - - /** - * Creates a horizontal list via [list]. - * - * @see list - */ - inline fun horizontalList( - listOrder: UIListLayout.ListOrder = UIListLayout.ListOrder.Forward, - style: String? = activeStyle, - block: Block = {} - ) = list(UIListLayout.ListDirection.Horizontal, listOrder, style, block) + block: UIListLayout.() -> Unit = {} + ) = component(UIListLayout(listDirection, listOrder, style), block) /** * Creates a copy of the given layout and creates a normal block of [UIAutoLayout] where * components can be defined. */ - @JvmOverloads - inline fun autoLayout(layout: UIAutoLayout, block: Block = {}) = component(layout.copy(), activeStyle, block) - - /** - * Creates an [UIAutoLayout] from the given [listDirection]. [UIAutoLayout] are designed to mimic Figma's - * auto layout feature. See [UIAutoLayout] for more information. - * - * @see UIAutoLayout - */ - @JvmOverloads - inline fun autoLayout( - listDirection: UIListLayout.ListDirection, - style: String? = null, - block: Block = {} - ) = component(UIAutoLayout(listDirection), style, block) + inline fun autoLayout(layout: UIAutoLayout, block: UIAutoLayout.() -> Unit) = component(layout.copy(), block) /** * Creates a [UISelectableController] which is a controller that has a single selected @@ -313,7 +291,6 @@ object UIComponentDSL { * @see controller * @see UISelectableController */ - @JvmOverloads - inline fun > selectable(block: Block> = {}) = + inline fun > selectable(block: UISelectableController.() -> Unit) = controller(UISelectableController(T::class), block) } \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIPathDSL.kt b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIPathDSL.kt index 3d33bfb..b49d5aa 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIPathDSL.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIPathDSL.kt @@ -2,8 +2,6 @@ package net.prismclient.aether.ui.dsl import net.prismclient.aether.ui.Aether import net.prismclient.aether.ui.renderer.UIRenderer -import net.prismclient.aether.ui.renderer.impl.property.UIRadius -import net.prismclient.aether.ui.util.Block /** * [UIPathDSL] is a DSL for paths. [UIRendererDSL] utilizes this to apply the paths. @@ -151,21 +149,6 @@ object UIPathDSL { x: Float, y: Float, radius: Float, startAngle: Float, endAngle: Float, windingOrder: UIRenderer.WindingOrder ) = renderer.arc(x, y, radius, startAngle, endAngle, windingOrder) - /** - * Renders a rectangle sub-path with the given bounds and [radius]. - */ - @JvmStatic - fun rect(x: Float, y: Float, width: Float, height: Float, radius: UIRadius?) = rect( - x, - y, - width, - height, - radius?.topLeft ?: 0f, - radius?.topRight ?: 0f, - radius?.bottomRight ?: 0f, - radius?.bottomLeft ?: 0f - ) - /** * Creates a rectangle sub-path with a single radius value. */ @@ -233,8 +216,8 @@ object UIPathDSL { * @see See NanoVG Composite paths */ @JvmStatic - inline fun hole(block: Block): UIPathDSL { - UIPathDSL.block() + inline fun hole(block: UIPathDSL.() -> Unit): UIPathDSL { + block() renderer.pathHole(true) return this } diff --git a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIRendererDSL.kt b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIRendererDSL.kt index cf0f1c0..926acc8 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIRendererDSL.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIRendererDSL.kt @@ -8,7 +8,8 @@ import net.prismclient.aether.ui.renderer.impl.border.UIStrokeDirection import net.prismclient.aether.ui.renderer.impl.font.UIFont import net.prismclient.aether.ui.renderer.impl.property.UIRadius import net.prismclient.aether.ui.renderer.other.UIContentFBO -import net.prismclient.aether.ui.util.* +import net.prismclient.aether.ui.util.Block +import net.prismclient.aether.ui.util.UIColor /** * [UIRendererDSL] wraps the [UIRenderer] class to minimize the amount of calls @@ -76,7 +77,7 @@ object UIRendererDSL { */ @JvmStatic fun color(color: Int) { - activeColor = color + UIRendererDSL.activeColor = color renderer.color(color) } @@ -153,13 +154,13 @@ object UIRendererDSL { * @see fontBounds */ @JvmStatic - fun fontWidth(): Float = fontBounds().maxX() - fontBounds().minX() + fun fontWidth(): Float = fontBounds()[2] - fontBounds()[0] /** * Returns the height of the most recent text render call. */ @JvmStatic - fun fontHeight(): Float = fontBounds().maxY() - fontBounds().minY() + fun fontHeight(): Float = fontBounds()[3] - fontBounds()[1] /** * Returns the ascender of the most recent text render call. @@ -175,9 +176,6 @@ object UIRendererDSL { // -- General Rendering -- // - /** - * renders a rectangle with the given bounds and [radius]. - */ @JvmStatic fun rect(x: Float, y: Float, width: Float, height: Float, radius: UIRadius?) = rect( x, @@ -401,7 +399,7 @@ object UIRendererDSL { @JvmStatic inline fun UIContentFBO.renderToFramebuffer(block: Block): UIRendererDSL { renderer.bindFBO(this) - beginFrame(this.width, this.height, this.contentScale) + beginFrame(this.scaledWidth, this.scaledHeight, this.contentScale) UIRendererDSL.block() endFrame() renderer.unbindFBO() diff --git a/src/main/kotlin/net/prismclient/aether/ui/renderer/UIProvider.kt b/src/main/kotlin/net/prismclient/aether/ui/renderer/UIProvider.kt index 5727146..1b94e72 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/renderer/UIProvider.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/renderer/UIProvider.kt @@ -48,9 +48,10 @@ object UIProvider { * Returns the name of the images given the [UIImageData] */ fun getImageName(imageData: UIImageData): String? { - for ((name, image) in images) { - if (image == imageData) - return name + for (image in images) { + if (image.value == imageData) { + return image.key + } } return null } diff --git a/src/main/kotlin/net/prismclient/aether/ui/renderer/UIRenderer.kt b/src/main/kotlin/net/prismclient/aether/ui/renderer/UIRenderer.kt index cc7ac79..a7ba8af 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/renderer/UIRenderer.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/renderer/UIRenderer.kt @@ -1,6 +1,7 @@ package net.prismclient.aether.ui.renderer import net.prismclient.aether.ui.renderer.image.UIImageData +import net.prismclient.aether.ui.renderer.impl.property.UIRadius import net.prismclient.aether.ui.renderer.other.UIContentFBO import java.nio.ByteBuffer @@ -138,6 +139,18 @@ interface UIRenderer { */ fun unbindFBO() + fun renderFbo( + fbo: UIContentFBO, + x: Float, + y: Float, + width: Float, + height: Float, + topLeft: Float, + topRight: Float, + bottomRight: Float, + bottomLeft: Float + ) + // -- Asset Loading --/ /** * Creates an image from the given [data] registered to the [imageName]. diff --git a/src/main/kotlin/net/prismclient/aether/ui/renderer/impl/background/UIGradientBackground.kt b/src/main/kotlin/net/prismclient/aether/ui/renderer/impl/background/UIGradientBackground.kt index 6097eb6..985502f 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/renderer/impl/background/UIGradientBackground.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/renderer/impl/background/UIGradientBackground.kt @@ -2,19 +2,18 @@ package net.prismclient.aether.ui.renderer.impl.background import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.unit.UIUnit -import net.prismclient.aether.ui.util.UIColor import net.prismclient.aether.ui.util.extensions.calculate import net.prismclient.aether.ui.util.extensions.renderer /** - * [UIGradientBackground] is the background renderer for a component which requests a gradient instead of a solid. + * [UIGradientBackground] is the background renderer for a component which requests a gradient instead of a solid * * @author sen - * @since 41.0 + * @since 4/26/2022 */ class UIGradientBackground : UIBackground() { - var gradientStartColor: UIColor? = null - var gradientEndColor: UIColor? = null + var gradientStartColor = 0 + var gradientEndColor = 0 var gradientX: UIUnit? = null var gradientY: UIUnit? = null var gradientWidth: UIUnit? = null @@ -45,18 +44,15 @@ class UIGradientBackground : UIBackground() { component?.relHeight ?: 0f, true ) - gradientWidthCache = gradientXCache + + gradientWidthCache = calculate(gradientWidth, component, component?.relWidth ?: 0f, component?.relHeight ?: 0f, false) - gradientHeightCache = gradientYCache + + gradientHeightCache = calculate(gradientHeight, component, component?.relWidth ?: 0f, component?.relHeight ?: 0f, true) } override fun render() { renderer { - path { - linearGradient(gradientXCache, gradientYCache, gradientWidthCache, gradientHeightCache, gradientStartColor?.rgba ?: 0, gradientEndColor?.rgba ?: 0) - rect(cachedX, cachedY, cachedWidth, cachedHeight, radius) - }.fillPaint() + TODO("Gradient suport") } } diff --git a/src/main/kotlin/net/prismclient/aether/ui/renderer/impl/scrollbar/UIScrollbar.kt b/src/main/kotlin/net/prismclient/aether/ui/renderer/impl/scrollbar/UIScrollbar.kt index ee79d69..a2e837d 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/renderer/impl/scrollbar/UIScrollbar.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/renderer/impl/scrollbar/UIScrollbar.kt @@ -1,8 +1,8 @@ package net.prismclient.aether.ui.renderer.impl.scrollbar import net.prismclient.aether.ui.component.UIComponent -import net.prismclient.aether.ui.component.type.layout.UIContainer -import net.prismclient.aether.ui.component.type.layout.UIContainerSheet +import net.prismclient.aether.ui.component.type.layout.container.UIContainer +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet import net.prismclient.aether.ui.renderer.impl.background.UIBackground import net.prismclient.aether.ui.renderer.impl.border.UIBorder import net.prismclient.aether.ui.renderer.impl.property.UIRadius @@ -56,7 +56,7 @@ class UIScrollbar(val type: Scrollbar) : UIColoredShape() { } fun shouldRender() { - val container = component as UIContainer + val container = component as UIContainer<*> // Check based on the overflow if the scrollbar should be rendered or not shouldRender = if (type == Scrollbar.Vertical) { @@ -121,25 +121,12 @@ class UIScrollbar(val type: Scrollbar) : UIColoredShape() { val mouseX = component!!.getMouseX() val mouseY = component!!.getMouseY() - val isSliderSelected = mouseX >= x && mouseX <= x + w && mouseY >= y && mouseY <= y + h - val isScrollbarSelected = mouseX >= cachedX && mouseX <= cachedX + cachedWidth && mouseY >= cachedY && mouseY <= cachedY + cachedHeight - - if (isSliderSelected) { + if (mouseX >= x && mouseX <= x + w && mouseY >= y && mouseY <= y + h) { selected = true mouseOffset = if (type == Scrollbar.Vertical) mouseY - y else mouseX - x - } else if (isScrollbarSelected) { - println("scrollbarSelected") - -// value = if (type == Scrollbar.Vertical) { -// (mouseY - cachedY - (mouseY - y)) / (cachedHeight - sliderSize).coerceAtLeast(Float.MIN_VALUE) -// } else { -// (mouseX - cachedX - (mouseX - x)) / (cachedWidth - sliderSize).coerceAtLeast(Float.MIN_VALUE) -// } - } else { - return false + return true } - - return true + return false } fun mouseMoved() { diff --git a/src/main/kotlin/net/prismclient/aether/ui/style/UIStyleSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/style/UIStyleSheet.kt index 353084a..eb9ed72 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/style/UIStyleSheet.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/style/UIStyleSheet.kt @@ -2,7 +2,7 @@ package net.prismclient.aether.ui.style import net.prismclient.aether.ui.animation.UIAnimation import net.prismclient.aether.ui.component.UIComponent -import net.prismclient.aether.ui.component.type.layout.UIFrameSheet +import net.prismclient.aether.ui.component.type.layout.styles.UIFrameSheet import net.prismclient.aether.ui.component.util.enums.UIAlignment import net.prismclient.aether.ui.component.util.enums.UIAlignment.* import net.prismclient.aether.ui.renderer.impl.background.UIBackground @@ -19,7 +19,6 @@ import net.prismclient.aether.ui.util.extensions.px import net.prismclient.aether.ui.util.extensions.rel import net.prismclient.aether.ui.util.interfaces.UIAnimatable import net.prismclient.aether.ui.util.interfaces.UICopy -import net.prismclient.aether.ui.util.name /** * [UIStyleSheet] is the superclass of all styles. It holds the general @@ -40,13 +39,11 @@ import net.prismclient.aether.ui.util.name * are thrown when used. If the style sheet is intended on being inheritable, the apply method * should also be overridden. See [UIFrameSheet] for an example. * - * @since 1.0 * @see Styles * @see How to create styles */ -open class UIStyleSheet() : UICopy, UIAnimatable { - var name: String = "" - +open class UIStyleSheet @JvmOverloads constructor(var name: String = "") : UICopy, + UIAnimatable { /** * When true, the property will not be cleared when Aether cleans up styles. */ @@ -85,12 +82,10 @@ open class UIStyleSheet() : UICopy, UIAnimatable { ) { val component = animation.component - if (!component.overridden) { - component.x = previous?.x.lerp(current?.x, component, x, progress, false) - component.y = previous?.y.lerp(current?.y, component, y, progress, true) - component.width = previous?.width.lerp(current?.width, component, width, progress, false) - component.height = previous?.height.lerp(current?.height, component, height, progress, true) - } + component.x = previous?.x.lerp(current?.x, component, x, progress, false) + component.y = previous?.y.lerp(current?.y, component, y, progress, true) + component.width = previous?.width.lerp(current?.width, component, width, progress, false) + component.height = previous?.height.lerp(current?.height, component, height, progress, true) if (previous?.background != null || current?.background != null) { background = background ?: UIBackground() @@ -349,7 +344,7 @@ open class UIStyleSheet() : UICopy, UIAnimatable { this.marginLeft = marginLeft } - override fun copy(): UIStyleSheet = UIStyleSheet().name(name).apply(this) + override fun copy(): UIStyleSheet = UIStyleSheet(name).apply(this) /** * Applies the properties of an existing sheet to this diff --git a/src/main/kotlin/net/prismclient/aether/ui/style/util/UIAnchorPoint.kt b/src/main/kotlin/net/prismclient/aether/ui/style/util/UIAnchorPoint.kt index 1aa6ca5..db2bfe2 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/style/util/UIAnchorPoint.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/style/util/UIAnchorPoint.kt @@ -3,7 +3,6 @@ package net.prismclient.aether.ui.style.util import net.prismclient.aether.ui.animation.UIAnimation import net.prismclient.aether.ui.component.util.enums.UIAlignment import net.prismclient.aether.ui.unit.UIUnit -import net.prismclient.aether.ui.util.extensions.align import net.prismclient.aether.ui.util.extensions.lerp import net.prismclient.aether.ui.util.extensions.px import net.prismclient.aether.ui.util.interfaces.UIAnimatable @@ -23,7 +22,7 @@ class UIAnchorPoint : UIAnimatable { infix fun align(alignment: UIAlignment) { x ?: run { x = px(0) } y ?: run { y = px(0) } - align(alignment, x!!, y!!) + net.prismclient.aether.ui.util.extensions.align(alignment, x!!, y!!) } override fun animate( diff --git a/src/main/kotlin/net/prismclient/aether/ui/util/Shorthands.kt b/src/main/kotlin/net/prismclient/aether/ui/util/Shorthands.kt index a58938d..e1e85f7 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/util/Shorthands.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/util/Shorthands.kt @@ -4,7 +4,6 @@ import net.prismclient.aether.ui.animation.UIAnimation import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.dsl.UIComponentDSL import net.prismclient.aether.ui.renderer.UIProvider -import net.prismclient.aether.ui.renderer.impl.background.UIGradientBackground import net.prismclient.aether.ui.renderer.impl.property.UIMargin import net.prismclient.aether.ui.renderer.impl.property.UIPadding import net.prismclient.aether.ui.renderer.impl.property.UIRadius @@ -35,50 +34,47 @@ const val NEAREST = 32 /** * Creates a DSL block from the given [obj] of type [T]. */ -inline fun blockFrom(obj: T, block: Block) = obj.block() +inline fun blockFrom(obj: T, block: T.() -> Unit) = obj.block() /** - * Creates a style sheet [block] from the given style sheet [S], sets the [name] and registers the style. + * Registers a style sheet for the given style, [S]. */ -@JvmName("styleExtension") -inline fun S.style(name: String, block: Block): S = apply { - this.name = name - this.block() - UIProvider.registerStyle(this) +inline fun style(style: S, block: S.() -> Unit) { + style.block() + UIProvider.registerStyle(style) } /** - * Creates a style sheet [block] from the given style sheet [S], sets the [name] and registers the style. + * Registers a style sheet for the given style, [S]. Alternative to [style]. + * + * @see style */ -inline fun style(sheet: S, name: String, block: Block): S { - sheet.name = name - sheet.block() - UIProvider.registerStyle(sheet) - return sheet -} +inline fun styleOf(style: S, block: S.() -> Unit) = style(style, block) + +@JvmName("styleExtension") +inline fun S.style(block: S.() -> Unit) = style(this, block) /** - * Creates a style [block] from the given component [C]. If the style has not been created - * one is automatically allocated with the name of + * Registers a style sheet in the component scope. This is used when no style is provided + * to the component, and instead, the style is provided at the component level. * - * "$C.toString()-sheet" - */ -inline fun , S : UIStyleSheet> C.style(block: Block): C = this.style("$this-sheet", block) + * If the style's name is blank, it will be formatted as "Gen-${component.toString()}". + */ +inline fun , S : UIStyleSheet> C.style(style: S, block: S.() -> Unit): C = apply { + UIComponentDSL.updateState(this) + if (style.name.isEmpty()) style.name = "Gen-$this" + styleOf(style, block) + this.applyStyle(style.name) + UIComponentDSL.restoreState(this) +} /** - * Creates a style [block] from the given component [C]. If the style has not been - * created, one is automatically allocated with the name of [name]. + * Creates a style [block] on a [UIComponent] of the style sheet [S]. */ -inline fun , S : UIStyleSheet> C.style(name: String, block: Block): C = also { - if (!this.hasStyle()) { - this.createsStyle().run { - this.name = name - UIProvider.registerStyle(this) - } - this.applyStyle(name) - } - +inline fun , S : UIStyleSheet> C.style(block: S.() -> Unit): C = apply { + UIComponentDSL.updateState(this) this.style.block() + UIComponentDSL.restoreState(this) } /** @@ -139,7 +135,7 @@ fun marginOf(top: UIUnit, right: UIUnit, bottom: UIUnit, left: UIUnit): UIMargin * * @see ucreate */ -inline fun create(block: Block) { +inline fun create(block: UIComponentDSL.() -> Unit) { UIComponentDSL.begin() UIComponentDSL.block() UIComponentDSL.complete() @@ -148,7 +144,7 @@ inline fun create(block: Block) { /** * Unsafe version of [build]. Does not allocate/deallocate the stacks, thus nothing will be reset */ -inline fun ucreate(block: Block) = UIComponentDSL.block() +inline fun ucreate(block: UIComponentDSL.() -> Unit) = UIComponentDSL.block() /** * Loads the [dependable]. @@ -156,11 +152,12 @@ inline fun ucreate(block: Block) = UIComponentDSL.block() fun include(dependable: UIDependable) = dependable.load() /** - * Creates an animation for the stylesheet [S], with the name [animationName]. - * A block is given to add the keyframes and modify other properties of the [UIAnimation]. - * The animation is automatically registered under the name given. + * Creates an animation where the component is [C], the style of that component is [S], the animation + * name is [animationName], and an instance of the component's style is passed as [style]. A block is + * given to add the keyframes and modify other properties of the [UIAnimation]. The animation is + * automatically registered under the name given. */ -inline fun animationOf(animationName: String, style: S, block: UIAnimation.() -> Unit): UIAnimation { +fun animationOf(animationName: String, style: S, block: UIAnimation.() -> Unit): UIAnimation { val animation = UIAnimation(animationName, style) animation.block() UIProvider.registerAnimation(animationName, animation) @@ -168,6 +165,7 @@ inline fun animationOf(animationName: String, style: S, block } /** - * Creates a [UIGradientBackground], applies the given block to it, and returns it. + * Type alias for a function which has a receiver of [T] and accepts, and returns + * nothing. The block is intended to apply properties to the receiver [T]. */ -inline fun gradient(block: Block): UIGradientBackground = UIGradientBackground().also(block) \ No newline at end of file +typealias Block = T.() -> Unit \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/util/UIAetherLogger.kt b/src/main/kotlin/net/prismclient/aether/ui/util/UIAetherLogger.kt index 46696b9..4509113 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/util/UIAetherLogger.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/util/UIAetherLogger.kt @@ -2,33 +2,14 @@ package net.prismclient.aether.ui.util import net.prismclient.aether.ui.Aether -enum class LogLevel { - DEBUG, - GLOBAL -} - -internal inline fun timed(message: String, level: LogLevel = LogLevel.DEBUG, block: () -> Unit) { - if (level != LogLevel.DEBUG || Aether.debug) { - println("[Aether]: TIMED -> $message") - val start = System.currentTimeMillis() - block() - println("[Aether]: TIMED -> took ${System.currentTimeMillis() - start}ms") - } -} - -internal fun inform(message: String, level: LogLevel = LogLevel.DEBUG) { - if (level != LogLevel.DEBUG || Aether.debug) - println("[Aether]: INFO -> $message") -} - -internal fun debug(message: String) { +internal fun inform(message: String) { if (Aether.debug) - println("[Aether]: DEBUG -> $message") + println("[Aether]: $message") } -internal fun warn(message: String, level: LogLevel = LogLevel.DEBUG) { - if (level != LogLevel.DEBUG || Aether.debug) - println("[Aether]: WARNING -> $message") +internal fun warn(message: String) { + if (Aether.debug) + println("[Aether]: $message") } internal fun error(message: String) = println("[Aether]: ERR -> $message") \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/util/Util.kt b/src/main/kotlin/net/prismclient/aether/ui/util/Util.kt deleted file mode 100644 index 33477f8..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/util/Util.kt +++ /dev/null @@ -1,31 +0,0 @@ -package net.prismclient.aether.ui.util - -import net.prismclient.aether.ui.style.UIStyleSheet - -/** - * Type alias for a function which has a receiver of [T] and accepts, and returns - * nothing. The block is intended to apply properties to the receiver [T]. - */ -typealias Block = T.() -> Unit - -fun T.name(styleName: String): T { - this.name = styleName - return this -} - -fun FloatArray.minX(): Float = this[0] -fun FloatArray.minY(): Float = this[1] -fun FloatArray.maxX(): Float = this[2] -fun FloatArray.maxY(): Float = this[3] - -fun FloatArray.x(): Float = this[0] -fun FloatArray.y(): Float = this[1] -fun FloatArray.width(): Float = this[2] -fun FloatArray.height(): Float = this[3] - -fun FloatArray.red(): Float = this[0] -fun FloatArray.green(): Float = this[1] -fun FloatArray.blue(): Float = this[2] -fun FloatArray.alpha(): Float = this[3] - -fun FloatArray.advance(): Float = this[4] \ No newline at end of file diff --git a/src/main/kotlin/net/prismclient/aether/ui/util/interfaces/UITriConsumer.kt b/src/main/kotlin/net/prismclient/aether/ui/util/interfaces/UITriConsumer.kt new file mode 100644 index 0000000..2365d7d --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/util/interfaces/UITriConsumer.kt @@ -0,0 +1,23 @@ +package net.prismclient.aether.ui.util.interfaces + +import java.util.function.BiConsumer +import java.util.function.Consumer + +/** + * [UITriConsumer] is like a normal [Consumer] or [BiConsumer] except it accepts three + * arguments instead of one or two. + * + * @author sen + * @since 6/5/2022 + */ +fun interface UITriConsumer { + fun accept(a: A, b: B, c: C) + + @JvmDefault + fun andThen(after: UITriConsumer): UITriConsumer? { + return UITriConsumer { a: A, b: B, c: C -> + accept(a, b, c) + after.accept(a, b, c) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/Renderer.kt b/src/test/kotlin/Renderer.kt index 3bde412..e736fd8 100644 --- a/src/test/kotlin/Renderer.kt +++ b/src/test/kotlin/Renderer.kt @@ -30,8 +30,6 @@ object Renderer : UIRenderer { private val ctx: Long = nvgCreate(NVG_ANTIALIAS) private val fillColor: NVGColor = NVGColor.create() private val strokeColor: NVGColor = NVGColor.create() - private val gradient1: NVGColor = NVGColor.create() - private val gradient2: NVGColor = NVGColor.create() private var paint: NVGPaint? = null private var activeColor: Int = 0 @@ -55,8 +53,15 @@ object Renderer : UIRenderer { override fun color(color: Int) { activeColor = color - nvgColor(color, fillColor) - nvgFillColor(ctx, fillColor) + nvgFillColor( + ctx, nvgRGBA( + color.getRed().toByte(), + color.getGreen().toByte(), + color.getBlue().toByte(), + color.getAlpha().toByte(), + this.fillColor + ) + ) } override fun globalAlpha(alpha: Float) = nvgGlobalAlpha(ctx, alpha) @@ -89,7 +94,7 @@ object Renderer : UIRenderer { ctx, (width * contentScale).toInt(), (height * contentScale).toInt(), NVG_IMAGE_REPEATX or NVG_IMAGE_REPEATY ) ?: throw RuntimeException("Failed to create the framebuffer. w: $width, h: $height") val fbo = UIContentFBO( - framebuffer.image(), width, height, width * contentScale, height * contentScale, contentScale + framebuffer.fbo(), width * contentScale, height * contentScale, width, height, contentScale ) framebuffers[fbo] = framebuffer return fbo @@ -109,7 +114,7 @@ object Renderer : UIRenderer { nvgluBindFramebuffer( ctx, framebuffers[fbo] ?: throw NullPointerException("Unable to find the framebuffer $fbo.") ) - GL11.glViewport(0, 0, fbo.scaledWidth.toInt(), fbo.scaledHeight.toInt()) + GL11.glViewport(0, 0, fbo.width.toInt(), fbo.height.toInt()) GL11.glClearColor(0f, 0f, 0f, 0f) GL11.glClear(GL11.GL_COLOR_BUFFER_BIT or GL11.GL_STENCIL_BUFFER_BIT) } @@ -118,6 +123,28 @@ object Renderer : UIRenderer { nvgluBindFramebuffer(ctx, null) } + override fun renderFbo( + fbo: UIContentFBO, + x: Float, + y: Float, + width: Float, + height: Float, + topLeft: Float, + topRight: Float, + bottomRight: Float, + bottomLeft: Float, + ) { + allocPaint() + nvgImagePattern(ctx, x, y, width, height, 0f, framebuffers[fbo]!!.image(), 1f, paint!!) + nvgBeginPath(ctx) + color(-1) + paint!!.innerColor(fillColor) + paint!!.outerColor(fillColor) + nvgRoundedRectVarying(ctx, x, y, width, height, topLeft, topRight, bottomRight, bottomLeft) + nvgFillPaint(ctx, paint!!) + nvgFill(ctx) + } + override fun createImage(imageName: String, data: ByteBuffer, flags: Int): UIImageData { val imageData = UIImageData() val width = IntArray(1) @@ -275,7 +302,13 @@ object Renderer : UIRenderer { override fun strokeWidth(size: Float) = nvgStrokeWidth(ctx, size) override fun strokeColor(color: Int) { - nvgColor(color, strokeColor) + nvgRGBA( + color.getRed().toByte(), + color.getGreen().toByte(), + color.getBlue().toByte(), + color.getAlpha().toByte(), + strokeColor + ) nvgStrokeColor(ctx, strokeColor) } @@ -316,18 +349,22 @@ object Renderer : UIRenderer { override fun linearGradient(x: Float, y: Float, x2: Float, y2: Float, startColor: Int, endColor: Int) { allocPaint() - nvgColor(startColor, gradient1) - nvgColor(endColor, gradient2) - nvgLinearGradient(ctx, x, y, x2, y2, gradient1, gradient2, paint!!) + val color1 = createColor(startColor) + val color2 = createColor(endColor) + nvgLinearGradient(ctx, x, y, x2, y2, color1, color2, paint!!) + color1.free() + color2.free() } override fun radialGradient( x: Float, y: Float, innerRadius: Float, outerRadius: Float, startColor: Int, endColor: Int ) { allocPaint() - nvgColor(startColor, gradient1) - nvgColor(endColor, gradient2) - nvgRadialGradient(ctx, x, y, innerRadius, outerRadius, gradient1, gradient2, paint!!) + val color1 = createColor(startColor) + val color2 = createColor(endColor) + nvgRadialGradient(ctx, x, y, innerRadius, outerRadius, color1, color2, paint!!) + color1.free() + color2.free() } override fun allocPaint() { @@ -343,7 +380,8 @@ object Renderer : UIRenderer { override fun radToDeg(rad: Float): Float = nvgRadToDeg(rad) - private fun nvgColor(color: Int, nvgColor: NVGColor) { + private fun createColor(color: Int): NVGColor { + val nvgColor = NVGColor.calloc() nvgRGBA( color.getRed().toByte(), color.getGreen().toByte(), @@ -351,5 +389,6 @@ object Renderer : UIRenderer { color.getAlpha().toByte(), nvgColor ) + return nvgColor } } \ No newline at end of file diff --git a/src/test/kotlin/Runner.kt b/src/test/kotlin/Runner.kt index 0756640..548805a 100644 --- a/src/test/kotlin/Runner.kt +++ b/src/test/kotlin/Runner.kt @@ -1,7 +1,7 @@ import examples.Animations +import examples.AutoLayouts import examples.Default import examples.PathRendering - import net.prismclient.aether.ui.Aether import net.prismclient.aether.ui.Aether.Properties.updateMouse import net.prismclient.aether.ui.util.input.UIKey @@ -84,11 +84,9 @@ object Runner { } glfwSetFramebufferSizeCallback(window) { _: Long, width: Int, height: Int -> - if (width > 0 && height > 0) { - framebufferWidth = width - framebufferHeight = height - core!!.update(width / contentScaleX, height / contentScaleY, max(contentScaleX, contentScaleY)) - } + framebufferWidth = width + framebufferHeight = height + core!!.update(width / contentScaleX, height / contentScaleY, max(contentScaleX, contentScaleY)) } glfwSetKeyCallback(window) { _: Long, keyCode: Int, scanCode: Int, action: Int, _: Int -> @@ -192,10 +190,11 @@ object Runner { Aether.displayScreen( when (args[0]) { "Animations" -> Animations() + "AutoLayouts" -> AutoLayouts() "PathRendering" -> PathRendering() else -> Default() } ) - } else Aether.displayScreen(Default()) + } } } \ No newline at end of file diff --git a/src/test/kotlin/examples/Animations.kt b/src/test/kotlin/examples/Animations.kt index 4262ebd..1eb9896 100644 --- a/src/test/kotlin/examples/Animations.kt +++ b/src/test/kotlin/examples/Animations.kt @@ -22,15 +22,68 @@ import net.prismclient.aether.ui.util.extensions.px class Animations : UIScreen { override fun build() { create { - button("Hello").style { + include(Generic()) + UIStyleSheet("button").style { + align(UIAlignment.CENTER) + size(400, 40) + background(colorOf(asRGBA(0f, 0f, 0f, 0.3f)), radiusOf(0f)) { + border { + borderWidth = px(10) + borderColor = colorOf(asRGBA(255, 0, 0)) + } + } + font("Montserrat", px(16f), colorOf(-1), left or top) } - verticalList { + animationOf("someAnimation", UIStyleSheet()) { + UIQuart(1000L) repeat { + kf {} + kf { + position(50, 0) + } + kf { + position(0, 0) + } + kf { + position(0, 50) + } + kf { + position(50, 50) + } + } + onCompletion { + UIProvider.dispatchAnimation("someAnimation", it.component) + } + } - }.style { + animationOf("test", UIStyleSheet()) { + kf {} + UILinear(1000L) to { + background { +// backgroundColor = colorOf(asRGBA(0, 0, 255)) + border { + borderColor = colorOf(asRGBA(0f, 1f, 0f)) + borderWidth = px(1) + } + } + } + } + animationOf("move", UIStyleSheet()) { + kf {} + kf { + x = px(50) + } + } + + val l = label("Some text", "button").onMousePressed { + UIProvider.dispatchAnimation("test", it) + println("pressed") + }.style { + x = px(-50) } + UIProvider.dispatchAnimation("move", l) } } } \ No newline at end of file diff --git a/src/test/kotlin/examples/AutoLayouts.kt b/src/test/kotlin/examples/AutoLayouts.kt new file mode 100644 index 0000000..46584d1 --- /dev/null +++ b/src/test/kotlin/examples/AutoLayouts.kt @@ -0,0 +1,110 @@ +package examples + +import examples.deps.Generic +import net.prismclient.aether.ui.component.type.layout.UIFrame +import net.prismclient.aether.ui.component.type.layout.auto.UIAutoLayout +import net.prismclient.aether.ui.component.type.layout.list.UIListLayout +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet +import net.prismclient.aether.ui.component.util.enums.UIAlignment +import net.prismclient.aether.ui.screen.UIScreen +import net.prismclient.aether.ui.util.* +import net.prismclient.aether.ui.util.extensions.asRGBA +import net.prismclient.aether.ui.util.extensions.colorOf +import net.prismclient.aether.ui.util.extensions.px + +/** + * Auto Layouts are a neat feature which is designed to mimic the auto layout + * of Figma. Here is an example screen which utilizes these auto layouts to create + * a list of buttons. + * + * Auto Layouts, in layman terms, are a more advanced version of list layouts. It + * gives spacing and padding properties as well as better alignment support which + * list layouts and other layouts lack. It is one of the few [UIFrame] which are + * designed to be used in mass amounts. + * + * @author sen + * @since 7/3/2022 + * @see UIAutoLayout For more information about the specifics. + */ +class AutoLayouts : UIScreen { + override fun build() { + // Start a `create` block as usual to get a UIComponentDSL block + create { + // Include the UIDependable, so we can use the styles from it + include(Generic()) + + // There are two (suggested) ways to declare Auto Layouts. If intend + // to use auto layout, once (uncommon), you can simply declare the + // layout using `UIComponentDSL.component() + + /* + component(UIAutoLayout(UIListLayout.ListDirection.Horizontal, null)) { + Define properties of the layout, and components... + } + */ + + // Generally you will need to reuse the same layout so instead + // define the layout as a variable instead. + + // ListDirection -> Vertical or Horizontal + val layout = + UIAutoLayout(UIListLayout.ListDirection.Horizontal, null).style(UIContainerSheet(("someStyle"))) { + // Declare the style inline, as we only use it once + background(colorOf(asRGBA(59, 145, 255)), radiusOf(9f)) + } + + // Create a DSL block using this shorthand + blockFrom(layout) { + // Let's say we want an icon on the left, and text on the right + // which is all centered to the middle component. Oh, and let's + // give some padding to the edges, and let it automatically size itself + // (We declare the components below) + componentAlignment = UIAlignment.CENTER // Align the stuff to the center + + // Resize to the size of the layout. (Not) Fun Fact: Setting of the size + // of the component ensures that this it at least that value. + horizontalResizing = UIAutoLayout.ResizingMode.Hug + verticalResizing = UIAutoLayout.ResizingMode.Hug + + // Space the components by 10px... + componentSpacing = px(10) + + // Set the padding to 10 + // Alternatively paddingOf(Unit/Number, Unit/Number, Unit/Number, Unit/Number) + layoutPadding = paddingOf(10) + } + + // Let's make a vertical list, so we can better see what is happening! + // This simply stacks the components vertically. + list(UIListLayout.ListDirection.Vertical, UIListLayout.ListOrder.Forward) { + componentSpacing = px(10) // With a spacing of 10px + + autoLayout(layout) { + // Define your components here! + image("ui", "icon24x") + text("User Interface", "generic-font") + }.style { // Create a style block so we can add a 25px margin at the top + margin { + marginTop = px(25) + } + } + + // Add another + autoLayout(layout) { + image("ui", "icon24x") + text("Some more user interface!!!!", "generic-font") + } + + // Sure, why not another! + autoLayout(layout) { + text("Reversed icons???", "generic-font") + image("ui", "icon24x") + } + }.style(UIContainerSheet("autoLayoutList")) { + // Make the size of this vertical layout + size(400, 500) + // There is no background, as the background was not defined + } + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/examples/Default.kt b/src/test/kotlin/examples/Default.kt index 3e8d79b..1c3612a 100644 --- a/src/test/kotlin/examples/Default.kt +++ b/src/test/kotlin/examples/Default.kt @@ -1,23 +1,62 @@ package examples -import examples.deps.Generic import net.prismclient.aether.ui.animation.ease.impl.UIQuart -import net.prismclient.aether.ui.component.type.UILabel -import net.prismclient.aether.ui.component.type.image.UIImage -import net.prismclient.aether.ui.component.type.image.UIImageSheet -import net.prismclient.aether.ui.component.type.input.button.UIButton -import net.prismclient.aether.ui.component.type.layout.UIAutoLayout -import net.prismclient.aether.ui.component.type.layout.UIListLayout -import net.prismclient.aether.ui.component.type.layout.UIContainerSheet +import net.prismclient.aether.ui.component.type.layout.auto.UIAutoLayout +import net.prismclient.aether.ui.component.type.layout.list.UIListLayout +import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet import net.prismclient.aether.ui.component.util.enums.UIAlignment -import net.prismclient.aether.ui.dsl.UIAssetDSL import net.prismclient.aether.ui.renderer.UIProvider -import net.prismclient.aether.ui.renderer.impl.font.UIFont import net.prismclient.aether.ui.screen.UIScreen import net.prismclient.aether.ui.style.UIStyleSheet import net.prismclient.aether.ui.util.* import net.prismclient.aether.ui.util.extensions.* class Default : UIScreen { - override fun build() {} + override fun build() { + create { + styleOf(UIStyleSheet("btn")) { + size(200, 200) + background(colorOf(1f, 0f, 0f, 0.3f), radiusOf(25f)) + } + + val list = list(UIListLayout.ListDirection.Vertical) { + button("Some text", "btn") + button("Some text", "btn") + button("Some text", "btn") + button("Some text", "btn") + }.style(UIContainerSheet("")) { + control(UIAlignment.CENTER) + size(500, 500) + background(colorOf(-1), radiusOf(8f)) + useFBO = true + verticalScrollbar { + background { + backgroundColor = colorOf(0f, 0f, 0f ,0.3f) + } + y = rel(0.1) + x = rel(1) - px(10) + height = rel(0.8) + width = px(5) + color = colorOf(48, 48, 48) + radius = radiusOf(2.5f) + } + } + + animationOf("someAnimation", UIContainerSheet()) { + kf { + x = px(0) + } + UIQuart(1000L) to { + x = px(200) + } + UIQuart(1000L) to { + x = px(0) + } + onCompletion { + UIProvider.dispatchAnimation("someAnimation", it.component) + } + } +// UIProvider.dispatchAnimation("someAnimation", list) + } + } } \ No newline at end of file diff --git a/src/test/kotlin/examples/components/Chart.kt b/src/test/kotlin/examples/components/Chart.kt index 8e32fc3..e115d40 100644 --- a/src/test/kotlin/examples/components/Chart.kt +++ b/src/test/kotlin/examples/components/Chart.kt @@ -6,12 +6,8 @@ import net.prismclient.aether.ui.style.UIStyleSheet /** * An example component which draws a chart. */ -class Chart() : UIComponent() { +class Chart(style: String?) : UIComponent(style) { override fun renderComponent() { } - - override fun createsStyle(): UIStyleSheet { - TODO("Not yet implemented") - } } \ No newline at end of file diff --git a/src/test/kotlin/examples/deps/Generic.kt b/src/test/kotlin/examples/deps/Generic.kt index 831b3ed..eb8fc8c 100644 --- a/src/test/kotlin/examples/deps/Generic.kt +++ b/src/test/kotlin/examples/deps/Generic.kt @@ -9,7 +9,7 @@ import net.prismclient.aether.ui.util.extensions.colorOf import net.prismclient.aether.ui.util.extensions.px import net.prismclient.aether.ui.util.interfaces.UIDependable import net.prismclient.aether.ui.util.left -import net.prismclient.aether.ui.util.style +import net.prismclient.aether.ui.util.styleOf import net.prismclient.aether.ui.util.top /** @@ -41,17 +41,18 @@ class Generic : UIDependable { // Load some assets into memory. Aether is intended to support mainly JPEG, PNG, and SVG. // Either explicitly state the type // Reference the image with the name "ui" - //UIAssetDSL.svg("ui", "/prism/icons/navbar/ui.svg") + UIAssetDSL.svg("ui", "/prism/icons/navbar/ui.svg") // loadImage() // or let Aether figure it out // assumeLoadImage() // A 24x24 icon - UIImageSheet().style("icon24x") { + styleOf(UIImageSheet("icon24x")) { size(24, 24) } - UIStyleSheet().style("generic-font") { + // A example font + styleOf(UIStyleSheet("generic-font")) { // FontFamily to Montserrat // FontSize -> 16f // FontColor -> -1 = asRGBA(255, 255, 255) (aka white)