Giter VIP home page Giter VIP logo

elementa's Introduction

Elementa

Elementa (from the name of the first book published on Geometry by Euclid) is a library that aims to make GUI creation extremely simple. It's based on a couple key concepts, some of which may already be familiar to those who have worked with a browser's DOM.

The library is based around the idea of being declarative. This is a shift from how one would normally do graphics programming in Minecraft, or most other coding in general. In Elementa, you do not have to write code to calculate how to place a component at a certain point on the screen, instead you simply have to describe what you want.

Dependency

It's recommended that you include [Essential](link eventually) instead of adding it yourself.

In your repository block, add:

Groovy

maven {
    url = "https://repo.essential.gg/repository/maven-public"
}

Kotlin

maven(url = "https://repo.essential.gg/repository/maven-public")

To use the latest builds, use the following dependency:

Forge
implementation("gg.essential:elementa-$mcVersion-$mcPlatform:$buildNumber")
Fabric

Groovy

modImplementation(include("gg.essential:elementa-$mcVersion-$mcPlatform:$buildNumber"))

Kotlin

modImplementation(include("gg.essential:elementa-$mcVersion-$mcPlatform:$buildNumber")!!)

Build Reference

Build Reference
mcVersion mcPlatform buildNumber
1.18.1 fabric 1.18.1-fabric
1.18.1 forge 1.18.1-forge
1.17.1 fabric 1.17.1-fabric
1.17.1 forge 1.17.1-forge
1.16.2 forge 1.16.2-forge
1.12.2 forge 1.12.2-forge
1.8.9 forge 1.8.9-forge

If you were previously using v1.7.1 of Elementa and are now on the v2.0.0 builds, please refer to the migration document to know what has changed.

To learn about all the new features in v2.0.0, please read the what's new document.

IMPORTANT!

If you are using forge, you must also relocate Elementa to avoid potential crashes with other mods. To do this, you will need to use the Shadow Gradle plugin.

Groovy Version

You can do this by either putting it in your plugins block:

plugins {
    id "com.github.johnrengelman.shadow" version "$version"
}

or by including it in your buildscript's classpath and applying it:

buildscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath "gradle.plugin.com.github.jengelman.gradle.plugins:shadow:$version"
    }
}

apply plugin: "com.github.johnrengelman.shadow"

You'll then want to relocate Elementa to your own package to avoid breaking other mods

shadowJar {
    archiveClassifier.set(null)
    relocate("gg.essential.elementa", "your.package.elementa")
    // elementa dependencies
    relocate("gg.essential.universalcraft", "your.package.universalcraft")
}
tasks.named("reobfJar").configure { dependsOn(tasks.named("shadowJar")) }
Kotlin Script Version

You can do this by either putting it in your plugins block:

plugins {
    id("com.github.johnrengelman.shadow") version "$version"
}

or by including it in your buildscript's classpath and applying it:

buildscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath("gradle.plugin.com.github.jengelman.gradle.plugins:shadow:$version")
    }
}

apply(plugin = "com.github.johnrengelman.shadow")

You'll then want to relocate Elementa to your own package to avoid breaking other mods

tasks.shadowJar {
    archiveClassifier.set(null)
    relocate("gg.essential.elementa", "your.package.elementa")
    // elementa dependencies
    relocate("gg.essential.universalcraft", "your.package.universalcraft")
}
tasks.reobfJar { dependsOn(tasks.shadowJar) }

Legacy Builds

In your dependencies block, add:

implementation "club.sk1er:Elementa:1.7.1-$mcVersion"

Components

All the drawing in Elementa is done via UIComponents. There is a root component named Window that MUST be in the hierarchy of all components, thus making it the top of the component tree. All components have exactly 1 parent, and all components have 0-n children.

To create a component, simply instantiate an existing implementation such as UIBlock, or extend UIComponent yourself.

// Manually create and store a window instance. The Window is the entry point for Elementa's event system,
// in that you must call events on the window instance manually, the most common of which would be Window#draw.
// This call must be made every frame or else the library will never render your components. If your Gui extends
// Elementa's WindowScreen, this step will already be done and a window will be provided.
val window = Window()

