diff --git a/src/main/kotlin/net/prismclient/aether/ui/Aether.kt b/src/main/kotlin/net/prismclient/aether/ui/Aether.kt index bc7ee23..a9a0fba 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.container.UIContainer -import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet +import net.prismclient.aether.ui.component.type.layout.UIContainer +import net.prismclient.aether.ui.component.type.layout.UIContainerSheet import net.prismclient.aether.ui.event.input.UIMouseEvent import net.prismclient.aether.ui.renderer.UIProvider import net.prismclient.aether.ui.renderer.UIRenderer @@ -33,9 +33,10 @@ 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) { @@ -108,12 +109,14 @@ 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() } } } @@ -123,7 +126,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]. */ - fun mouseMoved(mouseX: Float, mouseY: Float) { + open 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) @@ -139,10 +142,10 @@ open class Aether(renderer: UIRenderer) { * * @see mouseScrolled */ - fun mouseChanged(mouseButton: Int, isRelease: Boolean) { + open fun mouseChanged(mouseButton: Int, isRelease: Boolean) { if (isRelease) { mouseReleasedListeners?.forEach { it.value.run() } - components?.forEach { it.mouseReleased(mouseX, mouseY) } + components?.forEach { it.mouseReleased(it.getMouseX(), it.getMouseY()) } return } @@ -182,7 +185,7 @@ open class Aether(renderer: UIRenderer) { return if (component != null) { component.focus() - component.mousePressed(UIMouseEvent(mouseX, mouseY, mouseButton, clickCount)) + component.mousePressed(UIMouseEvent(component.getMouseX(), component.getMouseY(), mouseButton, clickCount)) true } else false } @@ -211,7 +214,7 @@ open class Aether(renderer: UIRenderer) { i++ } c?.focus() - c?.mousePressed(UIMouseEvent(mouseX, mouseY, mouseButton, clickCount)) + c?.mousePressed(UIMouseEvent(c.getMouseX(), c.getMouseY(), mouseButton, clickCount)) } /** @@ -220,7 +223,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... */ - fun keyPressed(character: Char) { + open fun keyPressed(character: Char) { keyPressListeners?.forEach { it.value.accept(character) } (focusedComponent as? UIComponent<*>)?.keyPressed(character) } @@ -230,6 +233,7 @@ 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) } @@ -241,20 +245,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). @@ -265,42 +269,36 @@ 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 @@ -308,49 +306,42 @@ 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 @@ -502,13 +493,7 @@ open class Aether(renderer: UIRenderer) { * Focuses the component. Please use [UIComponent.focus] instead. */ @JvmStatic - 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.") - } + fun focus(component: T) where T : UIComponent<*>, T : UIFocusable { focusedComponent = component } @@ -548,7 +533,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 new file mode 100644 index 0000000..9218a08 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/Timings.kt @@ -0,0 +1,83 @@ +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 deleted file mode 100644 index 0751f94..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/callback/UICoreCallback.kt +++ /dev/null @@ -1,17 +0,0 @@ -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 c721c96..c6b0a79 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.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.component.type.layout.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(style: String?) { +abstract class UIComponent { /** * The style of the component. */ @@ -193,10 +193,6 @@ abstract class UIComponent(style: String?) { 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 @@ -208,8 +204,7 @@ abstract class UIComponent(style: String?) { * @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. @@ -246,8 +241,10 @@ abstract class UIComponent(style: String?) { * might request for this method to be invoked. */ open fun update() { - if (!this::style.isInitialized) - throw UninitializedStyleSheetException(this) + if (!this::style.isInitialized) { + println("creating style...") + style = createsStyle() + } calculateBounds() // Update the size, then the anchor, and then the position @@ -336,8 +333,7 @@ abstract class UIComponent(style: String?) { if (animations != null) { animations!!.forEach { it.value.update() } animations!!.entries.removeIf { it.value.isCompleted } - if (animations!!.isEmpty()) - animations = null + if (animations!!.isEmpty()) animations = null } } @@ -365,7 +361,17 @@ abstract class UIComponent(style: String?) { */ abstract fun renderComponent() - /** Input **/ + /** + * 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 -- // /** * Invoked when the mouse moves @@ -431,7 +437,7 @@ abstract class UIComponent(style: String?) { mouseScrollListeners?.forEach { it.value.accept(this, scrollAmount) } } - /** Event **/ + // -- Event -- // /** * Invoked once on the initialization of the component. @@ -601,36 +607,28 @@ abstract class UIComponent(style: String?) { open fun getMouseY(): Float = Aether.mouseY - getParentYOffset() /** - * Returns the offset of the parent on the x-axis. It incorporates for [UIFrame] and [UIContainer] scroll offsets. + * 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. */ - 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 + 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 } else 0f - } + } else 0f /** - * Returns the offset of the parent on the y-axis. It incorporates for [UIFrame] and [UIContainer] scrolling offsets + * 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. */ - 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 + 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 } else 0f - } + } else 0f /** * Shorthand for calculating the x or width of this component @@ -658,7 +656,7 @@ abstract class UIComponent(style: String?) { open fun focus() { if (this is UIFocusable) { Aether.focus(this) - focusListeners?.forEach { it.value.accept(this as UIComponent, true) } + focusListeners?.forEach { it.value.accept(this, 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 0d224f0..b0dd7de 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,6 +2,7 @@ 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 @@ -57,8 +58,20 @@ 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 dfa4489..fac02bd 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,10 +7,12 @@ import net.prismclient.aether.ui.style.UIStyleSheet * [UILabel] is a component which draws a label, or string on screen. * * @author sen - * @since 5/15/2022 + * @since 1.0 */ -class UILabel(var text: String, style: String?) : UIComponent(style) { +class UILabel(var text: String) : UIComponent() { 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 deleted file mode 100644 index 699c807..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorCursor.kt +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 5c056bb..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorPicker.kt +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 7b82376..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/color/UIColorSwatchSheet.kt +++ /dev/null @@ -1,16 +0,0 @@ -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 85131de..3421eda 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,16 +4,21 @@ 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 implementation for rendering images on screen. It accepts` - * an image + * [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. * * @author sen - * @since 5/20/2022 + * @since 1.0 */ -class UIImage(name: String, style: String?) : UIComponent(style) { +class UIImage(name: String) : UIComponent() { var image: String = name set(value) { field = value @@ -24,12 +29,9 @@ class UIImage(name: String, style: String?) : UIComponent(style) { /** * Loads am image or svg from the specified location with a given name */ - constructor(name: String, location: String, style: String?) : this( - name, - style - ) { - UIAssetDSL.image(name, location) - } + constructor(name: String, location: String) : this( + name + ) { UIAssetDSL.image(name, location) } init { activeImage = UIProvider.getImage(image) @@ -37,7 +39,7 @@ class UIImage(name: String, style: String?) : UIComponent(style) { override fun renderComponent() { renderer { - color(style.imageColor) + color(style.imageColor?.rgba ?: -1) renderImage( image, x, y, width, height, style.imageRadius?.topLeft ?: 0f, @@ -47,4 +49,24 @@ class UIImage(name: String, style: String?) : UIComponent(style) { ) } } + + 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 deleted file mode 100644 index e63bada..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/image/UIImageSheet.kt +++ /dev/null @@ -1,28 +0,0 @@ -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 0d522f9..0c0082a 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,14 +4,15 @@ import net.prismclient.aether.ui.component.UIComponent import net.prismclient.aether.ui.style.UIStyleSheet /** - * [UIButton] is a simple class, which is used to create a button. + * [UIButton] is the default implementation of [UIComponent]. It renders the given text to the font. * * @author sen - * @since 5/16/2022 - * @param T The stylesheet (used for inheritance) leave as UIStyleSheet. + * @since 1.0 */ -open class UIButton(open var text: String, style: String?) : UIComponent(style) { +open class UIButton(open var text: String) : UIComponent() { 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 deleted file mode 100644 index 61193d6..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UICheckbox.kt +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index c09b6b0..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIIconButton.kt +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index a8f64f4..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UIImageButton.kt +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index 00d91cc..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/button/UISelectableButton.kt +++ /dev/null @@ -1,37 +0,0 @@ -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 b1737bf..40dfe84 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,6 +2,9 @@ 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 @@ -17,16 +20,15 @@ import kotlin.math.roundToInt * @see UISliderShape */ open class UISlider( - value: Double, var range: ClosedFloatingPointRange, var step: Double, style: String? -) : UIComponent(style) { + value: Double, var range: ClosedFloatingPointRange, var step: Double +) : UIComponent() { /** * 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) } @@ -110,4 +112,20 @@ 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 deleted file mode 100644 index a3efd8e..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/slider/UISliderSheet.kt +++ /dev/null @@ -1,28 +0,0 @@ -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 1f85f76..020a740 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,12 +1,19 @@ 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 @@ -20,12 +27,11 @@ import java.util.function.Consumer * length of the overall text. * * @author sen - * @since 6/6/2022 - * @see UITextField.filter Pre-made text filters. + * @since 1.0 + * @see UITextField.Filters pre-made text filters. */ -open class UITextField(text: String, var placeholder: String? = null, var filter: TextFilter, style: String?) : - UIButton(text, style), UIFocusable { - override var text: String = text +open class UITextField(text: String, var placeholder: String? = null, var filter: TextFilter) : UIComponent(), UIFocusable { + var text: String = text set(value) { field = value textChangedListener?.forEach { it.value.accept(this) } @@ -46,7 +52,7 @@ open class UITextField(text: String, var placeholder: String? = null, var filter /** Blink **/ protected var timeSinceLastBlink: Long = 0L - protected var blink: Boolean = false + protected var blink: Boolean = true init { Aether.onModifierKeyChange(this.toString()) { key, value -> @@ -111,7 +117,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 (timeSinceLastBlink + style.blinkRate <= System.currentTimeMillis()) { + if (style.blinkRate > 0 && (timeSinceLastBlink + style.blinkRate <= System.currentTimeMillis())) { blink = !blink timeSinceLastBlink = System.currentTimeMillis() } @@ -231,6 +237,8 @@ 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, @@ -259,4 +267,38 @@ 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 deleted file mode 100644 index 0ae3407..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/input/textfield/UITextFieldSheet.kt +++ /dev/null @@ -1,48 +0,0 @@ -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/auto/UIAutoLayout.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIAutoLayout.kt similarity index 84% rename from src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayout.kt rename to src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIAutoLayout.kt index f29b036..a731d21 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayout.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIAutoLayout.kt @@ -1,13 +1,12 @@ -package net.prismclient.aether.ui.component.type.layout.auto +package net.prismclient.aether.ui.component.type.layout 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 @@ -27,10 +26,8 @@ import net.prismclient.aether.ui.util.interfaces.UICopy * @author sen * @since 1.1 */ -class UIAutoLayout @JvmOverloads constructor( - listDirection: ListDirection = ListDirection.Horizontal, - style: String? -) : UIListLayout(listDirection, ListOrder.Forward, style), UICopy { +class UIAutoLayout @JvmOverloads constructor(listDirection: ListDirection = ListDirection.Horizontal) : + UIListLayout(listDirection, ListOrder.Forward), 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. @@ -67,6 +64,14 @@ class UIAutoLayout @JvmOverloads constructor( */ 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 @@ -82,28 +87,27 @@ class UIAutoLayout @JvmOverloads constructor( var h = 0f for (i in components.indices) { + val component = components[i] if (horizontalResizing == ResizingMode.Hug) { w = if (listDirection == ListDirection.Horizontal) { - w + components[i].relWidth + if (i < components.size - 1) spacing else 0f + w + component.relWidth + component.marginLeft + component.marginRight + if (i < components.size - 1) spacing else 0f } else { - components[i].relWidth.coerceAtLeast(w) + (component.relWidth + component.marginLeft + component.marginRight).coerceAtLeast(w) } } if (verticalResizing == ResizingMode.Hug) { h = if (listDirection == ListDirection.Vertical) { - h + components[i].relHeight + if (i < components.size - 1) spacing else 0f + h + component.relHeight + component.marginTop + component.marginBottom + if (i < components.size - 1) spacing else 0f } else { - components[i].relHeight.coerceAtLeast(h) + (component.relHeight + component.marginTop + component.marginBottom).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() @@ -140,7 +144,7 @@ class UIAutoLayout @JvmOverloads constructor( 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) { @@ -148,7 +152,7 @@ class UIAutoLayout @JvmOverloads constructor( 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 } @@ -164,7 +168,7 @@ class UIAutoLayout @JvmOverloads constructor( /** * Copy the properties of this layout to a new one (excluding components). */ - override fun copy(): UIAutoLayout = UIAutoLayout(listDirection, style.name).also { + override fun copy(): UIAutoLayout = UIAutoLayout(listDirection).also { // UIAutoLayout properties it.horizontalResizing = horizontalResizing it.verticalResizing = verticalResizing 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 new file mode 100644 index 0000000..5459dd0 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIContainer.kt @@ -0,0 +1,275 @@ +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 73d348d..6e30a6e 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,12 +2,13 @@ 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 /** @@ -27,7 +28,7 @@ import net.prismclient.aether.ui.util.warn * @author sen * @since 1.0 */ -abstract class UIFrame(style: String?) : UIComponent(style), UIFocusable { +abstract class UIFrame : UIComponent(), UIFocusable { /** * The components of this frame. */ @@ -59,7 +60,16 @@ abstract class UIFrame(style: String?) : UIComponent(style) * frame has been updated, but prior to the first render. */ open fun updateFBO() { - if (style.useFBO) fbo = Aether.renderer.createFBO(relWidth, relHeight) + // 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) + } + } } /** @@ -93,11 +103,10 @@ abstract class UIFrame(style: String?) : UIComponent(style) components.forEach(UIComponent<*>::render) } } - requiresUpdate = false } + requiresUpdate = false } - override fun render() { updateAnimation() style.background?.render() @@ -106,17 +115,12 @@ abstract class UIFrame(style: String?) : UIComponent(style) 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 - ) + renderer { + path { + imagePattern(fbo!!.imagePattern, relX, relY, relWidth, relHeight, 0f, 1f) + rect(relX, relY, relWidth, relHeight) + }.fillPaint() + } } else { if (style.clipContent) UIRendererDSL.scissor(relX, relY, relWidth, relHeight) { components.forEach(UIComponent<*>::render) @@ -162,4 +166,53 @@ abstract class UIFrame(style: String?) : UIComponent(style) 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 new file mode 100644 index 0000000..2c39452 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIGridLayout.kt @@ -0,0 +1,9 @@ +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/list/UIListLayout.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIListLayout.kt similarity index 88% rename from src/main/kotlin/net/prismclient/aether/ui/component/type/layout/list/UIListLayout.kt rename to src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIListLayout.kt index 692b393..1b6a63f 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/list/UIListLayout.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UIListLayout.kt @@ -1,9 +1,7 @@ -package net.prismclient.aether.ui.component.type.layout.list +package net.prismclient.aether.ui.component.type.layout -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.component.type.layout.UIListLayout.ListOrder.Backwards +import net.prismclient.aether.ui.component.type.layout.UIListLayout.ListOrder.Forward import net.prismclient.aether.ui.unit.UIUnit /** @@ -16,8 +14,7 @@ import net.prismclient.aether.ui.unit.UIUnit open class UIListLayout @JvmOverloads constructor( var listDirection: ListDirection = ListDirection.Vertical, var listOrder: ListOrder = Forward, - style: String? -) : UIContainer(style) { +) : UIContainer() { /** * The spacing between each component in the layout. */ diff --git a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/tab/UITab.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UITab.kt similarity index 53% rename from src/main/kotlin/net/prismclient/aether/ui/component/type/layout/tab/UITab.kt rename to src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UITab.kt index 2b1a715..c47a21e 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/tab/UITab.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/UITab.kt @@ -1,3 +1,3 @@ -package net.prismclient.aether.ui.component.type.layout.tab +package net.prismclient.aether.ui.component.type.layout 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/layout/auto/UIAutoLayoutSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayoutSheet.kt deleted file mode 100644 index 7197cc7..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/auto/UIAutoLayoutSheet.kt +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 315d789..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/container/UIContainer.kt +++ /dev/null @@ -1,173 +0,0 @@ -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 deleted file mode 100644 index 624d639..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/grid/UIGridLayout.kt +++ /dev/null @@ -1,155 +0,0 @@ -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/styles/UIContainerSheet.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIContainerSheet.kt deleted file mode 100644 index 210ac5e..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIContainerSheet.kt +++ /dev/null @@ -1,102 +0,0 @@ -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 deleted file mode 100644 index ff31030..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/layout/styles/UIFrameSheet.kt +++ /dev/null @@ -1,53 +0,0 @@ -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/other/progress/UIProgress.kt b/src/main/kotlin/net/prismclient/aether/ui/component/type/other/UIProgress.kt similarity index 58% rename from src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgress.kt rename to src/main/kotlin/net/prismclient/aether/ui/component/type/other/UIProgress.kt index 583f357..69bd0d2 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgress.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/component/type/other/UIProgress.kt @@ -1,7 +1,10 @@ -package net.prismclient.aether.ui.component.type.other.progress +package net.prismclient.aether.ui.component.type.other 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 @@ -12,14 +15,27 @@ import net.prismclient.aether.ui.util.extensions.renderer * To configure the color see [UIProgressSheet.progressColor]. * * @author sen - * @since 6/23/2022 + * @since 1.0 */ -class UIProgress @JvmOverloads constructor(var progress: Float = 0f, style: String?) : - UIComponent(style) { +class UIProgress @JvmOverloads constructor(var progress: Float = 0f) : UIComponent() { 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 deleted file mode 100644 index 44b73e9..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/type/other/progress/UIProgressSheet.kt +++ /dev/null @@ -1,21 +0,0 @@ -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 62f9ea8..12aaeb6 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 deleted file mode 100644 index d1037de..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/component/util/enums/UIOverflow.kt +++ /dev/null @@ -1,8 +0,0 @@ -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 73a338b..83125fe 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 { - warn("Failed to bulk load [$folderLocation] as the file was null.") + error("Failed to bulk load [$folderLocation] as the file was null.") return 0 } return internalBulkLoad(File(file.toURI()), deep, appendedString, imageFlags, svgScale).also { - warn("Bulk loaded $it files.") + inform("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 f4649bd..ded9ef4 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIComponentDSL.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIComponentDSL.kt @@ -7,15 +7,14 @@ 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.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.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.dsl.UIComponentDSL.activeStyle -import net.prismclient.aether.ui.style.UIStyleSheet +import net.prismclient.aether.ui.util.Block import net.prismclient.aether.ui.util.interfaces.UIDependable import java.util.* @@ -136,8 +135,9 @@ object UIComponentDSL { * * @return T The component */ - inline fun > component(component: T, block: T.() -> Unit): T { + inline fun > component(component: T, style: String?, block: Block): 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: C.() -> Unit) { + inline fun , T : UIComponent<*>> controller(controller: C, block: Block) { 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: UIComponentDSL.() -> Unit): UIComponentDSL { + inline fun ignore(block: Block): 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: UIComponentDSL.() -> Unit) { + inline fun style(styleName: String, block: Block) { // 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,8 +187,7 @@ 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() @@ -207,8 +206,8 @@ object UIComponentDSL { * @see label */ @JvmOverloads - inline fun text(text: String, style: String? = activeStyle, block: UILabel.() -> Unit = {}) = - component(UILabel(text, style), block) + inline fun text(text: String, style: String? = activeStyle, block: Block = {}) = + component(UILabel(text), style, block) /** * An alternative to [text]. Creates a [UILabel] with the provided [text]. @@ -216,27 +215,14 @@ object UIComponentDSL { * @see text */ @JvmOverloads - inline fun label(text: String, style: String? = activeStyle, block: UILabel.() -> Unit = {}) = - component(UILabel(text, style), block) + inline fun label(text: String, style: String? = activeStyle, block: Block = {}) = + component(UILabel(text), style, block) /** * Creates a [UIButton] with the provided [text], like a label. */ - 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) + inline fun button(text: String, style: String? = activeStyle, block: Block = {}) = + component(UIButton(text), style, block) /** * Creates a [UISlider] with the given [value] which stays within the [range] and steps by the @@ -248,22 +234,22 @@ object UIComponentDSL { range: ClosedFloatingPointRange, step: Number, style: String? = activeStyle, - block: UISlider.() -> Unit = {} - ) = component(UISlider(value.toDouble(), range, step.toDouble(), style), block) + block: Block = {} + ) = 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: UIImage.() -> Unit = {}) = - component(UIImage(imageName, style), block) + inline fun image(imageName: String, style: String? = activeStyle, block: Block = {}) = + 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: UIContainer.() -> Unit = {}) = - component(UIContainer(style), block) + inline fun container(style: String? = activeStyle, block: Block> = {}) = + component(UIContainer(), style, block) /** * Creates a [UIListLayout] with the given [listDirection], which defines the direction that it @@ -275,14 +261,50 @@ object UIComponentDSL { listDirection: UIListLayout.ListDirection, listOrder: UIListLayout.ListOrder = UIListLayout.ListOrder.Forward, style: String? = activeStyle, - block: UIListLayout.() -> Unit = {} - ) = component(UIListLayout(listDirection, listOrder, style), block) + 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) /** * Creates a copy of the given layout and creates a normal block of [UIAutoLayout] where * components can be defined. */ - inline fun autoLayout(layout: UIAutoLayout, block: UIAutoLayout.() -> Unit) = component(layout.copy(), block) + @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) /** * Creates a [UISelectableController] which is a controller that has a single selected @@ -291,6 +313,7 @@ object UIComponentDSL { * @see controller * @see UISelectableController */ - inline fun > selectable(block: UISelectableController.() -> Unit) = + @JvmOverloads + inline fun > selectable(block: Block> = {}) = 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 b49d5aa..3d33bfb 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIPathDSL.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIPathDSL.kt @@ -2,6 +2,8 @@ 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. @@ -149,6 +151,21 @@ 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. */ @@ -216,8 +233,8 @@ object UIPathDSL { * @see See NanoVG Composite paths */ @JvmStatic - inline fun hole(block: UIPathDSL.() -> Unit): UIPathDSL { - block() + inline fun hole(block: Block): UIPathDSL { + 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 926acc8..cf0f1c0 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/dsl/UIRendererDSL.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/dsl/UIRendererDSL.kt @@ -8,8 +8,7 @@ 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.Block -import net.prismclient.aether.ui.util.UIColor +import net.prismclient.aether.ui.util.* /** * [UIRendererDSL] wraps the [UIRenderer] class to minimize the amount of calls @@ -77,7 +76,7 @@ object UIRendererDSL { */ @JvmStatic fun color(color: Int) { - UIRendererDSL.activeColor = color + activeColor = color renderer.color(color) } @@ -154,13 +153,13 @@ object UIRendererDSL { * @see fontBounds */ @JvmStatic - fun fontWidth(): Float = fontBounds()[2] - fontBounds()[0] + fun fontWidth(): Float = fontBounds().maxX() - fontBounds().minX() /** * Returns the height of the most recent text render call. */ @JvmStatic - fun fontHeight(): Float = fontBounds()[3] - fontBounds()[1] + fun fontHeight(): Float = fontBounds().maxY() - fontBounds().minY() /** * Returns the ascender of the most recent text render call. @@ -176,6 +175,9 @@ 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, @@ -399,7 +401,7 @@ object UIRendererDSL { @JvmStatic inline fun UIContentFBO.renderToFramebuffer(block: Block): UIRendererDSL { renderer.bindFBO(this) - beginFrame(this.scaledWidth, this.scaledHeight, this.contentScale) + beginFrame(this.width, this.height, 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 1b94e72..5727146 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/renderer/UIProvider.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/renderer/UIProvider.kt @@ -48,10 +48,9 @@ object UIProvider { * Returns the name of the images given the [UIImageData] */ fun getImageName(imageData: UIImageData): String? { - for (image in images) { - if (image.value == imageData) { - return image.key - } + for ((name, image) in images) { + if (image == imageData) + return name } 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 a7ba8af..cc7ac79 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/renderer/UIRenderer.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/renderer/UIRenderer.kt @@ -1,7 +1,6 @@ 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 @@ -139,18 +138,6 @@ 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 985502f..6097eb6 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,18 +2,19 @@ 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 4/26/2022 + * @since 41.0 */ class UIGradientBackground : UIBackground() { - var gradientStartColor = 0 - var gradientEndColor = 0 + var gradientStartColor: UIColor? = null + var gradientEndColor: UIColor? = null var gradientX: UIUnit? = null var gradientY: UIUnit? = null var gradientWidth: UIUnit? = null @@ -44,15 +45,18 @@ class UIGradientBackground : UIBackground() { component?.relHeight ?: 0f, true ) - gradientWidthCache = + gradientWidthCache = gradientXCache + calculate(gradientWidth, component, component?.relWidth ?: 0f, component?.relHeight ?: 0f, false) - gradientHeightCache = + gradientHeightCache = gradientYCache + calculate(gradientHeight, component, component?.relWidth ?: 0f, component?.relHeight ?: 0f, true) } override fun render() { renderer { - TODO("Gradient suport") + path { + linearGradient(gradientXCache, gradientYCache, gradientWidthCache, gradientHeightCache, gradientStartColor?.rgba ?: 0, gradientEndColor?.rgba ?: 0) + rect(cachedX, cachedY, cachedWidth, cachedHeight, radius) + }.fillPaint() } } 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 a2e837d..ee79d69 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.container.UIContainer -import net.prismclient.aether.ui.component.type.layout.styles.UIContainerSheet +import net.prismclient.aether.ui.component.type.layout.UIContainer +import net.prismclient.aether.ui.component.type.layout.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,12 +121,25 @@ class UIScrollbar(val type: Scrollbar) : UIColoredShape() { val mouseX = component!!.getMouseX() val mouseY = component!!.getMouseY() - if (mouseX >= x && mouseX <= x + w && mouseY >= y && mouseY <= y + h) { + 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) { selected = true mouseOffset = if (type == Scrollbar.Vertical) mouseY - y else mouseX - x - return true + } 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 false + + return true } 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 eb9ed72..353084a 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.styles.UIFrameSheet +import net.prismclient.aether.ui.component.type.layout.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,6 +19,7 @@ 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 @@ -39,11 +40,13 @@ import net.prismclient.aether.ui.util.interfaces.UICopy * 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 @JvmOverloads constructor(var name: String = "") : UICopy, - UIAnimatable { +open class UIStyleSheet() : UICopy, UIAnimatable { + var name: String = "" + /** * When true, the property will not be cleared when Aether cleans up styles. */ @@ -82,10 +85,12 @@ open class UIStyleSheet @JvmOverloads constructor(var name: String = "") : UICop ) { val component = animation.component - 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 (!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) + } if (previous?.background != null || current?.background != null) { background = background ?: UIBackground() @@ -344,7 +349,7 @@ open class UIStyleSheet @JvmOverloads constructor(var name: String = "") : UICop this.marginLeft = marginLeft } - override fun copy(): UIStyleSheet = UIStyleSheet(name).apply(this) + override fun copy(): UIStyleSheet = UIStyleSheet().name(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 db2bfe2..1aa6ca5 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,6 +3,7 @@ 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 @@ -22,7 +23,7 @@ class UIAnchorPoint : UIAnimatable { infix fun align(alignment: UIAlignment) { x ?: run { x = px(0) } y ?: run { y = px(0) } - net.prismclient.aether.ui.util.extensions.align(alignment, x!!, y!!) + 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 e1e85f7..a58938d 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/util/Shorthands.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/util/Shorthands.kt @@ -4,6 +4,7 @@ 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 @@ -34,47 +35,50 @@ const val NEAREST = 32 /** * Creates a DSL block from the given [obj] of type [T]. */ -inline fun blockFrom(obj: T, block: T.() -> Unit) = obj.block() +inline fun blockFrom(obj: T, block: Block) = obj.block() /** - * Registers a style sheet for the given style, [S]. + * Creates a style sheet [block] from the given style sheet [S], sets the [name] and registers the style. */ -inline fun style(style: S, block: S.() -> Unit) { - style.block() - UIProvider.registerStyle(style) +@JvmName("styleExtension") +inline fun S.style(name: String, block: Block): S = apply { + this.name = name + this.block() + UIProvider.registerStyle(this) } /** - * Registers a style sheet for the given style, [S]. Alternative to [style]. - * - * @see style + * Creates a style sheet [block] from the given style sheet [S], sets the [name] and registers the style. */ -inline fun styleOf(style: S, block: S.() -> Unit) = style(style, block) - -@JvmName("styleExtension") -inline fun S.style(block: S.() -> Unit) = style(this, block) +inline fun style(sheet: S, name: String, block: Block): S { + sheet.name = name + sheet.block() + UIProvider.registerStyle(sheet) + return sheet +} /** - * 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. + * Creates a style [block] from the given component [C]. If the style has not been created + * one is automatically allocated with the name of * - * 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) -} + * "$C.toString()-sheet" + */ +inline fun , S : UIStyleSheet> C.style(block: Block): C = this.style("$this-sheet", block) /** - * Creates a style [block] on a [UIComponent] of the style sheet [S]. + * 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]. */ -inline fun , S : UIStyleSheet> C.style(block: S.() -> Unit): C = apply { - UIComponentDSL.updateState(this) +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) + } + this.style.block() - UIComponentDSL.restoreState(this) } /** @@ -135,7 +139,7 @@ fun marginOf(top: UIUnit, right: UIUnit, bottom: UIUnit, left: UIUnit): UIMargin * * @see ucreate */ -inline fun create(block: UIComponentDSL.() -> Unit) { +inline fun create(block: Block) { UIComponentDSL.begin() UIComponentDSL.block() UIComponentDSL.complete() @@ -144,7 +148,7 @@ inline fun create(block: UIComponentDSL.() -> Unit) { /** * Unsafe version of [build]. Does not allocate/deallocate the stacks, thus nothing will be reset */ -inline fun ucreate(block: UIComponentDSL.() -> Unit) = UIComponentDSL.block() +inline fun ucreate(block: Block) = UIComponentDSL.block() /** * Loads the [dependable]. @@ -152,12 +156,11 @@ inline fun ucreate(block: UIComponentDSL.() -> Unit) = UIComponentDSL.block() fun include(dependable: UIDependable) = dependable.load() /** - * 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. + * 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. */ -fun animationOf(animationName: String, style: S, block: UIAnimation.() -> Unit): UIAnimation { +inline fun animationOf(animationName: String, style: S, block: UIAnimation.() -> Unit): UIAnimation { val animation = UIAnimation(animationName, style) animation.block() UIProvider.registerAnimation(animationName, animation) @@ -165,7 +168,6 @@ fun animationOf(animationName: String, style: S, block: UIAni } /** - * 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]. + * Creates a [UIGradientBackground], applies the given block to it, and returns it. */ -typealias Block = T.() -> Unit \ No newline at end of file +inline fun gradient(block: Block): UIGradientBackground = UIGradientBackground().also(block) \ 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 4509113..46696b9 100644 --- a/src/main/kotlin/net/prismclient/aether/ui/util/UIAetherLogger.kt +++ b/src/main/kotlin/net/prismclient/aether/ui/util/UIAetherLogger.kt @@ -2,14 +2,33 @@ package net.prismclient.aether.ui.util import net.prismclient.aether.ui.Aether -internal fun inform(message: String) { - if (Aether.debug) - println("[Aether]: $message") +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 warn(message: String) { +internal fun inform(message: String, level: LogLevel = LogLevel.DEBUG) { + if (level != LogLevel.DEBUG || Aether.debug) + println("[Aether]: INFO -> $message") +} + +internal fun debug(message: String) { if (Aether.debug) - println("[Aether]: $message") + println("[Aether]: DEBUG -> $message") +} + +internal fun warn(message: String, level: LogLevel = LogLevel.DEBUG) { + if (level != LogLevel.DEBUG || Aether.debug) + println("[Aether]: WARNING -> $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 new file mode 100644 index 0000000..33477f8 --- /dev/null +++ b/src/main/kotlin/net/prismclient/aether/ui/util/Util.kt @@ -0,0 +1,31 @@ +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 deleted file mode 100644 index 2365d7d..0000000 --- a/src/main/kotlin/net/prismclient/aether/ui/util/interfaces/UITriConsumer.kt +++ /dev/null @@ -1,23 +0,0 @@ -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 e736fd8..3bde412 100644 --- a/src/test/kotlin/Renderer.kt +++ b/src/test/kotlin/Renderer.kt @@ -30,6 +30,8 @@ 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 @@ -53,15 +55,8 @@ object Renderer : UIRenderer { override fun color(color: Int) { activeColor = color - nvgFillColor( - ctx, nvgRGBA( - color.getRed().toByte(), - color.getGreen().toByte(), - color.getBlue().toByte(), - color.getAlpha().toByte(), - this.fillColor - ) - ) + nvgColor(color, fillColor) + nvgFillColor(ctx, fillColor) } override fun globalAlpha(alpha: Float) = nvgGlobalAlpha(ctx, alpha) @@ -94,7 +89,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.fbo(), width * contentScale, height * contentScale, width, height, contentScale + framebuffer.image(), width, height, width * contentScale, height * contentScale, contentScale ) framebuffers[fbo] = framebuffer return fbo @@ -114,7 +109,7 @@ object Renderer : UIRenderer { nvgluBindFramebuffer( ctx, framebuffers[fbo] ?: throw NullPointerException("Unable to find the framebuffer $fbo.") ) - GL11.glViewport(0, 0, fbo.width.toInt(), fbo.height.toInt()) + GL11.glViewport(0, 0, fbo.scaledWidth.toInt(), fbo.scaledHeight.toInt()) GL11.glClearColor(0f, 0f, 0f, 0f) GL11.glClear(GL11.GL_COLOR_BUFFER_BIT or GL11.GL_STENCIL_BUFFER_BIT) } @@ -123,28 +118,6 @@ 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) @@ -302,13 +275,7 @@ object Renderer : UIRenderer { override fun strokeWidth(size: Float) = nvgStrokeWidth(ctx, size) override fun strokeColor(color: Int) { - nvgRGBA( - color.getRed().toByte(), - color.getGreen().toByte(), - color.getBlue().toByte(), - color.getAlpha().toByte(), - strokeColor - ) + nvgColor(color, strokeColor) nvgStrokeColor(ctx, strokeColor) } @@ -349,22 +316,18 @@ object Renderer : UIRenderer { override fun linearGradient(x: Float, y: Float, x2: Float, y2: Float, startColor: Int, endColor: Int) { allocPaint() - val color1 = createColor(startColor) - val color2 = createColor(endColor) - nvgLinearGradient(ctx, x, y, x2, y2, color1, color2, paint!!) - color1.free() - color2.free() + nvgColor(startColor, gradient1) + nvgColor(endColor, gradient2) + nvgLinearGradient(ctx, x, y, x2, y2, gradient1, gradient2, paint!!) } override fun radialGradient( x: Float, y: Float, innerRadius: Float, outerRadius: Float, startColor: Int, endColor: Int ) { allocPaint() - val color1 = createColor(startColor) - val color2 = createColor(endColor) - nvgRadialGradient(ctx, x, y, innerRadius, outerRadius, color1, color2, paint!!) - color1.free() - color2.free() + nvgColor(startColor, gradient1) + nvgColor(endColor, gradient2) + nvgRadialGradient(ctx, x, y, innerRadius, outerRadius, gradient1, gradient2, paint!!) } override fun allocPaint() { @@ -380,8 +343,7 @@ object Renderer : UIRenderer { override fun radToDeg(rad: Float): Float = nvgRadToDeg(rad) - private fun createColor(color: Int): NVGColor { - val nvgColor = NVGColor.calloc() + private fun nvgColor(color: Int, nvgColor: NVGColor) { nvgRGBA( color.getRed().toByte(), color.getGreen().toByte(), @@ -389,6 +351,5 @@ 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 548805a..0756640 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,9 +84,11 @@ object Runner { } glfwSetFramebufferSizeCallback(window) { _: Long, width: Int, height: Int -> - framebufferWidth = width - framebufferHeight = height - core!!.update(width / contentScaleX, height / contentScaleY, max(contentScaleX, contentScaleY)) + if (width > 0 && height > 0) { + framebufferWidth = width + framebufferHeight = height + core!!.update(width / contentScaleX, height / contentScaleY, max(contentScaleX, contentScaleY)) + } } glfwSetKeyCallback(window) { _: Long, keyCode: Int, scanCode: Int, action: Int, _: Int -> @@ -190,11 +192,10 @@ 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 1eb9896..4262ebd 100644 --- a/src/test/kotlin/examples/Animations.kt +++ b/src/test/kotlin/examples/Animations.kt @@ -22,68 +22,15 @@ import net.prismclient.aether.ui.util.extensions.px class Animations : UIScreen { override fun build() { create { - include(Generic()) + button("Hello").style { - 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) } - 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) - } - } - - animationOf("test", UIStyleSheet()) { - kf {} - UILinear(1000L) to { - background { -// backgroundColor = colorOf(asRGBA(0, 0, 255)) - border { - borderColor = colorOf(asRGBA(0f, 1f, 0f)) - borderWidth = px(1) - } - } - } - } + verticalList { - 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 deleted file mode 100644 index 46584d1..0000000 --- a/src/test/kotlin/examples/AutoLayouts.kt +++ /dev/null @@ -1,110 +0,0 @@ -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 1c3612a..3e8d79b 100644 --- a/src/test/kotlin/examples/Default.kt +++ b/src/test/kotlin/examples/Default.kt @@ -1,62 +1,23 @@ package examples +import examples.deps.Generic import net.prismclient.aether.ui.animation.ease.impl.UIQuart -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.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.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() { - 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) - } - } + override fun build() {} } \ 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 e115d40..8e32fc3 100644 --- a/src/test/kotlin/examples/components/Chart.kt +++ b/src/test/kotlin/examples/components/Chart.kt @@ -6,8 +6,12 @@ import net.prismclient.aether.ui.style.UIStyleSheet /** * An example component which draws a chart. */ -class Chart(style: String?) : UIComponent(style) { +class Chart() : UIComponent() { 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 eb8fc8c..831b3ed 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.styleOf +import net.prismclient.aether.ui.util.style import net.prismclient.aether.ui.util.top /** @@ -41,18 +41,17 @@ 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 - styleOf(UIImageSheet("icon24x")) { + UIImageSheet().style("icon24x") { size(24, 24) } - // A example font - styleOf(UIStyleSheet("generic-font")) { + UIStyleSheet().style("generic-font") { // FontFamily to Montserrat // FontSize -> 16f // FontColor -> -1 = asRGBA(255, 255, 255) (aka white)