// Here we are creating an instance of one of the simplest components available, a UIBlock.
// Next, we must give it positions to tell the library where to draw the component. Here we
// simply give it a 10 pixel width and height.
// Finally, we have to add it to our hierarchy in some way, and in this instance we want it to be
// a child of the Window. Now that it is in the hierarchy, it will be drawn when we render our Window.
val box = UIBlock(Color.RED /* java.awt.Color */).constrain {
    width = 10.pixels()
    height = 10.pixels()
} childOf window

A showcase of all the components provided by Elementa:

Components GUI Photo

Read more about all of these components here.

Constraints

All components have a set of constraints that determine its X/Y position, width/height, and color. The default set of constraints sets a component's x, y, width, height to be 0, and color to be Color.WHITE.

A key thing to realize with these components is that everything is relative to its parent. When we center a component, it will be in the center of its direct parent, whether it is the Window or perhaps another UIBlock.

This also showcases exactly how declarative the library is. Our code is saying that we would like our box to be in the center of our parent, and that is all we need to do. No code to figure out how to position it there, no code to calculate. We simply describe exactly what we want, and Elementa will do the rest for you.

val box = UIBlock().constrain {
    x = CenterConstraint()
    y = 10.pixels()
    width = 20.pixels()
    height = 36.pixels()
}

Effects

Additionally, a component can have a list of effects, special modifiers that can affect the rendering of a component or its children. One of the most common effects is the ScissorEffect. When enabled for an arbitrary component, this effect restricts all of its children to be drawn inside its own boundaries. Anything drawn outside that area will simply be cut off. Any component that is not a child (direct or indirect) of the component where the effect is enabled will not have their rendering affected.

val box = UIBlock() effect ScissorEffect()

Animations

Elementa also provides a strong animation API. When you make an animation, you set all the new constraints you would like to animate to, as well as the length (and optionally, delay) of the animation.

When animating, you have a wide variety of animation strategies (algorithms) to choose from, and you can of course implement more yourself. All the built-in animation strategies come from the Animations enum.

box.animate {
    // Algorithm, length, new constraint, and optionally, delay.
    // All times are in seconds.
    setWidthAnimation(Animations.OUT_EXP, 0.5f, ChildBasedSizeConstraint(2f))
}

Basic Events

Elementa also provides some basic events that can run your animations, or anything else of your choosing.

box.animate {
    setWidthAnimation(Animations.OUT_EXP, 0.5f, ChildBasedSizeConstraint(2f))
    
    // This will run when the animation is complete.
    // If this animation had multiple "animation components",
    // this would trigger when they were all complete.
    onComplete {
        // Trigger new animation or anything.    
    }
}

// Runs a single time when the mouse moves from a state of not hovering to hovering.
box.onMouseEnter {
    // Animate, set color, run business logic, etc.
}

There are many more events than solely those two, and they can be found throughout UIComponent. Keep in mind that all events stem from the Window component, and events must be manually called on the Window. For example, in order to receive an onMouseClick event, you MUST call Window#mouseClick. This is also all handled by Elementa's WindowScreen.

All together

This is a basic excerpt of code from an Elementa GUI. To see a more fleshed out example, look to the ExampleGui class.

val window = Window()

val box = UIBlock().constrain {
    x = CenterConstraint()
    y = 10f.pixels()
    width = 10f.pixels()
    height = 36f.pixels()
} effect ScissorEffect() childOf window

box.animate {
    setWidthAnimation(Animations.OUT_EXP, 0.5f, ChildBasedSizeConstraint(2f))

    onComplete {
        // Trigger new animation or anything.    
    }
}

box.onMouseEnter {
    // Animate, set color, etc.
}

elementa's People

Contributors

blackbeltpanda avatar callumbugajski avatar camnwalter avatar caoimhebyrne avatar chachydev avatar dediamondpro avatar djtheredstoner avatar falsehonesty avatar johni0702 avatar karkkikuppi avatar kerbybit avatar llamalad7 avatar mattco98 avatar mew avatar moulberry avatar nichrosia avatar redepicness avatar shedaniel avatar sk1er avatar sychic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

elementa's Issues

Elementa throws NoSuchElementException when using empty lists in Markdown Component

Describe the bug
When trying to create a Markdown Component with a character that's also a markdown prefix, such as a single hyphen ("-") which normally denotes a list, Elementa will throw a NoSuchElementException and not render the screen.

To Reproduce
Steps to reproduce the behavior:

  1. Create a Markdown Component
  2. Set the components text content to "-"
  3. See error

Expected behavior
A list prefix with no other content should probably just render the prefix itself.

Additional context
It's possible that the same would occur for other elements that expect a markdown prefix to be followed by some content, but I haven't tested that to make sure.

Customisable Scroll Animation

Is your feature request related to a problem? Please describe.
Scrolling with the Elementa UI library feels pretty smooth, but I would like to choose the specific animation algorithm - the scrolling in other programs such as my web browser uses a different animation.

Describe the solution you'd like
In the constructor, add a parameter to change the AnimationStategy.

Describe alternatives you've considered
I tried subclassing ScrollComponent, but it appears to be final.

MarkdownComponent: NotImplementedError in certain edge cases with blank markdown

Describe the bug
In certain cases, the a MarkdownComponent can have 0 drawables, which causes a NotImplementedError here.

To Reproduce
Steps to reproduce the behavior:

  1. Apply this patch to ComponentsGui
@@ -267,7 +268,7 @@
         } childOf window
 
         ComponentType("Markdown") {
-            MarkdownComponent(
+            val c = MarkdownComponent(
                 """
                     # Markdown!
                     
@@ -283,6 +284,11 @@
                 width = 200.pixels()
                 height = 100.pixels()
             } childOf this
+
+            c.onMouseClick {
+                c.bindText(BasicState(""))
+            }
+
         } childOf window
 
         ComponentType("SVG") {
  1. Open the components gui
  2. Click on the current text of the markdown component, hold and drag to another location on the screen
  3. The gui crashes and a NotImplementedError is logged (example)

Expected behavior
The the markdown component becomes blank without crashing the gui.

Additional context
There are possibly other ways to trigger this issue, but this is the way I found. The root cause appears to be commonmark's parser not having any children nodes for a blank document, causing MarkdownRenderer to produce an empty drawable list

Encourage use of `by` instead of `=`

Using val example by UIExampleComponent() instead of val example = UIExampleComponent() has the huge advantage that the inspector can show the name of the component.
We should explain this in the docs somewhere, and consistently make use of it in all example code.

ExampleGui.kt not working correctly

Describe the bug
So, when the GUI opens there are two problems with the StickyNote() classwhen you click on "Create Notes!":

  1. You aren't able to write any text
  2. The "textHolder" is 2 pixels to high

To Reproduce
Steps to reproduce the behavior:
First Bug

  1. Open the GUI in any way you set it up (I did it with a command)
  2. Click on 'Create Notes!'
  3. See error: You can't write any text

Second Bug

  1. Open the GUI in any way you set it up (I did it with a command)
  2. Click on 'Create Notes!'
  3. The Gray Area is too long, goes till outside the "StickyNote" on the bottom side

Screenshots
Second Bug
image

[Craftify (based on Elementa)] Elementa crashes after a while of leaving the game open, killing the Craftify GUI.

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Run minecraft with Craftify (a music control mod)
  2. Join a world and set up Craftify using /craftify (Documentation here)
  3. Leave the game open and idle for who knows how long
  4. You'll get a message in chat and a ton of spam in the logs, and the "now playing" area will stop rendering.

Expected behavior
Leaving the game open doesn't cause any errors

Additional context

System: Arch Linux (6.4.4-zen1-1-zen)
Java version: 17
Minecraft: 1.20.1
Elementa version: integrated as a child mod, unknown

Log:

[13:14:36] [Render thread/INFO]: Game entered main loop!   <-- Game officially started

...    over 4 hours later of various logs from other mods and the main game...

[17:35:23] [Render thread/INFO]: [STDOUT]: Elementa: Cyclic constraint structure detected!
[17:35:23] [Render thread/INFO]: [STDOUT]: If you are a developer, set the environment variable "elementa.dev=true" to assist in debugging the issue.
[17:35:23] [Render thread/INFO]: [STDOUT]: Gui name: class_433
[17:35:23] [Render thread/INFO]: [STDERR]: java.lang.StackOverflowError
[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.RelativeConstraint.getWidthImpl(RelativeConstraint.kt:38)
[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.WidthConstraint.getWidth(Constraint.kt:138)
[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163)
[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.WidthConstraint.getWidth(Constraint.kt:138)
[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163)

...    Continues for thousands of lines

[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163)
[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.WidthConstraint.getWidth(Constraint.kt:138)
[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163)
[17:35:23] [Render thread/INFO]: [STDERR]: 	at gg.essential.elementa.constraints.WidthConstraint.getWidth(Constraint.kt:138)
[17:35:23] [Render thread/INFO]: [CHAT] §cElementa encountered an error while drawing a GUI. Check your logs for more information.
[17:35:23] [DefaultDispatcher-worker-6/ERROR]: Uncaught exception in thread "DefaultDispatcher-worker-6"
java.lang.StackOverflowError: null
	at gg.essential.elementa.constraints.WidthConstraint.getWidth(Constraint.kt:138) ~[elementa-590-c4250c8341948a4b.jar:?]
	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163) ~[elementa-590-c4250c8341948a4b.jar:?]
	at gg.essential.elementa.constraints.WidthConstraint.getWidth(Constraint.kt:138) ~[elementa-590-c4250c8341948a4b.jar:?]
	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163) ~[elementa-590-c4250c8341948a4b.jar:?]

...    more log spam

	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163) ~[elementa-590-c4250c8341948a4b.jar:?]
	at gg.essential.elementa.constraints.WidthConstraint.getWidth(Constraint.kt:138) ~[elementa-590-c4250c8341948a4b.jar:?]
	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163) ~[elementa-590-c4250c8341948a4b.jar:?]
	at gg.essential.elementa.constraints.WidthConstraint.getWidth(Constraint.kt:138) ~[elementa-590-c4250c8341948a4b.jar:?]
	at gg.essential.elementa.constraints.animation.WidthAnimationComponent.getWidthImpl(AnimationComponent.kt:163) ~[elementa-590-c4250c8341948a4b.jar:?]
[17:37:48] [Render thread/INFO]: [CHAT] [Rank]Multiplayerplayer1: Ill give someone a stack of emeralds for feather falling 4
[17:37:59] [Render thread/INFO]: [CHAT] [Rank]Multiplayerplayer2: ive got one
[17:38:21] [Render thread/INFO]: [CHAT] [Rank]Multiplayerplayer1: Bet

MSDF FontRenderer misaligned text height

Describe the bug
Text rendered with the MSDF FontRenderer is higher than it should be, outside the bounding box of the constraints.

To Reproduce
Steps to reproduce the behavior:

  1. Render any UIText with fontProvider set to one that uses MSDF FontRenderer.
  2. Having an OutlineEffect makes the misalignment easier to see.

Expected behavior
Text should be rendered inside the bounding box of the constraints.

Screenshots
image
Sample text rendered using DefaultFonts.ELEMENTA_MINECRAFT_FONT_RENDERER with CenterConstraint() and an OutlineEffect().

image
Same text and code, but using DefaultFonts.VANILLA_FONT_RENDERER instead.

Additional context
Only affects fonts created with FontRenderer. VanillaFontRenderer and BasicFontRenderer are rendered correctly

Bug with at least 1.15+ Elementa

Describe the bug
When loading the game, the game crashes, presumably because of the slick2d dependency (as I was told on the discord)

To Reproduce
Steps to reproduce the behavior:

  1. Load the game
  2. Crash

Expected behavior
I expected the game to not crash on startup

  • OS: Windows 10 2004

Crash Error:
https://pastebin.com/NpNSd7ud

README.md install instructions do not seem to be up to date and a little bit vague

This project tells people to install the latest snapshot versions by adding implementation "club.sk1er:Elementa:129-$mcVersion-SNAPSHOT" in their dependencies section. However, Vigilance specifies "gg.essential:Elementa:339-$mcVersion-SNAPSHOT" instead.

Also, $mcVersion does not seem to be a well explained variable. Generally, variables like that are expected to be replaced version numbers such as "1.16.5". But, looking through the repository's maven-metadata.xml, it seems that the format is different, e.g. $mcVersion = 11602 is used for all 1.16 versions.

FillConstraint ignores paddings by SiblingConstrains

Describe the bug
When you make a stacked component and for example use Y, and every time you use SiblingConstraint() with paddings, the space in the parent component reduces, but FillComponent ignores that and uses full size and exceeds the space given by the parent component.

To Reproduce
Steps to reproduce the behavior:

val block = UIBlock().constrain {
                y = CramSiblingConstraint(3f)
                x = CramSiblingConstraint(3f)
                width = 100.pixels
                height = 110.pixels
                color = ConstantColorConstraint(VigilancePalette.getDividerDark())
            } effect ScissorEffect() childOf scroller
            UIImage.ofResource(module.logo).constrain {
                x = CenterConstraint()
                y = SiblingConstraint(3f) + 5f.pixels
                width = 64.pixels
                height = 64.pixels
            } childOf block
            UIText(module.spacedName).constrain {
                x = CenterConstraint()
                y = SiblingConstraint(10f)
            } childOf block
            val statusBlock = UIBlock(ConstantColorConstraint(BooleanDefinedColorState(module::state))).constrain {
                y = SiblingConstraint()
                x = CenterConstraint()
                width = 100.percent
                height = FillConstraint()
            } childOf block

            UIText("Enabled").constrain {
                x = CenterConstraint()
                y = CenterConstraint()
            } childOf statusBlock

Expected behavior
Used hard coded values

Снимок экрана 2023-10-11 в 20 26 17

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
No paddings:
No paddings
With paddings:
Paddings

Timer API

Is your feature request related to a problem? Please describe.
Sometimes, a gui will require running certain code at repeating intervals, such as performing an action while a button is being held. Currently, the Animation API supports running an animation for a specified amount of time, so I'd like there to be a general way to run any code over a certain period.

Describe the solution you'd like
An API such as the following could be nice:

component.startTimer(interval: Float, delay: Float) {

}

component.stopTimer()

Though this API is only feasible for a single timer. To support multiple timers, the start timer function could return a timer ID which could then be passed to stop timer, mimicking the browser's setInterval and clearInterval functions.

Unify units of time throughout the project

Elementa uses multiple units of time in various APIs, when it should really only ever use one. We need to choose between using floats for number of seconds, or Longs for number of milliseconds. This has to be resolved before release of 2.0, as it would be a breaking change.

Message Preview Selection

Describe the bug
While in the texting box in Essential, if you have a message at least 3 lines long in the preview, and you try to select the lines below, it crashes the GUI.
image

To Reproduce

  1. Go to a PM/DM of any user
  2. Type a message at least 3 lines long
  3. Go to the top and try selecting the lines below by holding down your cursor on the text you wrote and moving your cursor down
  4. It crashes, with an error

Expected behavior
The text below that you are trying to select should be selected, instead you are meant with the GUI closing

Screenshots/Videos
Video Example

Hide API

Is your feature request related to a problem? Please describe.
When certain elements need to be dynamically hidden, the current best option is to simply remove it from its parent, and then re-add it at a later time. The issue with this is the fact that its a little bulky and unintuitive.

Describe the solution you'd like
An API similar to

component.hide()
component.unhide()

component.animateBeforeHide {
	color = ...
}

component.animateAfterUnhide {
	width = ...
}

This would provide a simply, intuitive way to hide and unhide components, while also providing the flexibility of being able to animate in/out those components.

Features: Suggestions

Here's just a few suggestions that would make the GUI's we can create far more feature rich and appealing.

Ideas

  • Add scrollable areas
  • Rounding shader
  • Outline constraint
  • Add text wrapping
  • Shadows

Cannot set cursorColor for UITextInput

Describe the bug
UITextInput constructor accepts cursorColor field, but always constructs superclass with Color.WHITE

To Reproduce
Steps to reproduce the behavior:

  1. Create UITextInput component with custom cursorColor.
  2. See it always WHITE in game.

Expected behavior
UITextInput with cursor colored by color from cursorColor field.

Additional context
Just one line fix here.

Stenciling performance

Describe the bug
When you have a category with a bunch of options, performance is hit heavily wasting time trying to render but also being told to not render.

To Reproduce
Create a bunch of options using Vigilance, open the gui, watch everything come to a halt

Screenshots
A gui with ~30 options
image

A gui with ~200 options
image

Support for displaying a UScreen as an Elementa component

Is your feature request related to a problem? Please describe.
No

Describe the solution you'd like
Currently in Elementa there is no component to provide a Minecraft GUI (UScreen) as an element. This means that people who would like to incoporate Minecraft GUIs into their own mod have to open the GUI normally making it look tacky.

Describe alternatives you've considered
An alternative would be simply opening the Minecraft GUI via the way provided by the version (Minecraft#displayGuiScreen in 1.8.9).

Additional context

Debug Visual Mode

Is your feature request related to a problem? Please describe.
When debugging a GUI, it is sometimes hard to tell the bounds of a component, leading to the user attempting to add extraneous debug components, etc.

Describe the solution you'd like
Instead, there should be a "Debug Visual Mode" that the user can enable with a system property. This mode will outline all UIComponents with a little, brightly colored outline to make it obvious what the bounds of a component are. The outline can be done simply through 4 draw rect calls, and performance isn't really an issue.

1.19+

Hello, is it possible to update the mod to 1.19.2 ?
It crash because TranslatableText can't be casted to Text

Improve Docs

  • #92
  • Add constraints docs page
  • Maybe add something about states

Fix Elementa ScrollComponent

Is your feature request related to a problem? Please describe.
Horizontal scrolling only works when the shift is pressed, regardless of whether vertical scrolling is enabled or not. Code Link Code Link

Describe the solution you'd like
If vertical scrolling is disabled, then horizontal scrolling does not require pressing Shift.

Describe alternatives you've considered
The use of ASM transformers (Mixin) in this case is not advisable, and a private fork with a bug fix is ​​redundant.

Additional context
Discord Message

Add support for rounded images through shaders

Is your feature request related to a problem? Please describe.
At the moment, images can only be rounded through stencil which can look very pixelated and ugly

Describe the solution you'd like
fsh:

#version 110
uniform float u_Radius;
uniform vec4 u_InnerRect;

uniform sampler2D u_Texture;

varying vec2 f_Position;
varying vec2 f_TexCoord;

void main() {
    vec2 tl = u_InnerRect.xy - f_Position;
    vec2 br = f_Position - u_InnerRect.zw;
    vec2 dis = max(br, tl);
    float v = length(max(vec2(0.0), dis)) - u_Radius;
    float a = 1.0 - smoothstep(0.0, 1.0, v);
    gl_FragColor = vec4(texture2D(u_Texture, f_TexCoord).rgb, a);
}

vsh:

#version 110

varying vec2 f_Position;
varying out vec2 f_TexCoord;

void main() {
    f_Position = gl_Vertex.xy;
    f_TexCoord = gl_MultiTexCoord0.xy;

    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
    gl_FrontColor = gl_Color;
}

effect:

class ShaderEffect(private val radius: Float) : Effect() {
    private var shader: Shader = Shader("rounded_image", "rounded_image")
    private var shaderRadiusUniform: FloatUniform = FloatUniform(shader.getUniformLocation("u_Radius"))
    private var shaderInnerRectUniform = Vec4Uniform(shader.getUniformLocation("u_InnerRect"))

    override fun beforeDraw() {
        shader.bindIfUsable()
        shaderRadiusUniform.setValue(radius)
        shaderInnerRectUniform.setValue(
            Vector4f(
                boundComponent.getLeft() + radius,
                boundComponent.getTop() + radius,
                boundComponent.getRight() - radius,
                boundComponent.getBottom() - radius
            )
        )
    }

    override fun afterDraw() {
        shader.unbindIfUsable()
    }
}

Describe alternatives you've considered
Stencil

Additional context
with the shader:
image

Focus API

Is your feature request related to a problem? Please describe.
In order to support elements such as modals, a component needs to be able to grab focus away from other components, to avoid clicking "through" a component.

Describe the solution you'd like
I'd like for a UIComponent to be able to "focus" a single of its children. This would mean that any focused component would be the only child to receive mouse and keyboard events.

Clicks are not detected outside of parents

Describe the bug
If you create an object that has a negative coordinate, or a coordinate outside the parent, the onMouseClick event will not trigger. onMouseEnter and onMouseLeave still trigger however.

To Reproduce
Steps to reproduce the behavior:

  1. Create a parent object
  2. Create a child of the parent object, with a location outside the parent object
  3. Give the parent object an onMouseClick event

Expected behavior
The click event should trigger (as does the onMouseEnter and onMouseLeave)

StencilEffect doesn't work on 1.18

Describe the bug
Elementa's StencilEffect doesn't work in 1.18 as the enableStencil function doesn't have any implementation for versions less than 1.15.

To Reproduce
Steps to reproduce the behavior:

  1. Call StencilEffect.enableStencil() in your mod's initializer
  2. Add the StencilEffect to your component

Expected behavior
The component should be scissored around the parent component

Additional context
To enable stencils in 1.18, I found this gist, but I wasn't able to get it working properly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.