├── src └── main │ ├── resources │ ├── loading.png │ ├── vertical-scroll.png │ ├── textures │ │ ├── failure.png │ │ └── inspector │ │ │ ├── click.png │ │ │ ├── square_plus.png │ │ │ └── square_minus.png │ ├── fonts │ │ ├── Minecraft-Bold.png │ │ ├── Minecraft-Bold.ttf │ │ ├── Minecraft-Five.png │ │ ├── Minecraft-Regular.otf │ │ ├── Minecraft-Regular.png │ │ ├── JetBrainsMono-Regular.png │ │ └── JetBrainsMono-Regular.ttf │ ├── shaders │ │ ├── font.vsh │ │ ├── rect.vsh │ │ ├── circle.fsh │ │ ├── rounded_rect.fsh │ │ └── font.fsh │ ├── fabric.mod.json │ ├── pack.mcmeta │ └── svg │ │ ├── square-minus.svg │ │ ├── square-plus.svg │ │ ├── scroll.svg │ │ ├── failure.svg │ │ ├── test.svg │ │ ├── click.svg │ │ └── close.svg │ └── kotlin │ └── gg │ └── essential │ └── elementa │ ├── utils │ ├── TriConsumer.kt │ ├── vectors.kt │ ├── options.kt │ ├── invalidUsage.kt │ ├── extensions.kt │ ├── ResourceCache.kt │ └── Matrix2.kt │ ├── shaders │ ├── Shaders.kt │ └── Uniforms.kt │ ├── svg │ └── data │ │ ├── SVG.kt │ │ ├── SVGElement.kt │ │ ├── Point.kt │ │ ├── SVGLine.kt │ │ ├── SVGPolyline.kt │ │ ├── SVGAttributes.kt │ │ ├── SVGCircle.kt │ │ ├── SVGRect.kt │ │ └── SVGCurve.kt │ ├── components │ ├── plot │ │ ├── PointType.kt │ │ ├── Bounds.kt │ │ ├── LineType.kt │ │ └── PlotStyle.kt │ ├── image │ │ ├── CacheableImage.kt │ │ ├── ImageCache.kt │ │ ├── ImageProvider.kt │ │ ├── DefaultLoadingImage.kt │ │ └── DefaultFailureImage.kt │ ├── UIContainer.kt │ ├── MarkdownNode.kt │ ├── UIPoint.kt │ ├── inspector │ │ └── ArrowComponent.kt │ ├── updateFunc.kt │ └── input │ │ └── UIPasswordInput.kt │ ├── constraints │ ├── PaddingConstraint.kt │ ├── ConstraintType.kt │ ├── resolution │ │ ├── ResolverNode.kt │ │ ├── ConstraintResolver.kt │ │ ├── ConstraintVisitor.kt │ │ ├── ConstraintResolverV2.kt │ │ └── DirectedAcyclicGraph.kt │ ├── debug │ │ ├── NoopConstraintDebugger.kt │ │ ├── RecalculatingConstraintDebugger.kt │ │ ├── CycleSafeConstraintDebugger.kt │ │ └── ConstraintDebugger.kt │ ├── MousePositionConstraint.kt │ ├── InheritedColorConstraint.kt │ ├── ScaledTextConstraint.kt │ ├── ImageAspectConstraint.kt │ ├── MaxImageConstraint.kt │ ├── TextAspectConstraint.kt │ ├── AspectConstraint.kt │ ├── RoundingConstraint.kt │ ├── RelativeWindowConstraint.kt │ ├── CenterConstraint.kt │ ├── MaxConstraint.kt │ ├── MinConstraint.kt │ ├── ScaleConstraint.kt │ ├── AdditiveConstraint.kt │ ├── SubtractiveConstraint.kt │ └── RelativeConstraint.kt │ ├── state │ ├── v2 │ │ └── state.kt │ └── extensions.kt │ ├── dsl │ ├── animations.kt │ ├── components.kt │ ├── constraints.kt │ ├── basicConstraints.kt │ └── componentConstraints.kt │ ├── font │ ├── DefaultFonts.kt │ ├── ElementaFonts.kt │ ├── data │ │ ├── Font.kt │ │ └── FontInfo.kt │ └── FontProvider.kt │ ├── markdown │ ├── selection │ │ ├── ImageCursor.kt │ │ ├── TextCursor.kt │ │ └── Cursor.kt │ ├── drawables │ │ ├── CodeBlockDrawable.kt │ │ ├── SoftBreakDrawable.kt │ │ ├── HardBreakDrawable.kt │ │ └── HeaderDrawable.kt │ └── DrawState.kt │ ├── events │ ├── UIMouseEvents.kt │ └── UIEvent.kt │ ├── effects │ └── StencilEffect.kt │ └── transitions │ ├── BoundTransition.kt │ ├── RecursiveFadeOutTransition.kt │ ├── RecursiveFadeInTransition.kt │ └── ShrinkTransition.kt ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── mc-stubs ├── src │ └── main │ │ └── java │ │ └── net │ │ └── minecraft │ │ ├── class_2561.java │ │ ├── util │ │ ├── IChatComponent.java │ │ └── text │ │ │ └── ITextComponent.java │ │ └── network │ │ └── chat │ │ └── Component.java └── build.gradle.kts ├── unstable ├── layoutdsl │ ├── src │ │ └── main │ │ │ └── kotlin │ │ │ └── gg │ │ │ └── essential │ │ │ └── elementa │ │ │ └── unstable │ │ │ ├── layoutdsl │ │ │ ├── axis.kt │ │ │ ├── float.kt │ │ │ ├── modifier.kt │ │ │ ├── gradient.kt │ │ │ ├── lazy.kt │ │ │ ├── state.kt │ │ │ ├── basicModifiers.kt │ │ │ ├── alignment.kt │ │ │ ├── color.kt │ │ │ ├── events.kt │ │ │ └── util.kt │ │ │ └── common │ │ │ ├── HollowUIContainer.kt │ │ │ ├── stateExtensions.kt │ │ │ ├── Spacer.kt │ │ │ └── constraints │ │ │ ├── CenterPixelConstraint.kt │ │ │ └── AlternateConstraint.kt │ └── build.gradle.kts └── statev2 │ ├── src │ ├── main │ │ └── kotlin │ │ │ └── gg │ │ │ └── essential │ │ │ └── elementa │ │ │ └── unstable │ │ │ └── state │ │ │ └── v2 │ │ │ ├── flatten.kt │ │ │ ├── combinators │ │ │ ├── utils.kt │ │ │ ├── pair.kt │ │ │ ├── strings.kt │ │ │ ├── booleans.kt │ │ │ └── state.kt │ │ │ ├── color │ │ │ └── color.kt │ │ │ ├── utils │ │ │ └── completableFuture.kt │ │ │ ├── stateBy.kt │ │ │ ├── set.kt │ │ │ ├── impl │ │ │ └── Impl.kt │ │ │ ├── collections │ │ │ ├── TrackedSet.kt │ │ │ └── utils.kt │ │ │ └── list.kt │ └── test │ │ └── kotlin │ │ └── gg │ │ └── essential │ │ └── elementa │ │ └── unstable │ │ └── state │ │ └── v2 │ │ ├── StateSchedulerTest.kt │ │ └── ObservedLongTest.kt │ └── build.gradle.kts ├── gradle.properties ├── example ├── src │ └── main │ │ └── kotlin │ │ └── gg │ │ └── essential │ │ └── elementa │ │ └── example │ │ ├── main.kt │ │ ├── JavaTestGui.java │ │ ├── KtTestGui.kt │ │ └── ExamplesGui.kt └── build.gradle.kts ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── main.yml ├── Jenkinsfile ├── settings.gradle.kts └── gradlew.bat /src/main/resources/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/loading.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mc-stubs/src/main/java/net/minecraft/class_2561.java: -------------------------------------------------------------------------------- 1 | package net.minecraft; 2 | 3 | public class class_2561 { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/resources/vertical-scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/vertical-scroll.png -------------------------------------------------------------------------------- /mc-stubs/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | } 4 | 5 | java.toolchain.languageVersion = JavaLanguageVersion.of(8) 6 | -------------------------------------------------------------------------------- /mc-stubs/src/main/java/net/minecraft/util/IChatComponent.java: -------------------------------------------------------------------------------- 1 | package net.minecraft.util; 2 | 3 | public class IChatComponent { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/resources/textures/failure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/textures/failure.png -------------------------------------------------------------------------------- /src/main/resources/fonts/Minecraft-Bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/fonts/Minecraft-Bold.png -------------------------------------------------------------------------------- /src/main/resources/fonts/Minecraft-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/fonts/Minecraft-Bold.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/Minecraft-Five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/fonts/Minecraft-Five.png -------------------------------------------------------------------------------- /mc-stubs/src/main/java/net/minecraft/network/chat/Component.java: -------------------------------------------------------------------------------- 1 | package net.minecraft.network.chat; 2 | 3 | public class Component { 4 | } 5 | -------------------------------------------------------------------------------- /mc-stubs/src/main/java/net/minecraft/util/text/ITextComponent.java: -------------------------------------------------------------------------------- 1 | package net.minecraft.util.text; 2 | 3 | public class ITextComponent { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/resources/fonts/Minecraft-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/fonts/Minecraft-Regular.otf -------------------------------------------------------------------------------- /src/main/resources/fonts/Minecraft-Regular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/fonts/Minecraft-Regular.png -------------------------------------------------------------------------------- /src/main/resources/textures/inspector/click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/textures/inspector/click.png -------------------------------------------------------------------------------- /src/main/resources/fonts/JetBrainsMono-Regular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/fonts/JetBrainsMono-Regular.png -------------------------------------------------------------------------------- /src/main/resources/fonts/JetBrainsMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/fonts/JetBrainsMono-Regular.ttf -------------------------------------------------------------------------------- /src/main/resources/textures/inspector/square_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/textures/inspector/square_plus.png -------------------------------------------------------------------------------- /src/main/resources/textures/inspector/square_minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EssentialGG/Elementa/HEAD/src/main/resources/textures/inspector/square_minus.png -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/axis.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | enum class Axis { 4 | HORIZONTAL, 5 | VERTICAL 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/utils/TriConsumer.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.utils 2 | 3 | @FunctionalInterface 4 | interface TriConsumer { 5 | fun accept(t: T, u: U, v: V) 6 | } -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/flatten.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2 2 | 3 | fun State>.flatten() = memo { this@flatten()() } 4 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/float.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | enum class FloatPosition { 4 | START, 5 | CENTER, 6 | END 7 | } -------------------------------------------------------------------------------- /src/main/resources/shaders/font.vsh: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | varying vec2 pos; 4 | 5 | void main() { 6 | pos = gl_MultiTexCoord0.st; 7 | 8 | gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/resources/shaders/rect.vsh: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | varying vec2 f_Position; 4 | 5 | void main() { 6 | f_Position = gl_Vertex.xy; 7 | 8 | gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 9 | gl_FrontColor = gl_Color; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "elementa", 4 | "name": "Elementa", 5 | "version": "${version}", 6 | "environment": "client", 7 | "custom": { 8 | "modmenu": { 9 | "badges": [ "library" ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "examplemod resources", 4 | "pack_format": 5, 5 | "_comment": "A pack_format of 5 requires json lang files and some texture changes from 1.15. Note: we require v5 pack meta for all mods." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/shaders/Shaders.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.shaders 2 | 3 | import gg.essential.universal.UGraphics 4 | 5 | @Deprecated("Use UniversalCraft's UShader instead.") 6 | object Shaders { 7 | val newShaders: Boolean get() = UGraphics.areShadersSupported() 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/svg/square-minus.svg: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/SVG.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | data class SVG( 4 | val elements: List, 5 | val width: Float?, 6 | val height: Float?, 7 | val strokeWidth: Float, 8 | val roundLineCaps: Boolean, 9 | val roundLineJoins: Boolean 10 | ) -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/utils/vectors.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.utils 2 | 3 | // Vectors for shaders 4 | 5 | data class Vector2f(val x: Float, val y: Float) 6 | 7 | data class Vector3f(val x: Float, val y: Float, val z: Float) 8 | 9 | data class Vector4f(val x: Float, val y: Float, val z: Float, val w: Float) -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/combinators/utils.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.combinators 2 | 3 | import gg.essential.elementa.unstable.state.v2.MutableState 4 | 5 | fun MutableState.reorder(vararg mapping: Int) = 6 | bimap({ mapping[it] }, { mapping.indexOf(it) }) 7 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/plot/PointType.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.plot 2 | 3 | enum class PointType(private val drawFunc: (List, PlotStyle) -> Unit) { 4 | None({ _, _ -> }); 5 | 6 | fun draw(points: List, style: PlotStyle) { 7 | drawFunc(points, style) 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/PaddingConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | 5 | interface PaddingConstraint { 6 | 7 | fun getVerticalPadding(component: UIComponent): Float 8 | 9 | fun getHorizontalPadding(component: UIComponent) : Float 10 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | essential.defaults.loom=0 2 | modGroup=club.sk1er 3 | modVersion=1.7.1 4 | modBaseName=Elementa 5 | forgeVersion=1.8.9-11.15.1.2318 6 | mcpVersion=stable_22 7 | 8 | org.gradle.daemon=true 9 | org.gradle.parallel=true 10 | org.gradle.configureoncommand=true 11 | org.gradle.parallel.threads=4 12 | org.gradle.jvmargs=-Xmx5G 13 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/image/CacheableImage.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.image 2 | 3 | import gg.essential.universal.utils.ReleasedDynamicTexture 4 | 5 | interface CacheableImage { 6 | 7 | fun supply(image: CacheableImage) 8 | 9 | fun applyTexture(texture: ReleasedDynamicTexture?) 10 | 11 | } -------------------------------------------------------------------------------- /src/main/resources/svg/square-plus.svg: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/ConstraintType.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | enum class ConstraintType(val prettyName: String) { 4 | X("X"), 5 | Y("Y"), 6 | WIDTH("Width"), 7 | HEIGHT("Height"), 8 | RADIUS("Radius"), 9 | COLOR("Color"), 10 | FONT_PROVIDER("Font Provider"), 11 | TEXT_SCALE("TextScale") 12 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/gg/essential/elementa/example/main.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.example 2 | 3 | import gg.essential.universal.UScreen 4 | import gg.essential.universal.standalone.runUniversalCraft 5 | 6 | fun main() = runUniversalCraft("Elementa Example", 854, 480) { window -> 7 | UScreen.displayScreen(ExamplesGui()) 8 | window.renderScreenUntilClosed() 9 | } 10 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/combinators/pair.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.combinators 2 | 3 | import gg.essential.elementa.unstable.state.v2.State 4 | 5 | operator fun State>.component1(): State = this.map { it.first } 6 | operator fun State>.component2(): State = this.map { it.second } 7 | -------------------------------------------------------------------------------- /src/main/resources/shaders/circle.fsh: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | uniform float u_Radius; 4 | uniform vec2 u_CenterPos; 5 | 6 | varying vec2 f_Position; 7 | 8 | //out vec4 fragColor; 9 | 10 | void main() { 11 | float v = length(f_Position - u_CenterPos); 12 | 13 | float a = 1.0 - smoothstep(u_Radius - 1.0, u_Radius, v); 14 | 15 | gl_FragColor = gl_Color * vec4(1.0, 1.0, 1.0, a); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/svg/scroll.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/state/v2/state.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.state.v2 2 | 3 | /** 4 | * Holds strong references to listeners to prevent them from being garbage collected. 5 | */ 6 | interface ReferenceHolder { 7 | fun holdOnto(listener: Any): () -> Unit 8 | 9 | object Weak : ReferenceHolder { 10 | override fun holdOnto(listener: Any): () -> Unit = {} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | application 4 | id("gg.essential.defaults.repo") 5 | } 6 | 7 | dependencies { 8 | implementation(libs.universalcraft.standalone) 9 | implementation(project(":")) 10 | implementation(project(":unstable:layoutdsl")) 11 | } 12 | 13 | kotlin.jvmToolchain(8) 14 | 15 | application { 16 | mainClass.set("gg.essential.elementa.example.MainKt") 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/svg/failure.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/resolution/ResolverNode.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.resolution 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.ConstraintType 5 | import gg.essential.elementa.constraints.SuperConstraint 6 | 7 | data class ResolverNode( 8 | val component: UIComponent, 9 | val constraint: SuperConstraint<*>, 10 | val constraintType: ConstraintType 11 | ) -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/SVGElement.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | import java.nio.FloatBuffer 4 | 5 | abstract class SVGElement { 6 | val attributes: SVGAttributes = SVGAttributes() 7 | 8 | abstract fun getVertexCount(): Int 9 | 10 | open fun applyAttributes() { } 11 | 12 | open fun drawSmoothPoints(): Boolean { 13 | return true 14 | } 15 | 16 | abstract fun createBuffer(buffer: FloatBuffer): Int 17 | } -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/combinators/strings.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.combinators 2 | 3 | import gg.essential.elementa.unstable.state.v2.State 4 | 5 | fun State.contains(other: State, ignoreCase: Boolean = false) = 6 | zip(other) { a, b -> a.contains(b, ignoreCase) } 7 | 8 | fun State.isEmpty() = map { it.isEmpty() } 9 | 10 | fun State.isNotEmpty() = map { it.isNotEmpty() } 11 | -------------------------------------------------------------------------------- /src/main/resources/svg/test.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/color/color.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.color 2 | 3 | import gg.essential.elementa.constraints.ColorConstraint 4 | import gg.essential.elementa.dsl.basicColorConstraint 5 | import gg.essential.elementa.unstable.state.v2.State 6 | import java.awt.Color 7 | 8 | fun State.toConstraint() = basicColorConstraint { get() } 9 | 10 | val State.constraint: ColorConstraint 11 | get() = toConstraint() 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | eclipse 3 | bin 4 | *.launch 5 | .settings 6 | .metadata 7 | .classpath 8 | .project 9 | 10 | # idea 11 | out 12 | classes 13 | *.ipr 14 | *.iws 15 | *.iml 16 | .idea 17 | 18 | # gradle 19 | build 20 | .gradle 21 | 22 | #Netbeans 23 | .nb-gradle 24 | .nb-gradle-properties 25 | 26 | # other 27 | run 28 | .DS_Store 29 | Thumbs.db 30 | lib/* 31 | *.default 32 | 33 | # minecraft 34 | saves 35 | logs 36 | config 37 | options.txt 38 | usercache.json 39 | usernamecache.json 40 | *.txt 41 | -------------------------------------------------------------------------------- /src/main/resources/shaders/rounded_rect.fsh: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | uniform float u_Radius; 4 | uniform vec4 u_InnerRect; 5 | 6 | varying vec2 f_Position; 7 | 8 | //out vec4 fragColor; 9 | 10 | void main() { 11 | vec2 tl = u_InnerRect.xy - f_Position; 12 | vec2 br = f_Position - u_InnerRect.zw; 13 | vec2 dis = max(br, tl); 14 | 15 | float v = length(max(vec2(0.0), dis)) - u_Radius; 16 | float a = 1.0 - smoothstep(0.0, 1.0, v); 17 | gl_FragColor = gl_Color * vec4(1.0, 1.0, 1.0, a); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/svg/click.svg: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/dsl/animations.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.dsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.animation.AnimatingConstraints 5 | 6 | /** 7 | * Wrapper around [UIComponent.makeAnimation] and [UIComponent.animateTo], 8 | * providing a handy dandy DSL. 9 | */ 10 | inline fun T.animate(animation: AnimatingConstraints.() -> Unit) = apply { 11 | val anim = this.makeAnimation() 12 | anim.animation() 13 | this.animateTo(anim) 14 | } 15 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/common/HollowUIContainer.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.common 2 | 3 | import gg.essential.elementa.components.UIContainer 4 | 5 | /** 6 | * A UIContainer that does not return true for [isPointInside] unless 7 | * any of the child are hovered 8 | */ 9 | open class HollowUIContainer : UIContainer() { 10 | 11 | override fun isPointInside(x: Float, y: Float): Boolean { 12 | return children.any { 13 | it.isPointInside(x, y) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/utils/options.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.utils 2 | 3 | internal val devPropSet = System.getProperty("elementa.dev")?.toBoolean() ?: false 4 | private val debugPropSet = System.getProperty("elementa.debug")?.toBoolean() ?: false 5 | 6 | var elementaDev: Boolean = devPropSet 7 | set(value) { 8 | if (devPropSet) { 9 | field = value 10 | } 11 | } 12 | 13 | var elementaDebug: Boolean = debugPropSet 14 | set(value) { 15 | if (debugPropSet) { 16 | field = value 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/font/DefaultFonts.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.font 2 | 3 | import gg.essential.elementa.VanillaFontRenderer 4 | 5 | object DefaultFonts { 6 | @JvmStatic 7 | val VANILLA_FONT_RENDERER: FontProvider = VanillaFontRenderer() 8 | 9 | @JvmStatic 10 | val ELEMENTA_MINECRAFT_FONT_RENDERER: FontProvider = ElementaFonts.MINECRAFT 11 | 12 | @JvmStatic 13 | val JETBRAINS_MONO_FONT_RENDERER: FontProvider = ElementaFonts.JETBRAINS_MONO 14 | 15 | @JvmStatic 16 | val MINECRAFT_FIVE: FontProvider = ElementaFonts.MINECRAFT_FIVE 17 | } -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/combinators/booleans.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.combinators 2 | 3 | import gg.essential.elementa.unstable.state.v2.MutableState 4 | import gg.essential.elementa.unstable.state.v2.State 5 | 6 | infix fun State.and(other: State) = 7 | zip(other) { a, b -> a && b } 8 | 9 | infix fun State.or(other: State) = 10 | zip(other) { a, b -> a || b } 11 | 12 | operator fun State.not() = map { !it } 13 | 14 | operator fun MutableState.not() = bimap({ !it }, { !it }) 15 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/UIContainer.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.universal.UMatrixStack 5 | 6 | /** 7 | * Bare-bones component that does no rendering and simply offers a bounding box. 8 | */ 9 | open class UIContainer : UIComponent() { 10 | override fun draw(matrixStack: UMatrixStack) { 11 | // This is necessary because if it isn't here, effects will never be applied. 12 | beforeDrawCompat(matrixStack) 13 | 14 | // no-op 15 | 16 | super.draw(matrixStack) 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/resources/svg/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/markdown/selection/ImageCursor.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.markdown.selection 2 | 3 | import gg.essential.elementa.markdown.drawables.ImageDrawable 4 | 5 | class ImageCursor(target: ImageDrawable) : Cursor(target) { 6 | override fun compareTo(other: Cursor<*>): Int { 7 | if (other !is ImageCursor) { 8 | return target.y.compareTo(other.target.y).let { 9 | if (it == 0) target.x.compareTo(other.target.x) else it 10 | } 11 | } 12 | 13 | return if (target.url == other.target.url) return 0 else 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/debug/NoopConstraintDebugger.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.debug 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.ConstraintType 5 | import gg.essential.elementa.constraints.SuperConstraint 6 | import gg.essential.elementa.constraints.getCached 7 | 8 | internal class NoopConstraintDebugger : ConstraintDebugger { 9 | override fun evaluate(constraint: SuperConstraint, type: ConstraintType, component: UIComponent): Float { 10 | return constraint.getCached(component) { invokeImpl(constraint, type, component) } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/events/UIMouseEvents.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.events 2 | 3 | import gg.essential.elementa.UIComponent 4 | 5 | data class UIClickEvent( 6 | val absoluteX: Float, 7 | val absoluteY: Float, 8 | val mouseButton: Int, 9 | val target: UIComponent, 10 | val currentTarget: UIComponent, 11 | val clickCount: Int 12 | ) : UIEvent() { 13 | val relativeX = absoluteX - currentTarget.getLeft() 14 | val relativeY = absoluteY - currentTarget.getTop() 15 | } 16 | 17 | data class UIScrollEvent( 18 | val delta: Double, 19 | val target: UIComponent, 20 | val currentTarget: UIComponent 21 | ) : UIEvent() -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/common/stateExtensions.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.common 2 | 3 | import gg.essential.elementa.state.State 4 | import gg.essential.elementa.state.v2.ReferenceHolder 5 | 6 | fun State.onSetValueAndNow(listener: (T) -> Unit) = onSetValue(listener).also { listener(get()) } 7 | 8 | @Deprecated("See `State.onSetValue`. Use `stateBy`/`effect` instead.") 9 | fun gg.essential.elementa.unstable.state.v2.State.onSetValueAndNow(owner: ReferenceHolder, listener: (T) -> Unit) = 10 | onSetValue(owner, listener).also { listener(get()) } 11 | 12 | operator fun State.not() = map { !it } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/image/ImageCache.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.image 2 | 3 | import java.awt.image.BufferedImage 4 | import java.net.URL 5 | 6 | /** 7 | * Used as optional parameter for UIImage in order to cache images loaded through a URL 8 | */ 9 | interface ImageCache { 10 | 11 | /** 12 | * Returns the cached version of the URL if available, or null if it should be loaded from the URL 13 | */ 14 | operator fun get(url: URL): BufferedImage? 15 | 16 | /** 17 | * Called when an uncached URL is successfully loaded and needs to be cached 18 | */ 19 | operator fun set(url: URL, image: BufferedImage) 20 | 21 | 22 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Initialize') { 5 | steps { 6 | sh "./gradlew clean" 7 | sh "./gradlew preprocessResources" 8 | } 9 | } 10 | 11 | stage('Build') { 12 | steps { 13 | sh "./gradlew publish -PBUILD_ID=${env.BUILD_ID} -Pbranch=${env.BRANCH_NAME} -PIS_CI=true --no-daemon" 14 | } 15 | } 16 | 17 | stage('Report') { 18 | steps { 19 | archiveArtifacts 'versions/1.8.9/build/libs/*.jar' 20 | archiveArtifacts 'versions/1.12.2/build/libs/*.jar' 21 | archiveArtifacts 'versions/1.15.2/build/libs/*.jar' 22 | archiveArtifacts 'versions/1.16.2/build/libs/*.jar' 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/MousePositionConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | 6 | class MousePositionConstraint : PositionConstraint { 7 | override var cachedValue = 0f 8 | override var recalculate = true 9 | override var constrainTo: UIComponent? = null 10 | 11 | override fun getXPositionImpl(component: UIComponent): Float { 12 | return UIComponent.getMouseX() 13 | } 14 | 15 | override fun getYPositionImpl(component: UIComponent): Float { 16 | return UIComponent.getMouseY() 17 | } 18 | 19 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { } 20 | } 21 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/utils/completableFuture.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.utils 2 | 3 | import gg.essential.elementa.unstable.state.v2.State 4 | import gg.essential.elementa.unstable.state.v2.mutableStateOf 5 | import java.util.concurrent.CompletableFuture 6 | import java.util.concurrent.Executor 7 | 8 | fun CompletableFuture.toState(mainThreadExecutor: Executor): State { 9 | if (isDone) { 10 | return State { get() } 11 | } 12 | 13 | val resolved by lazy(LazyThreadSafetyMode.NONE) { 14 | val resolved = mutableStateOf(null) 15 | thenAcceptAsync({ resolved.set(it) }, mainThreadExecutor) 16 | resolved 17 | } 18 | 19 | return State { if (isDone) get() else resolved() } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/debug/RecalculatingConstraintDebugger.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.debug 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.ConstraintType 5 | import gg.essential.elementa.constraints.SuperConstraint 6 | 7 | internal class RecalculatingConstraintDebugger( 8 | private val inner: ConstraintDebugger = NoopConstraintDebugger(), 9 | ) : ConstraintDebugger { 10 | private val visited = mutableSetOf>() 11 | 12 | override fun evaluate(constraint: SuperConstraint, type: ConstraintType, component: UIComponent): Float { 13 | if (visited.add(constraint)) { 14 | constraint.recalculate = true 15 | } 16 | return inner.evaluate(constraint, type, component) 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/InheritedColorConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | import java.awt.Color 6 | 7 | class InheritedColorConstraint : ColorConstraint { 8 | override var cachedValue = Color.WHITE 9 | override var recalculate = true 10 | override var constrainTo: UIComponent? = null 11 | 12 | override fun getColorImpl(component: UIComponent): Color { 13 | return (constrainTo ?: component.parent).getColor() 14 | } 15 | 16 | // Color constraints will only ever have parent dependencies, so there is no possibility 17 | // of an invalid constraint here 18 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/events/UIEvent.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.events 2 | 3 | abstract class UIEvent { 4 | var propagationStopped = false 5 | var propagationStoppedImmediately = false 6 | 7 | /** 8 | * Stops this event from continuing to bubble to parent components. If there are 9 | * multiple listeners on the current component, those "sibling" listeners will continue to fire. 10 | */ 11 | fun stopPropagation() { 12 | propagationStopped = true 13 | } 14 | 15 | /** 16 | * Stops this event from continuing to bubble to parent components and stops it from being passed 17 | * to the remaining listeners on the current component, meaning the event is completely stopped. 18 | */ 19 | fun stopImmediatePropagation() { 20 | propagationStoppedImmediately = true 21 | } 22 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | maven("https://maven.fabricmc.net") 6 | maven("https://maven.architectury.dev/") 7 | maven("https://maven.minecraftforge.net") 8 | maven("https://repo.essential.gg/repository/maven-public") 9 | } 10 | plugins { 11 | val egtVersion = "0.5.0" 12 | id("gg.essential.defaults") version egtVersion 13 | id("gg.essential.defaults.maven-publish") version egtVersion 14 | id("gg.essential.multi-version.root") version egtVersion 15 | id("gg.essential.multi-version.api-validation") version egtVersion 16 | } 17 | } 18 | 19 | rootProject.name = "Elementa" 20 | 21 | 22 | include(":mc-stubs") 23 | 24 | include(":unstable:statev2") 25 | include(":unstable:layoutdsl") 26 | 27 | 28 | include(":example") 29 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/font/ElementaFonts.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.font 2 | 3 | import gg.essential.elementa.font.data.Font 4 | 5 | object ElementaFonts { 6 | private val MINECRAFT_FONT = Font.fromResource("/fonts/Minecraft-Regular") 7 | private val MINECRAFT_BOLD_RAW = Font.fromResource("/fonts/Minecraft-Bold") 8 | private val JETBRAINS_MONO_FONT = Font.fromResource("/fonts/JetBrainsMono-Regular") 9 | private val MINECRAFT_FIVE_FONT = Font.fromResource("/fonts/Minecraft-Five") 10 | 11 | @JvmStatic 12 | val MINECRAFT = FontRenderer(MINECRAFT_FONT, boldFont = MINECRAFT_BOLD_RAW) 13 | 14 | @JvmStatic 15 | val MINECRAFT_BOLD = FontRenderer(MINECRAFT_BOLD_RAW) 16 | 17 | @JvmStatic 18 | val JETBRAINS_MONO = FontRenderer(JETBRAINS_MONO_FONT) 19 | 20 | @JvmStatic 21 | val MINECRAFT_FIVE = BasicFontRenderer(MINECRAFT_FIVE_FONT) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/stateBy.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2 2 | 3 | /** 4 | * Creates a state that derives its value using the given [block]. The value of any state may be accessed within this 5 | * block via [StateByScope.invoke]. These accesses are tracked and the block is automatically re-evaluated whenever any 6 | * one of them changes. 7 | */ 8 | @Deprecated("Use `memo` (result is cached) or `State` lambda (result is not cached)") 9 | fun stateBy(block: StateByScope.() -> T): State { 10 | return memo { 11 | val scope = object : StateByScope { 12 | override fun State.invoke(): T { 13 | return with(this@memo) { get() } 14 | } 15 | } 16 | block(scope) 17 | } 18 | } 19 | 20 | @Deprecated("Superseded by `Observer`") 21 | interface StateByScope { 22 | operator fun State.invoke(): T 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/font/data/Font.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.font.data 2 | 3 | import gg.essential.universal.UGraphics 4 | import gg.essential.universal.utils.ReleasedDynamicTexture 5 | import com.google.gson.JsonParser 6 | import java.io.InputStream 7 | 8 | class Font( 9 | val fontInfo: FontInfo, 10 | private val atlas: InputStream 11 | ) { 12 | private lateinit var texture: ReleasedDynamicTexture 13 | 14 | fun getTexture(): ReleasedDynamicTexture { 15 | if (!::texture.isInitialized) { 16 | texture = UGraphics.getTexture(atlas) 17 | } 18 | 19 | return texture 20 | } 21 | 22 | companion object { 23 | fun fromResource(path: String): Font { 24 | val json = this::class.java.getResourceAsStream("$path.json") 25 | val fontInfo = FontInfo.fromJson(JsonParser().parse(json.reader()).asJsonObject) 26 | 27 | return Font(fontInfo, this::class.java.getResourceAsStream("$path.png")) 28 | } 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/dsl/components.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.dsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.UIConstraints 5 | import gg.essential.elementa.effects.Effect 6 | import kotlin.properties.Delegates 7 | import kotlin.reflect.KProperty 8 | 9 | inline infix fun T.constrain(config: UIConstraints.() -> Unit) = apply { 10 | constraints.config() 11 | } 12 | 13 | inline infix fun T.addChild(child: T.() -> UIComponent) = apply { 14 | addChild(child()) 15 | } 16 | 17 | infix fun T.childOf(parent: UIComponent) = apply { 18 | parent.addChild(this) 19 | } 20 | 21 | infix fun T.effect(effect: Effect) = apply { 22 | this.enableEffect(effect) 23 | } 24 | 25 | operator fun T.provideDelegate( 26 | thisRef: Any?, 27 | property: KProperty<*> 28 | ) = Delegates.observable(this.also { componentName = property.name }) { _, _, value -> 29 | value.componentName = property.name 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/plot/Bounds.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.plot 2 | 3 | import java.awt.Color 4 | import kotlin.math.abs 5 | import kotlin.math.round 6 | 7 | data class PlotPoint(val x: Float, val y: Float) { 8 | constructor(x: Number, y: Number) : this(x.toFloat(), y.toFloat()) 9 | } 10 | 11 | class Bounds( 12 | min: Number, 13 | max: Number, 14 | val numberOfGridLines: Int, 15 | val showLabels: Boolean = false, 16 | val labelColor: Color = Color.WHITE, 17 | val labelToString: (Float) -> String = { 18 | if (abs(it - round(it)) <= 0.00001) { 19 | round(it).toInt().toString() 20 | } else "%.1f".format(it) 21 | } 22 | ) { 23 | val min = min.toFloat() 24 | val max = max.toFloat() 25 | 26 | val range: Float 27 | get() = max - min 28 | 29 | companion object { 30 | fun fromPoints(points: List, numberOfGridLines: Int = 5) = 31 | Bounds(points.minOrNull()!!, points.maxOrNull()!!, numberOfGridLines) 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/debug/CycleSafeConstraintDebugger.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.debug 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.ConstraintType 5 | import gg.essential.elementa.constraints.SuperConstraint 6 | 7 | internal class CycleSafeConstraintDebugger( 8 | private val inner: ConstraintDebugger = NoopConstraintDebugger(), 9 | ) : ConstraintDebugger { 10 | private val stack = mutableSetOf>() 11 | 12 | override fun evaluate(constraint: SuperConstraint, type: ConstraintType, component: UIComponent): Float { 13 | if (constraint in stack) { 14 | // Cycle detected! Let's just return some arbitrary value so we can continue. 15 | return 10f // non-0 so the component can still be highlighted 16 | } 17 | 18 | stack.add(constraint) 19 | try { 20 | return inner.evaluate(constraint, type, component) 21 | } finally { 22 | stack.remove(constraint) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/plot/LineType.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.plot 2 | 3 | import gg.essential.universal.UGraphics 4 | import org.lwjgl.opengl.GL11 5 | import java.awt.Color 6 | 7 | enum class LineType(private val drawFunc: (List) -> Unit) { 8 | Linear({ points -> 9 | GL11.glEnable(GL11.GL_LINE_SMOOTH) 10 | GL11.glBegin(GL11.GL_LINE_STRIP) 11 | points.forEach { 12 | GL11.glVertex2f(it.x, it.y) 13 | } 14 | GL11.glEnd() 15 | }); 16 | 17 | fun draw(points: List, color: Color, width: Float) { 18 | beforeDraw(color, width) 19 | drawFunc(points) 20 | afterDraw() 21 | } 22 | 23 | private fun beforeDraw(color: Color, width: Float) { 24 | UGraphics.enableBlend() 25 | UGraphics.disableTexture2D() 26 | 27 | GL11.glColor4f(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f) 28 | GL11.glLineWidth(width) 29 | } 30 | 31 | private fun afterDraw() { 32 | UGraphics.enableTexture2D() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/Point.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | data class Point(var x: Float, var y: Float) { 4 | constructor(x: Number, y: Number) : this(x.toFloat(), y.toFloat()) 5 | 6 | infix fun reflectedAround(other: Point): Point { 7 | return Point( 8 | 2f * other.x - x, 9 | 2f * other.y - y 10 | ) 11 | } 12 | 13 | operator fun plus(num: Number) = Point(x + num.toFloat(), y + num.toFloat()) 14 | operator fun plus(other: Point) = Point(x + other.x, y + other.y) 15 | 16 | operator fun minus(num: Number) = Point(x - num.toFloat(), y - num.toFloat()) 17 | operator fun minus(other: Point) = Point(x - other.x, y - other.y) 18 | 19 | operator fun times(num: Number) = Point(x * num.toFloat(), y * num.toFloat()) 20 | 21 | operator fun div(num: Number) = Point(x / num.toFloat(), y / num.toFloat()) 22 | } 23 | 24 | infix operator fun T.plus(point: Point) = point + this 25 | infix operator fun T.minus(point: Point) = point - this 26 | infix operator fun T.times(point: Point) = point * this 27 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/SVGLine.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | import gg.essential.elementa.impl.dom4j.Element 4 | import org.lwjgl.opengl.GL11 5 | import java.nio.FloatBuffer 6 | 7 | data class SVGLine(val point1: Point, val point2: Point) : SVGElement() { 8 | override fun getVertexCount(): Int { 9 | return 2 10 | } 11 | 12 | override fun applyAttributes() { 13 | attributes.modify(point1) 14 | attributes.modify(point2) 15 | } 16 | 17 | override fun createBuffer(buffer: FloatBuffer): Int { 18 | buffer.put(point1.x) 19 | buffer.put(point1.y) 20 | 21 | buffer.put(point2.x) 22 | buffer.put(point2.y) 23 | 24 | return GL11.GL_LINES 25 | } 26 | 27 | companion object { 28 | internal fun from(element: Element): SVGLine { 29 | return SVGLine( 30 | Point(element.attributeValue("x1").toFloat(), element.attributeValue("y1").toFloat()), 31 | Point(element.attributeValue("x2").toFloat(), element.attributeValue("y2").toFloat()) 32 | ) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/SVGPolyline.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | import gg.essential.elementa.impl.dom4j.Element 4 | import org.lwjgl.opengl.GL11 5 | import java.nio.FloatBuffer 6 | 7 | data class SVGPolyline(val points: List) : SVGElement() { 8 | override fun getVertexCount(): Int { 9 | return points.size 10 | } 11 | 12 | override fun applyAttributes() { 13 | points.forEach(attributes::modify) 14 | } 15 | 16 | override fun createBuffer(buffer: FloatBuffer): Int { 17 | points.forEach { 18 | buffer.put(it.x) 19 | buffer.put(it.y) 20 | } 21 | 22 | return GL11.GL_LINE_STRIP 23 | } 24 | 25 | companion object { 26 | internal fun from(element: Element): SVGPolyline { 27 | val points = element.attributeValue("points") 28 | .split(" ") 29 | .zipWithNext() 30 | .map { Point(it.first.toFloat(), it.second.toFloat()) } 31 | .filterIndexed { index, _ -> index % 2 == 0 } 32 | 33 | return SVGPolyline(points) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/markdown/selection/TextCursor.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.markdown.selection 2 | 3 | import gg.essential.elementa.dsl.width 4 | import gg.essential.elementa.markdown.drawables.TextDrawable 5 | 6 | /** 7 | * A simple class which points to a position in a TextDrawable. 8 | */ 9 | class TextCursor(target: TextDrawable, val offset: Int) : Cursor(target) { 10 | override val xBase = target.x + 11 | target.formattedText.substring(0, offset + target.style.numFormattingChars).width(target.scaleModifier) 12 | override val yBase = target.y 13 | 14 | override operator fun compareTo(other: Cursor<*>): Int { 15 | if (other !is TextCursor) { 16 | return target.y.compareTo(other.target.y).let { 17 | if (it == 0) target.x.compareTo(other.target.x) else it 18 | } 19 | } 20 | 21 | if (target == other.target) 22 | return offset.compareTo(other.offset) 23 | 24 | if (target.y == other.target.y) 25 | return target.x.compareTo(other.target.x) 26 | 27 | return target.y.compareTo(other.target.y) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/combinators/state.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.combinators 2 | 3 | import gg.essential.elementa.unstable.state.v2.MutableState 4 | import gg.essential.elementa.unstable.state.v2.State 5 | import gg.essential.elementa.unstable.state.v2.memo 6 | 7 | /** Maps this state into a new state */ 8 | fun State.map(mapper: (T) -> U): State { 9 | return memo { mapper(get()) } 10 | } 11 | 12 | /** Maps this mutable state into a new mutable state. */ 13 | fun MutableState.bimap(map: (T) -> U, unmap: (U) -> T): MutableState { 14 | return object : MutableState, State by this.map(map) { 15 | override fun set(mapper: (U) -> U) { 16 | this@bimap.set { unmap(mapper(map(it))) } 17 | } 18 | } 19 | } 20 | 21 | /** Zips this state with another state */ 22 | fun State.zip(other: State): State> = zip(other, ::Pair) 23 | 24 | /** Zips this state with another state using [mapper] */ 25 | fun State.zip(other: State, mapper: (T, U) -> V): State { 26 | return memo { mapper(this@zip(), other()) } 27 | } 28 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/modifier.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | 5 | interface Modifier { 6 | /** 7 | * Applies this modifier to the given component, and returns a function which can be called to undo the applied changes. 8 | */ 9 | fun applyToComponent(component: UIComponent): () -> Unit 10 | 11 | infix fun then(other: Modifier) = if (other === Modifier) this else CombinedModifier(this, other) 12 | 13 | companion object : Modifier { 14 | override fun applyToComponent(component: UIComponent): () -> Unit = {} 15 | 16 | override infix fun then(other: Modifier) = other 17 | } 18 | } 19 | 20 | private class CombinedModifier( 21 | private val first: Modifier, 22 | private val second: Modifier 23 | ) : Modifier { 24 | override fun applyToComponent(component: UIComponent): () -> Unit { 25 | val undoFirst = first.applyToComponent(component) 26 | val undoSecond = second.applyToComponent(component) 27 | return { 28 | undoSecond() 29 | undoFirst() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/MarkdownNode.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components 2 | 3 | import gg.essential.elementa.components.inspector.ArrowComponent 4 | import gg.essential.elementa.constraints.SiblingConstraint 5 | import gg.essential.elementa.dsl.constrain 6 | import gg.essential.elementa.dsl.pixels 7 | import gg.essential.elementa.markdown.drawables.Drawable 8 | import gg.essential.elementa.markdown.drawables.TextDrawable 9 | 10 | internal class MarkdownNode(private val targetDrawable: Drawable) : TreeNode() { 11 | private val componentClassName = targetDrawable.javaClass.simpleName.ifEmpty { "UnknownType" } 12 | private val componentDisplayName = 13 | componentClassName + if (targetDrawable is TextDrawable) " \"${targetDrawable.formattedText}\"" else "" 14 | 15 | private val component = UIText(componentDisplayName).constrain { 16 | x = SiblingConstraint() 17 | y = 2.pixels 18 | } 19 | 20 | init { 21 | targetDrawable.children.forEach { 22 | addChild(MarkdownNode(it)) 23 | } 24 | } 25 | override fun getArrowComponent() = ArrowComponent(targetDrawable.children.isEmpty()) 26 | 27 | override fun getPrimaryComponent() = component 28 | } 29 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "1.5.10" 3 | kotlinx-coroutines = "1.5.2" 4 | jetbrains-annotations = "23.0.0" 5 | universalcraft = "406" 6 | commonmark = "0.17.1" 7 | dom4j = "2.1.1" 8 | 9 | [libraries] 10 | kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } 11 | kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } 12 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } 13 | jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } 14 | universalcraft-forge10809 = { module = "gg.essential:universalcraft-1.8.9-forge", version.ref = "universalcraft" } 15 | universalcraft-standalone = { module = "gg.essential:universalcraft-standalone", version.ref = "universalcraft" } 16 | commonmark = { module = "org.commonmark:commonmark", version.ref = "commonmark" } 17 | commonmark-ext-gfm-strikethrough = { module = "org.commonmark:commonmark-ext-gfm-strikethrough", version.ref = "commonmark" } 18 | commonmark-ext-ins = { module = "org.commonmark:commonmark-ext-ins", version.ref = "commonmark" } 19 | dom4j = { module = "org.dom4j:dom4j", version.ref = "dom4j" } 20 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/common/Spacer.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.common 2 | 3 | import gg.essential.elementa.constraints.HeightConstraint 4 | import gg.essential.elementa.constraints.SiblingConstraint 5 | import gg.essential.elementa.constraints.WidthConstraint 6 | import gg.essential.elementa.dsl.constrain 7 | import gg.essential.elementa.dsl.pixels 8 | 9 | /** 10 | * A simple UIContainer where you can specify [width], [height], or both. 11 | * 12 | * If only [width] is specified, X-axis will be constrained to [SiblingConstraint]. 13 | * 14 | * If only [height] is specified, Y-axis will be constrained to [SiblingConstraint]. 15 | */ 16 | class Spacer(width: WidthConstraint = 0.pixels, height: HeightConstraint = 0.pixels) : HollowUIContainer() { 17 | constructor(width: Float, _desc: Int = 0) : this(width = width.pixels) { setX(SiblingConstraint()) } 18 | constructor(height: Float, _desc: Short = 0) : this(height = height.pixels) { setY(SiblingConstraint()) } 19 | constructor(width: Float, height: Float) : this(width = width.pixels, height = height.pixels) 20 | 21 | init { 22 | constrain { 23 | this.width = width 24 | this.height = height 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/state/extensions.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.state 2 | 3 | import gg.essential.elementa.constraints.ConstantColorConstraint 4 | import gg.essential.elementa.constraints.PixelConstraint 5 | import gg.essential.elementa.constraints.RelativeConstraint 6 | import java.awt.Color 7 | 8 | fun State.pixels( 9 | alignOpposite: Boolean = false, 10 | alignOutside: Boolean = false 11 | ) = PixelConstraint(0f, alignOpposite, alignOutside).bindValue(this) 12 | @JvmName("pixelsNumber") 13 | fun State.pixels(alignOpposite: Boolean = false, alignOutside: Boolean) = 14 | PixelConstraint(0f, alignOpposite, alignOutside).bindValue(this.map { it.toFloat() }) 15 | val State.pixels: PixelConstraint 16 | get() = pixels(alignOpposite = false, alignOutside = false) 17 | 18 | fun State.percent() = RelativeConstraint().bindValue(this) 19 | @JvmName("percentNumber") 20 | fun State.percent() = 21 | RelativeConstraint().bindValue(this.map { it.toFloat() }) 22 | val State.percent: RelativeConstraint 23 | get() = percent() 24 | 25 | fun State.toConstraint() = ConstantColorConstraint(this.get()).bindColor(this) 26 | val State.constraint: ConstantColorConstraint 27 | get() = toConstraint() 28 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/SVGAttributes.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | import kotlin.math.cos 4 | import kotlin.math.sin 5 | 6 | data class SVGAttributes( 7 | var strokeWidth: Float? = null, 8 | var transform: Transform? = null 9 | ) { 10 | fun modify(point: Point) { 11 | transform?.modify(point) 12 | } 13 | } 14 | 15 | data class Transform( 16 | var rotation: Rotation? = null 17 | ) { 18 | fun modify(point: Point) { 19 | rotation?.rotate(point) 20 | } 21 | } 22 | 23 | data class Rotation(val angle: Int, val originX: Float?, val originY: Float?) { 24 | fun rotate(point: Point) { 25 | val s = sin(Math.toRadians(angle.toDouble())) 26 | val c = cos(Math.toRadians(angle.toDouble())) 27 | 28 | // translate point back to origin: 29 | val normalX = point.x - (originX ?: 0f) 30 | val normalY = point.y - (originY ?: 0f) 31 | 32 | // rotate point 33 | var newX = (normalX * c - normalY * s).toFloat() 34 | var newY = (normalX * s + normalY * c).toFloat() 35 | 36 | // translate point back: 37 | newX += (originX ?: 0f) 38 | newY += (originY ?: 0f) 39 | 40 | point.x = newX 41 | point.y = newY 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/plot/PlotStyle.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.plot 2 | 3 | import java.awt.Color 4 | 5 | data class PlotStyle( 6 | val pointStyle: PointStyle = PointStyle(), 7 | val lineStyle: LineStyle = LineStyle(), 8 | val gridStyle: GridStyle = GridStyle(), 9 | val padding: Padding = Padding(10) 10 | ) 11 | 12 | class Padding( 13 | left: Number, 14 | top: Number, 15 | right: Number, 16 | bottom: Number 17 | ) { 18 | val left = left.toFloat() 19 | val top = top.toFloat() 20 | val right = right.toFloat() 21 | val bottom = bottom.toFloat() 22 | 23 | constructor(horizontal: Number, vertical: Number) : this(horizontal, vertical, horizontal, vertical) 24 | 25 | constructor(all: Number) : this(all, all, all, all) 26 | } 27 | 28 | data class PointStyle( 29 | val color: Color = Color.WHITE, 30 | val radius: Float = 2f, 31 | val type: PointType = PointType.None 32 | ) 33 | 34 | data class LineStyle( 35 | val color: Color = Color.WHITE, 36 | val width: Float = 2f, 37 | val type: LineType = LineType.Linear 38 | ) 39 | 40 | data class GridStyle( 41 | val color: Color = Color(80, 80, 80), 42 | val width: Float = 2f, 43 | val type: LineType = LineType.Linear 44 | ) -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/markdown/drawables/CodeBlockDrawable.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.markdown.drawables 2 | 3 | import gg.essential.elementa.markdown.DrawState 4 | import gg.essential.elementa.markdown.MarkdownComponent 5 | import gg.essential.elementa.markdown.MarkdownConfig 6 | import gg.essential.elementa.markdown.selection.Cursor 7 | import gg.essential.elementa.markdown.selection.TextCursor 8 | import gg.essential.universal.UMatrixStack 9 | 10 | class CodeBlockDrawable(md: MarkdownComponent) : Drawable(md) { 11 | override fun layoutImpl(x: Float, y: Float, width: Float): Layout { 12 | TODO("Not yet implemented") 13 | } 14 | 15 | override fun draw(matrixStack: UMatrixStack, state: DrawState) { 16 | TODO("Not yet implemented") 17 | } 18 | 19 | override fun cursorAt(mouseX: Float, mouseY: Float, dragged: Boolean, mouseButton: Int): Cursor<*> { 20 | TODO("Not yet implemented") 21 | } 22 | 23 | override fun cursorAtStart(): Cursor<*> { 24 | TODO("Not yet implemented") 25 | } 26 | 27 | override fun cursorAtEnd(): Cursor<*> { 28 | TODO("Not yet implemented") 29 | } 30 | 31 | override fun selectedText(asMarkdown: Boolean): String { 32 | TODO("Not yet implemented") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/markdown/selection/Cursor.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.markdown.selection 2 | 3 | import gg.essential.elementa.components.UIBlock 4 | import gg.essential.elementa.markdown.DrawState 5 | import gg.essential.elementa.markdown.MarkdownComponent 6 | import gg.essential.elementa.markdown.drawables.Drawable 7 | import gg.essential.universal.UMatrixStack 8 | import java.awt.Color 9 | 10 | abstract class Cursor(val target: T) { 11 | protected open val xBase = target.x 12 | protected open val yBase = target.y 13 | protected val height = target.height.toDouble() 14 | protected val width = height / 9.0 15 | 16 | @Deprecated(UMatrixStack.Compat.DEPRECATED, ReplaceWith("draw(matrixStack, state)")) 17 | fun draw(state: DrawState) = draw(UMatrixStack(), state) 18 | 19 | fun draw(matrixStack: UMatrixStack, state: DrawState) { 20 | if (!MarkdownComponent.DEBUG) 21 | return 22 | 23 | UIBlock.drawBlockSized( 24 | matrixStack, 25 | Color.RED, 26 | (xBase + state.xShift).toDouble(), 27 | (yBase + state.yShift).toDouble(), 28 | width, 29 | height 30 | ) 31 | } 32 | 33 | abstract operator fun compareTo(other: Cursor<*>): Int 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/SVGCircle.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | import gg.essential.elementa.impl.dom4j.Element 4 | import org.lwjgl.opengl.GL11 5 | import java.nio.FloatBuffer 6 | import kotlin.math.PI 7 | import kotlin.math.cos 8 | import kotlin.math.sin 9 | 10 | data class SVGCircle(val cx: Float, val cy: Float, val r: Float) : SVGElement() { 11 | override fun getVertexCount(): Int { 12 | return VERTEX_COUNT 13 | } 14 | 15 | override fun createBuffer(buffer: FloatBuffer): Int { 16 | val vertices = VERTEX_COUNT - 1 17 | val doublePi = 2f * PI.toFloat() 18 | 19 | for (i in 0..vertices) { 20 | buffer.put(cx + (r * cos(i * doublePi / vertices))) 21 | buffer.put(cy + (r * sin(i * doublePi / vertices))) 22 | } 23 | 24 | return GL11.GL_LINE_LOOP 25 | } 26 | 27 | override fun drawSmoothPoints() = false 28 | 29 | companion object { 30 | const val VERTEX_COUNT = 25 31 | 32 | internal fun from(element: Element): SVGCircle { 33 | return SVGCircle( 34 | element.attributeValue("cx").toFloat(), 35 | element.attributeValue("cy").toFloat(), 36 | element.attributeValue("r").toFloat() 37 | ) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/markdown/drawables/SoftBreakDrawable.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.markdown.drawables 2 | 3 | import gg.essential.elementa.markdown.DrawState 4 | import gg.essential.elementa.markdown.MarkdownComponent 5 | import gg.essential.elementa.markdown.MarkdownConfig 6 | import gg.essential.elementa.markdown.selection.Cursor 7 | import gg.essential.elementa.markdown.selection.TextCursor 8 | import gg.essential.universal.UMatrixStack 9 | 10 | /** 11 | * A soft break is one line break between lines of markdown text. 12 | */ 13 | class SoftBreakDrawable(md: MarkdownComponent) : Drawable(md) { 14 | override fun layoutImpl(x: Float, y: Float, width: Float): Layout { 15 | TODO("Not yet implemented") 16 | } 17 | 18 | override fun draw(matrixStack: UMatrixStack, state: DrawState) { 19 | TODO("Not yet implemented") 20 | } 21 | 22 | override fun cursorAt(mouseX: Float, mouseY: Float, dragged: Boolean, mouseButton: Int): Cursor<*> { 23 | TODO("Not yet implemented") 24 | } 25 | 26 | override fun cursorAtStart(): Cursor<*> { 27 | TODO("Not yet implemented") 28 | } 29 | 30 | override fun cursorAtEnd(): Cursor<*> { 31 | TODO("Not yet implemented") 32 | } 33 | 34 | override fun selectedText(asMarkdown: Boolean): String { 35 | TODO("Not yet implemented") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /unstable/layoutdsl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import gg.essential.gradle.multiversion.StripReferencesTransform.Companion.registerStripReferencesAttribute 2 | import gg.essential.gradle.util.setJvmDefault 3 | import gg.essential.gradle.util.versionFromBuildIdAndBranch 4 | 5 | plugins { 6 | kotlin("jvm") 7 | id("gg.essential.defaults") 8 | id("gg.essential.defaults.maven-publish") 9 | } 10 | 11 | version = versionFromBuildIdAndBranch() 12 | group = "gg.essential" 13 | 14 | dependencies { 15 | compileOnly(project(":")) 16 | api(project(":unstable:statev2")) 17 | 18 | val common = registerStripReferencesAttribute("common") { 19 | excludes.add("net.minecraft") 20 | } 21 | compileOnly(libs.versions.universalcraft.map { "gg.essential:universalcraft-1.8.9-forge:$it" }) { 22 | attributes { attribute(common, true) } 23 | } 24 | // Depending on LWJGL3 instead of 2 so we can choose opengl bindings only 25 | compileOnly("org.lwjgl:lwjgl-opengl:3.3.1") 26 | } 27 | tasks.compileKotlin.setJvmDefault("all") 28 | 29 | kotlin.jvmToolchain { 30 | (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(8)) 31 | } 32 | 33 | java.withSourcesJar() 34 | 35 | publishing { 36 | publications { 37 | named("maven") { 38 | artifactId = "elementa-unstable-${project.name}" 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/markdown/drawables/HardBreakDrawable.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.markdown.drawables 2 | 3 | import gg.essential.elementa.markdown.DrawState 4 | import gg.essential.elementa.markdown.MarkdownComponent 5 | import gg.essential.elementa.markdown.MarkdownConfig 6 | import gg.essential.elementa.markdown.selection.Cursor 7 | import gg.essential.elementa.markdown.selection.TextCursor 8 | import gg.essential.universal.UMatrixStack 9 | 10 | /** 11 | * A hard break is two or more line breaks between lines of 12 | * markdown text. 13 | */ 14 | class HardBreakDrawable(md: MarkdownComponent) : Drawable(md) { 15 | override fun layoutImpl(x: Float, y: Float, width: Float): Layout { 16 | TODO("Not yet implemented") 17 | } 18 | 19 | override fun draw(matrixStack: UMatrixStack, state: DrawState) { 20 | TODO("Not yet implemented") 21 | } 22 | 23 | override fun cursorAt(mouseX: Float, mouseY: Float, dragged: Boolean, mouseButton: Int): Cursor<*> { 24 | TODO("Not yet implemented") 25 | } 26 | 27 | override fun cursorAtStart(): Cursor<*> { 28 | TODO("Not yet implemented") 29 | } 30 | 31 | override fun cursorAtEnd(): Cursor<*> { 32 | TODO("Not yet implemented") 33 | } 34 | 35 | override fun selectedText(asMarkdown: Boolean): String { 36 | TODO("Not yet implemented") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /unstable/statev2/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import gg.essential.gradle.multiversion.StripReferencesTransform.Companion.registerStripReferencesAttribute 2 | import gg.essential.gradle.util.setJvmDefault 3 | import gg.essential.gradle.util.versionFromBuildIdAndBranch 4 | 5 | plugins { 6 | kotlin("jvm") 7 | id("gg.essential.defaults") 8 | id("gg.essential.defaults.maven-publish") 9 | } 10 | 11 | version = versionFromBuildIdAndBranch() 12 | group = "gg.essential" 13 | 14 | dependencies { 15 | compileOnly(project(":")) 16 | compileOnly(libs.kotlinx.coroutines.core) 17 | 18 | val common = registerStripReferencesAttribute("common") { 19 | excludes.add("net.minecraft") 20 | } 21 | compileOnly(libs.versions.universalcraft.map { "gg.essential:universalcraft-1.8.9-forge:$it" }) { 22 | attributes { attribute(common, true) } 23 | } 24 | 25 | testImplementation(kotlin("test")) 26 | testImplementation(project(":")) 27 | } 28 | 29 | tasks.test { 30 | useJUnitPlatform() 31 | } 32 | 33 | tasks.compileKotlin.setJvmDefault("all") 34 | 35 | kotlin.jvmToolchain { 36 | (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(8)) 37 | } 38 | 39 | java.withSourcesJar() 40 | 41 | publishing { 42 | publications { 43 | named("maven") { 44 | artifactId = "elementa-unstable-${project.name}" 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/image/ImageProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.image 2 | 3 | import gg.essential.universal.UMatrixStack 4 | import java.awt.Color 5 | 6 | interface ImageProvider { 7 | /** 8 | * Render the image provided by this component with the provided attributes. 9 | * 10 | * This method is guaranteed to be called from the main thread. 11 | * 12 | * Even though this method has a default implementation, it should in all cases be implemented. 13 | * The default implementation exists only for backwards compatibility. 14 | */ 15 | fun drawImage(matrixStack: UMatrixStack, x: Double, y: Double, width: Double, height: Double, color: Color) 16 | = matrixStack.runWithGlobalState { @Suppress("DEPRECATION") drawImageCompat(UMatrixStack(), x, y, width, height, color) } 17 | 18 | @Deprecated(UMatrixStack.Compat.DEPRECATED, ReplaceWith("drawImage(matrixStack, x, y, width, height, color)")) 19 | fun drawImage(x: Double, y: Double, width: Double, height: Double, color: Color): Unit = 20 | drawImage(UMatrixStack.Compat.get(), x, y, width, height, color) 21 | 22 | fun drawImageCompat(matrixStack: UMatrixStack, x: Double, y: Double, width: Double, height: Double, color: Color): Unit = 23 | UMatrixStack.Compat.runLegacyMethod(matrixStack) { @Suppress("DEPRECATION") drawImage(x, y, width, height, color) } 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/image/DefaultLoadingImage.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.image 2 | 3 | import gg.essential.elementa.utils.drawTexture 4 | import gg.essential.universal.UGraphics 5 | import gg.essential.universal.UMatrixStack 6 | import gg.essential.universal.utils.ReleasedDynamicTexture 7 | import org.lwjgl.opengl.GL11 8 | import java.awt.Color 9 | import java.awt.image.BufferedImage 10 | import javax.imageio.ImageIO 11 | 12 | object DefaultLoadingImage : ImageProvider { 13 | private var loadingImage: BufferedImage? = ImageIO.read(this::class.java.getResourceAsStream("/loading.png")) 14 | private lateinit var loadingTexture: ReleasedDynamicTexture 15 | 16 | override fun drawImage( 17 | matrixStack: UMatrixStack, 18 | x: Double, 19 | y: Double, 20 | width: Double, 21 | height: Double, 22 | color: Color, 23 | ) { 24 | if (!::loadingTexture.isInitialized) { 25 | loadingTexture = UGraphics.getTexture(loadingImage!!) 26 | loadingImage = null 27 | } 28 | 29 | drawTexture( 30 | matrixStack, 31 | loadingTexture, 32 | color, 33 | x, 34 | y, 35 | width, 36 | height, 37 | textureMinFilter = GL11.GL_LINEAR, 38 | textureMagFilter = GL11.GL_LINEAR, 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/image/DefaultFailureImage.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.image 2 | 3 | import gg.essential.elementa.utils.drawTexture 4 | import gg.essential.universal.UGraphics 5 | import gg.essential.universal.UMatrixStack 6 | import gg.essential.universal.utils.ReleasedDynamicTexture 7 | import org.lwjgl.opengl.GL11 8 | import java.awt.Color 9 | import java.awt.image.BufferedImage 10 | import javax.imageio.ImageIO 11 | 12 | object DefaultFailureImage : ImageProvider { 13 | 14 | private var loadingImage: BufferedImage? = ImageIO.read(this::class.java.getResourceAsStream("/textures/failure.png")) 15 | private lateinit var loadingTexture: ReleasedDynamicTexture 16 | 17 | override fun drawImage( 18 | matrixStack: UMatrixStack, 19 | x: Double, 20 | y: Double, 21 | width: Double, 22 | height: Double, 23 | color: Color, 24 | ) { 25 | if (!::loadingTexture.isInitialized) { 26 | loadingTexture = UGraphics.getTexture(loadingImage!!) 27 | loadingImage = null 28 | } 29 | 30 | drawTexture( 31 | matrixStack, 32 | loadingTexture, 33 | color, 34 | x, 35 | y, 36 | width, 37 | height, 38 | textureMinFilter = GL11.GL_LINEAR, 39 | textureMagFilter = GL11.GL_LINEAR, 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/effects/StencilEffect.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.effects 2 | 3 | import gg.essential.universal.UGraphics 4 | import gg.essential.universal.UMatrixStack 5 | import org.lwjgl.opengl.GL11.* 6 | 7 | /** 8 | * Allows for arbitrary scissoring of any shaped component. 9 | * 10 | * In order to use, you must call [enableStencil] in mod initialization. 11 | */ 12 | @Deprecated("Does not work on 1.14+") 13 | class StencilEffect : Effect() { 14 | override fun beforeDraw(matrixStack: UMatrixStack) { 15 | glEnable(GL_STENCIL_TEST) 16 | // commented to make component still draw 17 | //glColorMask ( false, false, false, false) 18 | glStencilFunc(GL_ALWAYS, 2, 0xffffffff.toInt()) 19 | glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE) 20 | } 21 | 22 | override fun beforeChildrenDraw(matrixStack: UMatrixStack) { 23 | //glColorMask (true, true, true, true) 24 | glStencilFunc(GL_EQUAL, 2, 0xffffffff.toInt()) 25 | glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) 26 | } 27 | 28 | override fun afterDraw(matrixStack: UMatrixStack) { 29 | glDisable(GL_STENCIL_TEST) 30 | } 31 | 32 | companion object { 33 | /** 34 | * Must be called in mod initialization to use [StencilEffect] 35 | */ 36 | @JvmStatic fun enableStencil() { 37 | @Suppress("DEPRECATION") 38 | UGraphics.enableStencil() 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/SVGRect.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | import gg.essential.elementa.impl.dom4j.Element 4 | import org.lwjgl.opengl.GL11 5 | import java.nio.FloatBuffer 6 | 7 | class SVGRect( 8 | private val topLeft: Point, 9 | private val topRight: Point, 10 | private val bottomRight: Point, 11 | private val bottomLeft: Point 12 | ) : SVGElement() { 13 | override fun getVertexCount(): Int { 14 | return 4 15 | } 16 | 17 | override fun createBuffer(buffer: FloatBuffer): Int { 18 | buffer.put(bottomLeft.x) 19 | buffer.put(bottomLeft.y) 20 | 21 | buffer.put(bottomRight.x) 22 | buffer.put(bottomRight.y) 23 | 24 | buffer.put(topRight.x) 25 | buffer.put(topRight.y) 26 | 27 | buffer.put(topLeft.x) 28 | buffer.put(topLeft.y) 29 | 30 | return GL11.GL_LINE_LOOP 31 | } 32 | 33 | companion object { 34 | internal fun from(element: Element): SVGRect { 35 | val topLeft = Point(element.attributeValue("x").toFloat(), element.attributeValue("y").toFloat()) 36 | val width = element.attributeValue("width").toFloat() 37 | val height = element.attributeValue("height").toFloat() 38 | 39 | return SVGRect( 40 | topLeft, 41 | topLeft.copy(x = topLeft.x + width), 42 | topLeft.copy(x = topLeft.x + width, y = topLeft.y + height), 43 | topLeft.copy(y = topLeft.y + height) 44 | ) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/gradient.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.effects.Effect 4 | import gg.essential.elementa.unstable.effects.GradientEffect 5 | import gg.essential.elementa.unstable.state.v2.State 6 | import gg.essential.elementa.unstable.state.v2.stateOf 7 | import java.awt.Color 8 | 9 | fun Modifier.gradient(top: Color, bottom: Color, _desc: GradientVertDesc = GradientDesc) = gradient(stateOf(top), stateOf(bottom), _desc) 10 | fun Modifier.gradient(left: Color, right: Color, _desc: GradientHorzDesc = GradientDesc) = gradient(stateOf(left), stateOf(right), _desc) 11 | 12 | fun Modifier.gradient(top: State, bottom: State, _desc: GradientVertDesc = GradientDesc) = gradient(top, top, bottom, bottom) 13 | fun Modifier.gradient(left: State, right: State, _desc: GradientHorzDesc = GradientDesc) = gradient(left, right, left, right) 14 | 15 | sealed interface GradientVertDesc 16 | sealed interface GradientHorzDesc 17 | private object GradientDesc : GradientVertDesc, GradientHorzDesc 18 | 19 | fun Modifier.gradient( 20 | topLeft: State, 21 | topRight: State, 22 | bottomLeft: State, 23 | bottomRight: State, 24 | ) = effect { GradientEffect(topLeft, topRight, bottomLeft, bottomRight) } 25 | 26 | private fun Modifier.effect(effect: () -> Effect) = this then { 27 | val instance = effect() 28 | enableEffect(instance) 29 | return@then { 30 | removeEffect(instance) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/src/main/kotlin/gg/essential/elementa/example/JavaTestGui.java: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.example; 2 | 3 | import gg.essential.elementa.ElementaVersion; 4 | import gg.essential.elementa.UIComponent; 5 | import gg.essential.elementa.WindowScreen; 6 | import gg.essential.elementa.components.UIBlock; 7 | import gg.essential.elementa.constraints.CenterConstraint; 8 | import gg.essential.elementa.constraints.ChildBasedSizeConstraint; 9 | import gg.essential.elementa.constraints.PixelConstraint; 10 | import gg.essential.elementa.constraints.animation.AnimatingConstraints; 11 | import gg.essential.elementa.constraints.animation.Animations; 12 | import gg.essential.elementa.effects.ScissorEffect; 13 | 14 | public class JavaTestGui extends WindowScreen { 15 | UIComponent box = new UIBlock() 16 | .setX(new CenterConstraint()) 17 | .setY(new PixelConstraint(10f)) 18 | .setWidth(new PixelConstraint(10f)) 19 | .setHeight(new PixelConstraint(36f)) 20 | .setChildOf(getWindow()) 21 | .enableEffect(new ScissorEffect()); 22 | 23 | public JavaTestGui() { 24 | super(ElementaVersion.V2); 25 | box.onMouseEnterRunnable(() -> { 26 | // Animate, set color, etc. 27 | AnimatingConstraints anim = box.makeAnimation(); 28 | anim.setWidthAnimation(Animations.OUT_EXP, 0.5f, new ChildBasedSizeConstraint(2f)); 29 | anim.onCompleteRunnable(() -> { 30 | // Trigger new animation or anything. 31 | }); 32 | box.animateTo(anim); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/src/main/kotlin/gg/essential/elementa/example/KtTestGui.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.example 2 | 3 | import gg.essential.elementa.ElementaVersion 4 | import gg.essential.elementa.WindowScreen 5 | import gg.essential.elementa.components.* 6 | import gg.essential.elementa.components.input.UIMultilineTextInput 7 | import gg.essential.elementa.components.inspector.Inspector 8 | import gg.essential.elementa.constraints.* 9 | import gg.essential.elementa.constraints.animation.Animations 10 | import gg.essential.elementa.dsl.* 11 | import gg.essential.elementa.effects.OutlineEffect 12 | import gg.essential.elementa.effects.ScissorEffect 13 | import gg.essential.elementa.font.DefaultFonts 14 | import gg.essential.elementa.font.ElementaFonts 15 | import gg.essential.elementa.utils.withAlpha 16 | import java.awt.Color 17 | 18 | class KtTestGui : WindowScreen(ElementaVersion.V2) { 19 | private val myTextBox = UIBlock(Color(0, 0, 0, 255)) 20 | 21 | init { 22 | val container = UIContainer().constrain { 23 | x = RelativeConstraint(.25f) 24 | y = RelativeConstraint(.25f) 25 | width = RelativeConstraint(.5f) 26 | height = RelativeConstraint(.5f) 27 | } childOf window 28 | for (i in 50..500) { 29 | if (i % 15 != 0) continue 30 | UIBlock(Color.RED).constrain { 31 | x = CramSiblingConstraint(10 / 3f) 32 | y = CramSiblingConstraint(10f / 3f) 33 | width = 5.pixels() 34 | height = 5.pixels() 35 | } childOf container effect OutlineEffect(Color.BLUE, 1f); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/markdown/DrawState.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.markdown 2 | 3 | /** 4 | * Stores universal state necessary for rendering all drawables. 5 | * 6 | * The first time we layout the markdown tree (in MarkdownComponent), 7 | * we lay everything out based upon the current x and y values of 8 | * the MarkdownComponent. At this time, however, we do not yet know 9 | * the height of the component (we need to layout all of the drawables 10 | * to determine their height first). After this first layout, we set 11 | * the height of the MarkdownComponent based on the heights and 12 | * positions of the drawables. 13 | * 14 | * When we set this height, the y location of the MarkdownComponent 15 | * may change (for example, if its y constraint is a CenterConstraint). 16 | * So when we change the height, we would have to re-rendering the 17 | * entire tree again to update the positions. It is possible that this 18 | * may actually be a never-ending process (i.e. changing the height 19 | * changes the position, and changing the position changes the height). 20 | * 21 | * To avoid all of the re-laying-out, we layout the first time and use 22 | * those initial position values as a "base position" for all of the 23 | * drawables. When MarkdownComponent#draw is called, it subtracts its 24 | * current position from those base positions, stores those in this 25 | * class as shift components, and passes that to all of the drawables. 26 | * Those drawables then offset any drawing they do by these shift 27 | * values. 28 | */ 29 | data class DrawState( 30 | val xShift: Float, 31 | val yShift: Float 32 | ) 33 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/UIPoint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.PositionConstraint 5 | import gg.essential.elementa.dsl.pixels 6 | import gg.essential.universal.UMatrixStack 7 | 8 | /** 9 | * "Component" with no width/height and therefore no visible rendering. 10 | * 11 | * Used primarily for [UIShape] 12 | */ 13 | class UIPoint( 14 | val x: PositionConstraint, 15 | val y: PositionConstraint 16 | ) : UIComponent() { 17 | val relativeX: Float 18 | get() = constraints.getX() 19 | 20 | val relativeY: Float 21 | get() = constraints.getY() 22 | 23 | val absoluteX: Float 24 | get() = getLeft() 25 | 26 | val absoluteY: Float 27 | get() = getTop() 28 | 29 | val point: Pair 30 | get() = relativeX to relativeY 31 | 32 | val absolutePoint: Pair 33 | get() = absoluteX to absoluteY 34 | 35 | init { 36 | setX(x) 37 | setY(y) 38 | } 39 | 40 | constructor(x: Number, y: Number) : this(x.pixels(), y.pixels()) 41 | 42 | constructor(point: Pair) : this(point.first.pixels(), point.second.pixels()) 43 | 44 | fun withX(x: PositionConstraint) = UIPoint(x, y) 45 | 46 | fun withX(x: Number) = UIPoint(x.pixels(), y) 47 | 48 | fun withY(y: PositionConstraint) = UIPoint(x, y) 49 | 50 | fun withY(y: Number) = UIPoint(x, y.pixels()) 51 | 52 | override fun draw(matrixStack: UMatrixStack) { 53 | beforeDraw(matrixStack) 54 | super.draw(matrixStack) 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/inspector/ArrowComponent.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.inspector 2 | 3 | import gg.essential.elementa.components.TreeArrowComponent 4 | import gg.essential.elementa.components.UIImage 5 | import gg.essential.elementa.constraints.CenterConstraint 6 | import gg.essential.elementa.dsl.childOf 7 | import gg.essential.elementa.dsl.constrain 8 | import gg.essential.elementa.dsl.pixels 9 | import gg.essential.universal.UMatrixStack 10 | 11 | class ArrowComponent(private val empty: Boolean) : TreeArrowComponent() { 12 | private val closedIcon = UIImage.ofResourceCached("/textures/inspector/square_plus.png").constrain { 13 | width = 7.pixels 14 | height = 7.pixels 15 | x = CenterConstraint() 16 | y = CenterConstraint() 17 | } 18 | private val openIcon = UIImage.ofResourceCached("/textures/inspector/square_minus.png").constrain { 19 | width = 7.pixels 20 | height = 7.pixels 21 | x = CenterConstraint() 22 | y = CenterConstraint() 23 | } 24 | 25 | init { 26 | constrain { 27 | width = 10.pixels 28 | height = 10.pixels 29 | } 30 | 31 | if (!empty) 32 | closedIcon childOf this 33 | } 34 | 35 | override fun open() { 36 | if (!empty) 37 | replaceChild(openIcon, closedIcon) 38 | } 39 | 40 | override fun close() { 41 | if (!empty) 42 | replaceChild(closedIcon, openIcon) 43 | } 44 | 45 | override fun draw(matrixStack: UMatrixStack) { 46 | beforeDraw(matrixStack) 47 | super.draw(matrixStack) 48 | } 49 | } -------------------------------------------------------------------------------- /unstable/statev2/src/test/kotlin/gg/essential/elementa/unstable/state/v2/StateSchedulerTest.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2 2 | 3 | import gg.essential.elementa.state.v2.ReferenceHolder 4 | import java.time.Instant 5 | import kotlin.test.Test 6 | import kotlin.test.assertEquals 7 | 8 | class StateSchedulerTest { 9 | @Test 10 | fun test() { 11 | val startTime = Instant.EPOCH.plusMillis(2) 12 | val currentTime = mutableStateOf(startTime) 13 | val scheduler = StateScheduler(currentTime) 14 | 15 | val completed = Array(100000) { false } 16 | val effects = completed.indices.map { i -> 17 | effect(ReferenceHolder.Weak) { 18 | val targetTime = Instant.EPOCH.plusMillis(i.toLong()) 19 | val reachedTarget = with(scheduler) { observe(targetTime) } 20 | // Only time the effect should run is when we reached the target time and during setup. 21 | if (reachedTarget) { 22 | completed[i] = true 23 | assertEquals(if (targetTime > startTime) targetTime else startTime, currentTime.getUntracked()) 24 | } else { 25 | assertEquals(startTime, currentTime.getUntracked()) 26 | } 27 | } 28 | } 29 | 30 | assertEquals(true, completed[0]) 31 | assertEquals(true, completed[1]) 32 | assertEquals(true, completed[2]) 33 | assertEquals(false, completed[3]) 34 | 35 | for (i in completed.indices) { 36 | currentTime.set(startTime.plusMillis(i.toLong())) 37 | } 38 | 39 | effects.forEach { it() } 40 | 41 | assert(completed.all { it }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/ScaledTextConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.components.UIText 5 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 6 | import gg.essential.universal.UGraphics 7 | import java.lang.UnsupportedOperationException 8 | 9 | /** 10 | * Sets the width/height to be a scale of the default text width and height 11 | */ 12 | class ScaledTextConstraint(var scale: Float) : SizeConstraint { 13 | override var cachedValue = 0f 14 | override var recalculate = true 15 | override var constrainTo: UIComponent? = null 16 | 17 | 18 | override fun getWidthImpl(component: UIComponent): Float { 19 | return when (component) { 20 | is UIText -> scale * UGraphics.getStringWidth(component.getText()) 21 | else -> throw IllegalAccessException("ScaledTextConstraint can only be used with UIText") 22 | } 23 | } 24 | 25 | override fun getHeightImpl(component: UIComponent): Float { 26 | return when(component) { 27 | is UIText -> scale * 9 28 | else -> throw IllegalAccessException("ScaledTextConstraint can only be used with UIText") 29 | } 30 | } 31 | 32 | override fun getRadiusImpl(component: UIComponent): Float { 33 | throw IllegalAccessException("ScaledTextConstraint cannot be used as a radius") 34 | } 35 | 36 | override fun to(component: UIComponent) = apply { 37 | throw UnsupportedOperationException("Constraint.to(UIComponent) is not available in this context!") 38 | } 39 | 40 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { } 41 | } 42 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/common/constraints/CenterPixelConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.common.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.ConstraintType 5 | import gg.essential.elementa.constraints.PositionConstraint 6 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 7 | import kotlin.math.ceil 8 | import kotlin.math.floor 9 | 10 | /** Centers the component to whole pixels, rounding down unless [roundUp] is true */ 11 | class CenterPixelConstraint(private val roundUp: Boolean = false) : PositionConstraint { 12 | 13 | override var cachedValue = 0f 14 | override var recalculate = true 15 | override var constrainTo: UIComponent? = null 16 | 17 | override fun getXPositionImpl(component: UIComponent): Float { 18 | val parent = constrainTo ?: component.parent 19 | 20 | val center = if (component.isPositionCenter()) { 21 | parent.getWidth() / 2 22 | } else { 23 | parent.getWidth() / 2 - component.getWidth() / 2 24 | } 25 | 26 | return parent.getLeft() + if (roundUp) ceil(center) else floor(center) 27 | } 28 | 29 | override fun getYPositionImpl(component: UIComponent): Float { 30 | val parent = constrainTo ?: component.parent 31 | 32 | val center = if (component.isPositionCenter()) { 33 | parent.getHeight() / 2 34 | } else { 35 | parent.getHeight() / 2 - component.getHeight() / 2 36 | } 37 | 38 | return parent.getTop() + if (roundUp) ceil(center) else floor(center) 39 | } 40 | 41 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) {} 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/utils/invalidUsage.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.utils 2 | 3 | import gg.essential.universal.UMinecraft 4 | 5 | internal enum class InvalidUsageBehavior { 6 | IGNORE, 7 | WARN, 8 | THROW, 9 | } 10 | private val invalidUsageBehaviorProp = when (System.getProperty("elementa.invalid_usage", "")) { 11 | "throw" -> InvalidUsageBehavior.THROW 12 | "warn" -> InvalidUsageBehavior.WARN 13 | "ignore" -> InvalidUsageBehavior.IGNORE 14 | else -> null 15 | } 16 | internal val invalidUsageBehavior: InvalidUsageBehavior 17 | get() = invalidUsageBehaviorProp ?: if (devPropSet) InvalidUsageBehavior.THROW else InvalidUsageBehavior.WARN 18 | 19 | internal fun handleInvalidUsage(message: String) { 20 | when (invalidUsageBehavior) { 21 | InvalidUsageBehavior.IGNORE -> return 22 | InvalidUsageBehavior.WARN -> { 23 | IllegalStateException(message).printStackTrace() 24 | } 25 | InvalidUsageBehavior.THROW -> { 26 | throw IllegalStateException(message).also { 27 | it.printStackTrace() // print anyway so they cannot accidentally silence it 28 | } 29 | } 30 | } 31 | } 32 | 33 | internal fun requireState(state: Boolean, message: String) { 34 | if (!state) { 35 | handleInvalidUsage(message) 36 | } 37 | } 38 | 39 | /** Ensure a method can only be called from the main thread. Lack of this check does **not** imply thread-safety. */ 40 | internal fun requireMainThread(message: String = "This method is not thread-safe and must be called from the main thread. " + 41 | "Consider the thread-safety of the calling code and use Window.enqueueRenderOperation if applicable.") { 42 | requireState(UMinecraft.isCallingFromMinecraftThread(), message) 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/ImageAspectConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.components.UIImage 5 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 6 | import java.lang.UnsupportedOperationException 7 | 8 | /** 9 | * Sets the width/height to be the correct aspect of its own height/width respectively. 10 | */ 11 | class ImageAspectConstraint : WidthConstraint, HeightConstraint { 12 | override var cachedValue = 0f 13 | override var recalculate = true 14 | override var constrainTo: UIComponent? = null 15 | 16 | override fun getWidthImpl(component: UIComponent): Float { 17 | val image = component as? UIImage ?: throw IllegalStateException("ImageAspectConstraint can only be used in UIImage components") 18 | return component.getHeight() * image.imageWidth / image.imageHeight 19 | } 20 | 21 | override fun getHeightImpl(component: UIComponent): Float { 22 | val image = component as? UIImage ?: throw IllegalStateException("ImageAspectConstraint can only be used in UIImage components") 23 | return component.getWidth() * image.imageHeight / image.imageWidth 24 | } 25 | 26 | override fun to(component: UIComponent) = apply { 27 | throw UnsupportedOperationException("Constraint.to(UIComponent) is not available in this context!") 28 | } 29 | 30 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 31 | when (type) { 32 | ConstraintType.WIDTH -> visitor.visitSelf(ConstraintType.HEIGHT) 33 | ConstraintType.HEIGHT -> visitor.visitSelf(ConstraintType.WIDTH) 34 | else -> throw IllegalArgumentException(type.prettyName) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/svg/data/SVGCurve.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.svg.data 2 | 3 | import org.lwjgl.opengl.GL11 4 | import java.nio.FloatBuffer 5 | import kotlin.math.pow 6 | 7 | sealed class SVGCurve(private val steps: Int) : SVGElement() { 8 | abstract val lastControlPoint: Point 9 | 10 | private val points by lazy { 11 | (0 until steps).map { 12 | it.toFloat() / steps 13 | }.map(::getPoint) 14 | } 15 | 16 | override fun getVertexCount() = steps 17 | 18 | override fun createBuffer(buffer: FloatBuffer): Int { 19 | points.forEach { 20 | buffer.put(it.x) 21 | buffer.put(it.y) 22 | } 23 | 24 | return GL11.GL_LINES 25 | } 26 | 27 | abstract fun getPoint(percent: Float): Point 28 | } 29 | 30 | class SVGQuadraticCurve( 31 | private val start: Point, 32 | private val control: Point, 33 | private val end: Point, 34 | steps: Int = 100 35 | ) : SVGCurve(steps) { 36 | override val lastControlPoint = control 37 | 38 | override fun getPoint(percent: Float): Point { 39 | val percentOp = 1f - percent 40 | return percentOp.pow(2) * start + 2f * percent * percentOp * control + percent.pow(2) * end 41 | } 42 | } 43 | 44 | class SVGCubicCurve( 45 | private val start: Point, 46 | private val control1: Point, 47 | private val control2: Point, 48 | private val end: Point, 49 | steps: Int = 100 50 | ) : SVGCurve(steps) { 51 | override val lastControlPoint = control2 52 | 53 | override fun getPoint(percent: Float): Point { 54 | val percentOp = 1f - percent 55 | return percentOp.pow(3) * start + 3f * percent * percentOp.pow(2) * control1 + 56 | 3f * percent.pow(2) * percentOp * control2 + percent.pow(3) * end 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/shaders/Uniforms.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.shaders 2 | 3 | import gg.essential.elementa.utils.Vector2f 4 | import gg.essential.elementa.utils.Vector4f 5 | import org.lwjgl.opengl.ARBShaderObjects 6 | import org.lwjgl.opengl.GL20 7 | 8 | @Deprecated("Use UniversalCraft's UShader instead.") 9 | abstract class ShaderUniform(val location: Int) { 10 | abstract fun setValue(value: T) 11 | } 12 | 13 | @Deprecated("Use UniversalCraft's UShader instead.") 14 | class FloatUniform(location: Int) : ShaderUniform(location) { 15 | override fun setValue(value: Float) { 16 | if (Shaders.newShaders) GL20.glUniform1f(location, value) 17 | else ARBShaderObjects.glUniform1fARB(location, value) 18 | } 19 | } 20 | 21 | @Deprecated("Use UniversalCraft's UShader instead.") 22 | class IntUniform(location: Int) : ShaderUniform(location) { 23 | override fun setValue(value: Int) { 24 | if (Shaders.newShaders) GL20.glUniform1i(location, value) 25 | else ARBShaderObjects.glUniform1iARB(location, value) 26 | } 27 | } 28 | 29 | @Deprecated("Use UniversalCraft's UShader instead.") 30 | class Vec4Uniform(location: Int) : ShaderUniform(location) { 31 | override fun setValue(value: Vector4f) { 32 | if (Shaders.newShaders) GL20.glUniform4f(location, value.x, value.y, value.z, value.w) 33 | else ARBShaderObjects.glUniform4fARB(location, value.x, value.y, value.z, value.w) 34 | } 35 | } 36 | 37 | @Deprecated("Use UniversalCraft's UShader instead.") 38 | class Vec2Uniform(location: Int) : ShaderUniform(location) { 39 | override fun setValue(value: Vector2f) { 40 | if (Shaders.newShaders) GL20.glUniform2f(location, value.x, value.y) 41 | else ARBShaderObjects.glUniform2fARB(location, value.x, value.y) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/lazy.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.components.UIContainer 4 | import gg.essential.elementa.components.Window 5 | import gg.essential.elementa.components.inspector.Inspector 6 | import gg.essential.elementa.unstable.state.v2.MutableState 7 | import gg.essential.elementa.unstable.state.v2.mutableStateOf 8 | import gg.essential.universal.UMatrixStack 9 | 10 | /** 11 | * Lazily initializes the inner scope by first only placing a [box] as described by [modifier] without any children and 12 | * only initializing the inner scope once that box has been rendered once. 13 | * 14 | * This should be a last reserve for initializing a large list of poorly optimized components, not a common shortcut to 15 | * "make it not lag". Properly profiling and fixing initialization performance issues should always be preferred. 16 | */ 17 | fun LayoutScope.lazyBox(modifier: Modifier = Modifier.fillParent(), block: LayoutScope.() -> Unit) { 18 | val initialized = mutableStateOf(false) 19 | box(modifier) { 20 | if_(initialized, cache = false /** don't need it; once initialized, we are never going back */) { 21 | block() 22 | } `else` { 23 | LazyComponent(initialized)(Modifier.fillParent()) 24 | } 25 | }.apply { 26 | automaticComponentName("lazyBox") 27 | } 28 | } 29 | 30 | private class LazyComponent(private val initialized: MutableState) : UIContainer() { 31 | override fun draw(matrixStack: UMatrixStack) { 32 | super.draw(matrixStack) 33 | 34 | Window.enqueueRenderOperation { 35 | initialized.set(true) 36 | } 37 | } 38 | } 39 | 40 | @Suppress("unused") 41 | private val init = run { 42 | Inspector.registerComponentFactory(null) 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/transitions/BoundTransition.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.transitions 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.animation.AnimatingConstraints 5 | 6 | /** 7 | * A normal transition which is bound to a single component. This 8 | * is useful if a transition needs to store some persistent state 9 | * about the component it is acting on. 10 | * 11 | * This class is very similar to Effect in that it has a boundComponent 12 | * field which stores the component this transition is bound to. 13 | * This class can only be used with a single component. If it is used on 14 | * multiple components, it will throw an IllegalStateException. 15 | */ 16 | abstract class BoundTransition : Transition() { 17 | protected lateinit var boundComponent: UIComponent 18 | 19 | final override fun beforeTransition(component: UIComponent) { 20 | if (::boundComponent.isInitialized) 21 | throw IllegalStateException("BoundTransition cannot be used with multiple components") 22 | boundComponent = component 23 | beforeTransition() 24 | } 25 | 26 | final override fun doTransition(component: UIComponent, constraints: AnimatingConstraints) { 27 | if (component != boundComponent) 28 | throw IllegalStateException("Expected $component to be the same as boundComponent $boundComponent") 29 | doTransition(constraints) 30 | } 31 | 32 | final override fun afterTransition(component: UIComponent) { 33 | if (component != boundComponent) 34 | throw IllegalStateException("Expected $component to be the same as boundComponent $boundComponent") 35 | afterTransition() 36 | } 37 | 38 | open fun beforeTransition() {} 39 | 40 | abstract fun doTransition(constraints: AnimatingConstraints) 41 | 42 | open fun afterTransition() {} 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/transitions/RecursiveFadeOutTransition.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.transitions 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.animation.AnimatingConstraints 5 | import gg.essential.elementa.constraints.animation.Animations 6 | import gg.essential.elementa.effects.RecursiveFadeEffect 7 | import gg.essential.elementa.state.BasicState 8 | import kotlin.properties.Delegates 9 | 10 | /** 11 | * Fades a component and all of its children to alpha 0. When 12 | * the transition is finished, the fade effect is removed, 13 | * meaning that the component will go back to its original 14 | * transparency. Typically, one would hide the component 15 | * after this transition is finished. 16 | */ 17 | class RecursiveFadeOutTransition @JvmOverloads constructor( 18 | private val time: Float = 1f, 19 | private val animationType: Animations = Animations.OUT_EXP 20 | ) : Transition() { 21 | private val isOverridden = BasicState(false) 22 | private val overriddenAlphaPercentage = BasicState(1f) 23 | private var alpha by Delegates.observable(1f) { _, _, newValue -> 24 | overriddenAlphaPercentage.set(newValue) 25 | } 26 | private val effect = RecursiveFadeEffect(isOverridden, overriddenAlphaPercentage) 27 | 28 | override fun beforeTransition(component: UIComponent) { 29 | effect.bindComponent(component) 30 | component.effects.add(effect) 31 | effect.setup() 32 | 33 | alpha = 1f 34 | isOverridden.set(true) 35 | } 36 | 37 | override fun doTransition(component: UIComponent, constraints: AnimatingConstraints) { 38 | constraints.setExtraDelay(time) 39 | component.apply { 40 | ::alpha.animate(animationType, time, 0f) 41 | } 42 | } 43 | 44 | override fun afterTransition(component: UIComponent) { 45 | effect.remove() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/dsl/constraints.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.dsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.* 5 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 6 | import gg.essential.elementa.constraints.MinConstraint 7 | import gg.essential.elementa.state.State 8 | import java.awt.Color 9 | 10 | infix fun SuperConstraint.coerceAtLeast(minConstraint: SuperConstraint) = 11 | CoerceAtLeastConstraint(this, minConstraint) 12 | 13 | infix fun SuperConstraint.coerceAtMost(minConstraint: SuperConstraint) = 14 | CoerceAtMostConstraint(this, minConstraint) 15 | 16 | fun SuperConstraint.coerceIn(minConstraint: SuperConstraint, maxConstraint: SuperConstraint) = 17 | CoerceInConstraint(this, minConstraint, maxConstraint) 18 | 19 | operator fun SuperConstraint.plus(other: SuperConstraint) = 20 | AdditiveConstraint(this, other) 21 | 22 | operator fun SuperConstraint.minus(other: SuperConstraint) = 23 | SubtractiveConstraint(this, other) 24 | 25 | operator fun SuperConstraint.times(factor: Number) = ScaleConstraint(this, factor.toFloat()) 26 | operator fun SuperConstraint.times(factor: State) = ScaleConstraint(this, 0f).bindValue(factor.map { it.toFloat() }) 27 | 28 | operator fun SuperConstraint.div(factor: Number) = ScaleConstraint(this, 1f / factor.toFloat()) 29 | operator fun SuperConstraint.div(factor: State) = ScaleConstraint(this, 0f).bindValue(factor.map { 1f / it.toFloat() }) 30 | 31 | fun max(first: SuperConstraint, second: SuperConstraint) = MaxConstraint(first, second) 32 | 33 | fun min(first: SuperConstraint, second: SuperConstraint) = MinConstraint(first, second) 34 | 35 | infix fun > U.boundTo(component: UIComponent) = apply { this.to(component) } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/transitions/RecursiveFadeInTransition.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.transitions 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.animation.AnimatingConstraints 5 | import gg.essential.elementa.constraints.animation.Animations 6 | import gg.essential.elementa.effects.RecursiveFadeEffect 7 | import gg.essential.elementa.state.BasicState 8 | import kotlin.properties.Delegates 9 | 10 | /** 11 | * Fades a component and all of its children to alpha 100%. When 12 | * the transition starts, the alpha is set to 0%, and climbs to 13 | * 100% during the transition. When the transition is over, the 14 | * effect is removed and all of the component alphas will be 15 | * set to their original value. 16 | */ 17 | class RecursiveFadeInTransition @JvmOverloads constructor( 18 | private val time: Float = 1f, 19 | private val animationType: Animations = Animations.OUT_EXP 20 | ) : Transition() { 21 | private val isOverridden = BasicState(false) 22 | private val overriddenAlphaPercentage = BasicState(0f) 23 | private var alpha by Delegates.observable(0f) { _, _, newValue -> 24 | overriddenAlphaPercentage.set(newValue) 25 | } 26 | private val effect = RecursiveFadeEffect(isOverridden, overriddenAlphaPercentage) 27 | 28 | override fun beforeTransition(component: UIComponent) { 29 | effect.bindComponent(component) 30 | component.effects.add(effect) 31 | effect.setup() 32 | 33 | alpha = 0f 34 | isOverridden.set(true) 35 | } 36 | 37 | override fun doTransition(component: UIComponent, constraints: AnimatingConstraints) { 38 | constraints.setExtraDelay(time) 39 | component.apply { 40 | ::alpha.animate(animationType, time, 1f) 41 | } 42 | } 43 | 44 | override fun afterTransition(component: UIComponent) { 45 | effect.remove() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/MaxImageConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.components.UIImage 5 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 6 | import java.lang.UnsupportedOperationException 7 | 8 | /** 9 | * Tries to expand to fill all of the remaining width/height available in this component's 10 | * parent. 11 | */ 12 | class MaxImageConstraint : WidthConstraint, HeightConstraint { 13 | override var cachedValue = 0f 14 | override var recalculate = true 15 | override var constrainTo: UIComponent? = null 16 | 17 | override fun getWidthImpl(component: UIComponent): Float { 18 | if (component !is UIImage) throw UnsupportedOperationException("MaxImageConstraint is not available in this context!") 19 | return (constrainTo ?: component.parent).getRight() - component.getLeft() 20 | } 21 | 22 | override fun getHeightImpl(component: UIComponent): Float { 23 | if (component !is UIImage) throw UnsupportedOperationException("MaxImageConstraint is not available in this context!") 24 | return (constrainTo ?: component.parent).getBottom() - component.getTop() 25 | } 26 | 27 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 28 | when (type) { 29 | ConstraintType.WIDTH -> { 30 | visitor.visitParent(ConstraintType.X) 31 | visitor.visitParent(ConstraintType.WIDTH) 32 | visitor.visitSelf(ConstraintType.X) 33 | } 34 | ConstraintType.HEIGHT -> { 35 | visitor.visitParent(ConstraintType.Y) 36 | visitor.visitParent(ConstraintType.HEIGHT) 37 | visitor.visitSelf(ConstraintType.Y) 38 | } 39 | else -> throw IllegalArgumentException(type.prettyName) 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/debug/ConstraintDebugger.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.debug 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.ConstraintType 5 | import gg.essential.elementa.constraints.HeightConstraint 6 | import gg.essential.elementa.constraints.RadiusConstraint 7 | import gg.essential.elementa.constraints.SuperConstraint 8 | import gg.essential.elementa.constraints.WidthConstraint 9 | import gg.essential.elementa.constraints.XConstraint 10 | import gg.essential.elementa.constraints.YConstraint 11 | import gg.essential.elementa.utils.roundToRealPixels 12 | 13 | internal interface ConstraintDebugger { 14 | fun evaluate(constraint: SuperConstraint, type: ConstraintType, component: UIComponent): Float 15 | 16 | fun invokeImpl(constraint: SuperConstraint, type: ConstraintType, component: UIComponent): Float = 17 | when (type) { 18 | ConstraintType.X -> (constraint as XConstraint).getXPositionImpl(component).roundToRealPixels() 19 | ConstraintType.Y -> (constraint as YConstraint).getYPositionImpl(component).roundToRealPixels() 20 | ConstraintType.WIDTH -> (constraint as WidthConstraint).getWidthImpl(component).roundToRealPixels() 21 | ConstraintType.HEIGHT -> (constraint as HeightConstraint).getHeightImpl(component).roundToRealPixels() 22 | ConstraintType.RADIUS -> (constraint as RadiusConstraint).getRadiusImpl(component) 23 | else -> throw UnsupportedOperationException() 24 | } 25 | } 26 | 27 | internal var constraintDebugger: ConstraintDebugger? = null 28 | 29 | internal inline fun withDebugger(debugger: ConstraintDebugger, block: () -> Unit) { 30 | val prevDebugger = constraintDebugger 31 | constraintDebugger = debugger 32 | try { 33 | block() 34 | } finally { 35 | constraintDebugger = prevDebugger 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/font/FontProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.font 2 | 3 | import gg.essential.elementa.constraints.SuperConstraint 4 | import gg.essential.universal.UMatrixStack 5 | import java.awt.Color 6 | 7 | interface FontProvider : SuperConstraint { 8 | fun getStringWidth(string: String, pointSize: Float): Float 9 | 10 | fun getStringHeight(string: String, pointSize: Float): Float 11 | 12 | // Note: Even though this method has a default implementation, it should in all cases be implemented. 13 | // The default implementation exists only for backwards compatibility. 14 | fun drawString( 15 | matrixStack: UMatrixStack, 16 | string: String, 17 | color: Color, 18 | x: Float, 19 | y: Float, 20 | originalPointSize: Float, //Unused for MC font 21 | scale: Float, 22 | shadow: Boolean = true, 23 | shadowColor: Color? = null 24 | ): Unit = matrixStack.runWithGlobalState { 25 | @Suppress("DEPRECATION") 26 | drawString(string, color, x, y, originalPointSize, scale, shadow, shadowColor) 27 | } 28 | 29 | @Deprecated(UMatrixStack.Compat.DEPRECATED, ReplaceWith("drawString(matrixStack, string, color, x, y, originalPointSize, scale, shadow, shadowColor)")) 30 | fun drawString( 31 | string: String, 32 | color: Color, 33 | x: Float, 34 | y: Float, 35 | originalPointSize: Float, 36 | scale: Float, 37 | shadow: Boolean = true, 38 | shadowColor: Color? = null 39 | ): Unit = drawString( 40 | UMatrixStack(), 41 | string, 42 | color, 43 | x, 44 | y, 45 | originalPointSize, 46 | scale, 47 | shadow, 48 | shadowColor 49 | ) 50 | 51 | fun getBaseLineHeight(): Float 52 | 53 | fun getShadowHeight(): Float 54 | 55 | fun getBelowLineHeight(): Float 56 | } -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/state.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.state.State 4 | import gg.essential.elementa.unstable.common.onSetValueAndNow 5 | import gg.essential.elementa.unstable.state.v2.combinators.map 6 | import gg.essential.elementa.unstable.state.v2.effect 7 | import gg.essential.elementa.unstable.state.v2.toV2 8 | import gg.essential.elementa.unstable.state.v2.State as StateV2 9 | 10 | @Deprecated("Using StateV1 is discouraged, use StateV2 instead") 11 | fun Modifier.then(state: State): Modifier { 12 | return this then { 13 | var reverse: (() -> Unit)? = null 14 | 15 | val cleanupState = state.onSetValueAndNow { 16 | reverse?.invoke() 17 | reverse = it.applyToComponent(this) 18 | }; 19 | 20 | { 21 | cleanupState() 22 | reverse?.invoke() 23 | reverse = null 24 | } 25 | } 26 | } 27 | 28 | fun Modifier.then(state: StateV2): Modifier { 29 | return this then component@{ 30 | var reverse: (() -> Unit)? = null 31 | 32 | val cleanupState = effect(this) { 33 | reverse?.invoke() 34 | reverse = state().applyToComponent(this@component) 35 | }; 36 | 37 | { 38 | cleanupState() 39 | reverse?.invoke() 40 | reverse = null 41 | } 42 | } 43 | } 44 | 45 | @Suppress("DeprecatedCallableAddReplaceWith") 46 | @Deprecated("Using StateV1 is discouraged, use StateV2 instead") 47 | fun Modifier.whenTrue(state: State, activeModifier: Modifier, inactiveModifier: Modifier = Modifier): Modifier = 48 | then(state.toV2().map { if (it) activeModifier else inactiveModifier }) 49 | 50 | fun Modifier.whenTrue(state: StateV2, activeModifier: Modifier, inactiveModifier: Modifier = Modifier): Modifier = 51 | then(state.map { if (it) activeModifier else inactiveModifier }) -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/updateFunc.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components 2 | 3 | import gg.essential.elementa.ElementaVersion 4 | import gg.essential.elementa.UIComponent 5 | import gg.essential.elementa.UIComponent.Flags 6 | 7 | /** 8 | * Called once at the start of every frame to update any animations and miscellaneous state. 9 | * 10 | * @param dt Time (in seconds) since last frame 11 | * @param dtMs Time (in milliseconds) since last frame 12 | * 13 | * This differs from `(dt / 1000).toInt()` in that it will account for the fractional milliseconds which would 14 | * otherwise be lost to rounding. E.g. if there are three frames each lasting 16.4ms, 15 | * `(dt / 1000).toInt()` would be 16 each time, but `dtMs` will be 16 on the first two frames and 17 on the third. 16 | */ 17 | typealias UpdateFunc = (dt: Float, dtMs: Int) -> Unit 18 | 19 | internal val NOP_UPDATE_FUNC: UpdateFunc = { _, _ -> } 20 | 21 | internal class NopUpdateFuncList(override val size: Int) : AbstractList() { 22 | override fun get(index: Int): UpdateFunc = NOP_UPDATE_FUNC 23 | } 24 | 25 | /** 26 | * Internal utility for components which used to use `animationFrame`, and therefore still have to do that for backwards 27 | * compatibility until v8 is enabled, but which then use UpdateFunc once v8 is enabled. 28 | */ 29 | internal fun UIComponent.addUpdateFuncOnV8ReplacingAnimationFrame(func: UpdateFunc) { 30 | // we override animationFrame only for backwards compatibility and use this UpdateFunc on newer versions 31 | ownFlags -= Flags.RequiresAnimationFrame 32 | 33 | addUpdateFunc(object : UpdateFunc { 34 | override fun invoke(dt: Float, dtMs: Int) { 35 | if (Window.of(this@addUpdateFuncOnV8ReplacingAnimationFrame).version < ElementaVersion.v8) { 36 | // handled by animationFrame 37 | removeUpdateFunc(this) 38 | return 39 | } 40 | func(dt, dtMs) 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/TextAspectConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.components.UIText 5 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 6 | import gg.essential.universal.UGraphics 7 | import java.lang.UnsupportedOperationException 8 | 9 | /** 10 | * For size: 11 | * Sets the width/height to be [value] multiple of its own height/width respectively. 12 | * 13 | * For position: 14 | * Sets the x/y position to be [value] multiple of its own y/x position respectively. 15 | */ 16 | class TextAspectConstraint : WidthConstraint, HeightConstraint { 17 | override var cachedValue = 0f 18 | override var recalculate = true 19 | override var constrainTo: UIComponent? = null 20 | 21 | override fun getWidthImpl(component: UIComponent): Float { 22 | val text = (component as? UIText)?.getText() ?: throw IllegalStateException("TextAspectConstraint can only be used in UIText components") 23 | return UGraphics.getStringWidth(text) * component.getHeight() / 9 24 | } 25 | 26 | override fun getHeightImpl(component: UIComponent): Float { 27 | val text = (component as? UIText)?.getText() ?: throw IllegalStateException("TextAspectConstraint can only be used in UIText components") 28 | return 9 * component.getWidth() / UGraphics.getStringWidth(text) 29 | } 30 | 31 | override fun to(component: UIComponent) = apply { 32 | throw UnsupportedOperationException("Constraint.to(UIComponent) is not available in this context!") 33 | } 34 | 35 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 36 | when (type) { 37 | ConstraintType.WIDTH -> visitor.visitSelf(ConstraintType.HEIGHT) 38 | ConstraintType.HEIGHT -> visitor.visitSelf(ConstraintType.WIDTH) 39 | else -> throw IllegalArgumentException(type.prettyName) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/utils/extensions.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.utils 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.components.Window 5 | import gg.essential.universal.UGraphics.CommonVertexFormats 6 | import gg.essential.universal.UResolution 7 | import gg.essential.universal.shader.BlendState 8 | import gg.essential.universal.shader.UShader 9 | import java.awt.Color 10 | import kotlin.math.abs 11 | import kotlin.math.round 12 | import kotlin.math.sign 13 | 14 | fun Float.guiHint(roundDown: Boolean) = UIComponent.guiHint(this, roundDown) 15 | fun Double.guiHint(roundDown: Boolean) = UIComponent.guiHint(this, roundDown) 16 | 17 | fun Float.roundToRealPixels(): Float { 18 | val factor = UResolution.scaleFactor.toFloat() 19 | return round(this * factor).let { if (it == 0f && abs(this) > 0.001f) sign(this) else it } / factor 20 | } 21 | fun Double.roundToRealPixels(): Double { 22 | val factor = UResolution.scaleFactor 23 | return round(this * factor).let { if (it == 0.0 && abs(this) > 0.001) sign(this) else it } / factor 24 | } 25 | 26 | fun Color.withAlpha(alpha: Int) = Color(this.red, this.green, this.blue, alpha) 27 | fun Color.withAlpha(alpha: Float) = Color(this.red, this.green, this.blue, (alpha * 255).toInt()) 28 | fun Color.invisible() = withAlpha(0) 29 | 30 | operator fun Color.component1() = this.red 31 | operator fun Color.component2() = this.green 32 | operator fun Color.component3() = this.blue 33 | operator fun Color.component4() = this.alpha 34 | 35 | @Deprecated("Stops working in 1.21.5, see UGraphics.Globals") 36 | @Suppress("DEPRECATION") 37 | internal fun UShader.Companion.readFromLegacyShader(vertName: String, fragName: String, blendState: BlendState) = 38 | fromLegacyShader(readElementaShaderSource(vertName, "vsh"), readElementaShaderSource(fragName, "fsh"), blendState) 39 | 40 | internal fun readElementaShaderSource(name: String, ext: String) = 41 | Window::class.java.getResource("/shaders/$name.$ext").readText() 42 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/AspectConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | 6 | /** 7 | * For size: 8 | * Sets the width/height to be [value] multiple of its own height/width respectively. 9 | * 10 | * For position: 11 | * Sets the x/y position to be [value] multiple of its own y/x position respectively. 12 | */ 13 | class AspectConstraint @JvmOverloads constructor(val value: Float = 1f) : PositionConstraint, SizeConstraint { 14 | override var cachedValue = 0f 15 | override var recalculate = true 16 | override var constrainTo: UIComponent? = null 17 | 18 | override fun getXPositionImpl(component: UIComponent): Float { 19 | return (constrainTo ?: component).getTop() * value 20 | } 21 | 22 | override fun getYPositionImpl(component: UIComponent): Float { 23 | return (constrainTo ?: component).getLeft()* value 24 | } 25 | 26 | override fun getWidthImpl(component: UIComponent): Float { 27 | return (constrainTo ?: component).getHeight() * value 28 | } 29 | 30 | override fun getHeightImpl(component: UIComponent): Float { 31 | return (constrainTo ?: component).getWidth() * value 32 | } 33 | 34 | override fun getRadiusImpl(component: UIComponent): Float { 35 | return (constrainTo ?: component).getRadius() * value 36 | } 37 | 38 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 39 | when (type) { 40 | ConstraintType.X -> visitor.visitSelf(ConstraintType.Y) 41 | ConstraintType.Y -> visitor.visitSelf(ConstraintType.X) 42 | ConstraintType.WIDTH -> visitor.visitSelf(ConstraintType.HEIGHT) 43 | ConstraintType.HEIGHT -> visitor.visitSelf(ConstraintType.WIDTH) 44 | ConstraintType.RADIUS -> {} // TODO: ??? 45 | else -> throw IllegalArgumentException(type.prettyName) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/utils/ResourceCache.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.utils 2 | 3 | import gg.essential.elementa.components.UIImage 4 | import gg.essential.elementa.components.image.CacheableImage 5 | import gg.essential.elementa.components.image.MSDFComponent 6 | import gg.essential.elementa.components.inspector.Inspector 7 | import java.awt.image.BufferedImage 8 | import java.util.concurrent.CompletableFuture 9 | import java.util.concurrent.ConcurrentHashMap 10 | import javax.imageio.ImageIO 11 | 12 | class ResourceCache(val size: Int = 50) { 13 | private val cacheMap = ConcurrentHashMap() 14 | 15 | fun getUIImage(path: String): CacheableImage { 16 | if (cacheMap.size > size) 17 | cacheMap.clear() 18 | val cachedImage = cacheMap.computeIfAbsent(path) { pth -> 19 | UIImage(CompletableFuture.supplyAsync { 20 | ImageIO.read(this::class.java.getResourceAsStream(pth)) 21 | }) 22 | } 23 | return UIImage(CompletableFuture.completedFuture(null)).also { 24 | cachedImage.supply(it) 25 | } 26 | } 27 | 28 | fun invalidateAll() { 29 | cacheMap.clear() 30 | } 31 | 32 | fun invalidate(path: String): Boolean { 33 | return cacheMap.remove(path) != null 34 | } 35 | 36 | fun getMSDFComponent(path: String): MSDFComponent { 37 | if (cacheMap.size > size) 38 | cacheMap.clear() 39 | val cachedImage = cacheMap.computeIfAbsent(path) { pth -> 40 | MSDFComponent(CompletableFuture.supplyAsync { 41 | ImageIO.read(this::class.java.getResourceAsStream(pth)) 42 | }) 43 | } 44 | return MSDFComponent(CompletableFuture.completedFuture(null)).also { 45 | cachedImage.supply(it) 46 | } 47 | } 48 | 49 | private companion object { 50 | init { 51 | Inspector.registerComponentFactory(ResourceCache::class.java) 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /unstable/statev2/src/test/kotlin/gg/essential/elementa/unstable/state/v2/ObservedLongTest.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ForTestingOnly::class) 2 | 3 | package gg.essential.elementa.unstable.state.v2 4 | 5 | import kotlin.test.Test 6 | import kotlin.test.assertEquals 7 | 8 | class ObservedLongTest { 9 | private fun explore(range: ClosedRange, func: (ObservedLong) -> R): List = 10 | explore(range, ::ObservedLong, func) 11 | 12 | @Test 13 | fun testGetValue() { 14 | assertEquals(listOf(-3L, -2, -1, 0, 1, 2, 3), explore(-3L..3) { it.getValue() }) 15 | } 16 | 17 | @Test 18 | fun testComparisons() { 19 | assertEquals(listOf(false, true, false), explore(0L..5L) { it.equal(2) }) 20 | assertEquals(listOf(true, false), explore(2L..5L) { it.equal(2) }) 21 | assertEquals(listOf(false, true), explore(0L..2L) { it.equal(2) }) 22 | 23 | assertEquals(listOf(true, false), explore(0L..5L) { it.lessOrEqual(2) }) 24 | assertEquals(listOf(true, false), explore(0L..5L) { it.less(2) }) 25 | assertEquals(listOf(false, true), explore(0L..5L) { it.greaterOrEqual(2) }) 26 | assertEquals(listOf(false, true), explore(0L..5L) { it.greater(2) }) 27 | 28 | assertEquals(listOf(true, false), explore(2L..5L) { it.lessOrEqual(2) }) 29 | assertEquals(listOf(false), explore(2L..5L) { it.less(2) }) 30 | assertEquals(listOf(true), explore(2L..5L) { it.greaterOrEqual(2) }) 31 | assertEquals(listOf(false, true), explore(2L..5L) { it.greater(2) }) 32 | } 33 | 34 | @Test 35 | fun testTimes() { 36 | assertEquals(listOf(-15L, -10, -5, 0, 5, 10, 15), explore(-3L..3) { (it * 5).getValue() }) 37 | assertEquals(listOf(15L, 10, 5, 0, -5, -10, -15), explore(-3L..3) { (it * -5).getValue() }) 38 | } 39 | 40 | @Test 41 | fun testDiv() { 42 | assertEquals(listOf(-3L, -2, -1, 0, 1, 2, 3), explore(-15L..15) { (it / 5).getValue() }) 43 | assertEquals(listOf(3L, 2, 1, 0, -1, -2, -3), explore(-15L..15) { (it / -5).getValue() }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/RoundingConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | import kotlin.math.ceil 6 | import kotlin.math.floor 7 | import kotlin.math.round 8 | 9 | /** 10 | * Rounds a constraint to an Int value using one of three modes: floor, 11 | * ceil, and rounding. Rounding is the default. 12 | */ 13 | class RoundingConstraint @JvmOverloads constructor( 14 | val constraint: SuperConstraint, 15 | var roundingMode: Mode = Mode.Round 16 | ) : MasterConstraint { 17 | override var cachedValue = 0f 18 | override var recalculate = true 19 | override var constrainTo: UIComponent? = null 20 | 21 | override fun getXPositionImpl(component: UIComponent): Float { 22 | return doRound((constraint as XConstraint).getXPositionImpl(component)) 23 | } 24 | 25 | override fun getYPositionImpl(component: UIComponent): Float { 26 | return doRound((constraint as YConstraint).getYPositionImpl(component)) 27 | } 28 | 29 | override fun getWidthImpl(component: UIComponent): Float { 30 | return doRound((constraint as WidthConstraint).getWidthImpl(component)) 31 | } 32 | 33 | override fun getHeightImpl(component: UIComponent): Float { 34 | return doRound((constraint as HeightConstraint).getHeightImpl(component)) 35 | } 36 | 37 | override fun getRadiusImpl(component: UIComponent): Float { 38 | return doRound((constraint as RadiusConstraint).getRadiusImpl(component)) 39 | } 40 | 41 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 42 | constraint.visit(visitor, type) 43 | } 44 | 45 | private fun doRound(number: Float) = when (roundingMode) { 46 | Mode.Floor -> floor(number) 47 | Mode.Ceil -> ceil(number) 48 | Mode.Round -> round(number) 49 | } 50 | 51 | enum class Mode { 52 | Floor, 53 | Ceil, 54 | Round, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/RelativeWindowConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.components.Window 5 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 6 | 7 | /** 8 | * Sets this component's X/Y position or width/height to be some percentage 9 | * of the Window 10 | */ 11 | class RelativeWindowConstraint @JvmOverloads constructor(val value: Float = 1f) : PositionConstraint, SizeConstraint { 12 | override var cachedValue = 0f 13 | override var recalculate = true 14 | override var constrainTo: UIComponent? = null 15 | 16 | override fun getXPositionImpl(component: UIComponent): Float { 17 | ensureConstrainedToWindow(component) 18 | return constrainTo!!.getLeft() + getWidth(component) 19 | } 20 | 21 | override fun getYPositionImpl(component: UIComponent): Float { 22 | ensureConstrainedToWindow(component) 23 | return constrainTo!!.getTop() + getHeight(component) 24 | } 25 | 26 | override fun getWidthImpl(component: UIComponent): Float { 27 | ensureConstrainedToWindow(component) 28 | return constrainTo!!.getWidth() * value 29 | } 30 | 31 | override fun getHeightImpl(component: UIComponent): Float { 32 | ensureConstrainedToWindow(component) 33 | return constrainTo!!.getHeight() * value 34 | } 35 | 36 | override fun getRadiusImpl(component: UIComponent): Float { 37 | ensureConstrainedToWindow(component) 38 | return (constrainTo!!.getWidth() * value) / 2f 39 | } 40 | 41 | override fun to(component: UIComponent): SuperConstraint { 42 | throw IllegalStateException("RelativeWindowConstraint cannot be bound to components") 43 | } 44 | 45 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 46 | // TODO: Some kind of visitWindow method? 47 | } 48 | 49 | private fun ensureConstrainedToWindow(component: UIComponent) { 50 | if (constrainTo == null) 51 | constrainTo = Window.of(component) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/set.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2 2 | 3 | import gg.essential.elementa.unstable.state.v2.collections.* 4 | 5 | typealias SetState = State> 6 | typealias MutableSetState = MutableState> 7 | 8 | fun State>.toSetState(): SetState { 9 | var oldSet = MutableTrackedSet() 10 | return memo { 11 | val newSet = get() 12 | oldSet.applyChanges(TrackedSet.Change.estimate(oldSet, newSet)).also { oldSet = it } 13 | } 14 | } 15 | 16 | fun SetState.mapChanges(init: (TrackedSet) -> U, update: (old: U, changes: Sequence>) -> U): State { 17 | var trackedSet: TrackedSet? = null 18 | var trackedValue: U? = null 19 | return memo { 20 | val newSet = get() 21 | val oldSet = trackedSet 22 | val newValue = 23 | if (oldSet == null) { 24 | init(newSet) 25 | } else { 26 | @Suppress("UNCHECKED_CAST") 27 | update(trackedValue as U, newSet.getChangesSince(oldSet)) 28 | } 29 | 30 | trackedSet = newSet 31 | trackedValue = newValue 32 | 33 | newValue 34 | } 35 | } 36 | 37 | fun SetState.mapChange(init: (TrackedSet) -> U, update: (old: U, change: TrackedSet.Change) -> U): State = 38 | mapChanges(init) { old, changes -> changes.fold(old, update) } 39 | 40 | fun mutableSetState(vararg elements: T): MutableSetState = 41 | mutableStateOf(MutableTrackedSet(mutableSetOf(*elements))) 42 | 43 | fun MutableSetState.add(element: T) = set { it.add(element) } 44 | fun MutableSetState.addAll(toAdd: Collection) = set { it.addAll(toAdd) } 45 | fun MutableSetState.setAll(newSet: Set) = set { it.applyChanges(TrackedSet.Change.estimate(it, newSet)) } 46 | fun MutableSetState.remove(element: T) = set { it.remove(element) } 47 | fun MutableSetState.removeAll(toRemove: Collection) = set { it.removeAll(toRemove) } 48 | fun MutableSetState.clear() = set { it.clear() } 49 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/CenterConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | import gg.essential.elementa.utils.roundToRealPixels 6 | 7 | /** 8 | * Centers this box on the X or Y axis. 9 | */ 10 | class CenterConstraint : PositionConstraint { 11 | override var cachedValue = 0f 12 | override var recalculate = true 13 | override var constrainTo: UIComponent? = null 14 | 15 | override fun getXPositionImpl(component: UIComponent): Float { 16 | val parent = constrainTo ?: component.parent 17 | 18 | return if (component.isPositionCenter()) { 19 | parent.getLeft() + (parent.getWidth() / 2).roundToRealPixels() 20 | } else { 21 | parent.getLeft() + (parent.getWidth() / 2 - component.getWidth() / 2).roundToRealPixels() 22 | } 23 | } 24 | 25 | override fun getYPositionImpl(component: UIComponent): Float { 26 | val parent = constrainTo ?: component.parent 27 | 28 | return if (component.isPositionCenter()) { 29 | parent.getTop() + (parent.getHeight() / 2).roundToRealPixels() 30 | } else { 31 | parent.getTop() + (parent.getHeight() / 2 - component.getHeight() / 2).roundToRealPixels() 32 | } 33 | } 34 | 35 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 36 | when (type) { 37 | ConstraintType.X -> { 38 | visitor.visitParent(ConstraintType.X) 39 | visitor.visitParent(ConstraintType.WIDTH) 40 | if (!visitor.component.isPositionCenter()) 41 | visitor.visitSelf(ConstraintType.WIDTH) 42 | } 43 | ConstraintType.Y -> { 44 | visitor.visitParent(ConstraintType.Y) 45 | visitor.visitParent(ConstraintType.HEIGHT) 46 | if (!visitor.component.isPositionCenter()) 47 | visitor.visitSelf(ConstraintType.HEIGHT) 48 | } 49 | else -> throw IllegalArgumentException(type.prettyName) 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/MaxConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | import kotlin.math.max 6 | 7 | class MaxConstraint(val first: SuperConstraint, val second: SuperConstraint) : MasterConstraint { 8 | override var cachedValue = 0f 9 | override var recalculate = true 10 | override var constrainTo: UIComponent? = null 11 | 12 | @Deprecated("See [ElementaVersion.V8].") 13 | @Suppress("DEPRECATION") 14 | override fun animationFrame() { 15 | super.animationFrame() 16 | first.animationFrame() 17 | second.animationFrame() 18 | } 19 | 20 | override fun getWidthImpl(component: UIComponent): Float { 21 | return max((first as WidthConstraint).getWidth(component), (second as WidthConstraint).getWidth(component)) 22 | } 23 | 24 | override fun getHeightImpl(component: UIComponent): Float { 25 | return max((first as HeightConstraint).getHeight(component), (second as HeightConstraint).getHeight(component)) 26 | } 27 | 28 | override fun getRadiusImpl(component: UIComponent): Float { 29 | return max((first as RadiusConstraint).getRadius(component), (second as RadiusConstraint).getRadius(component)) 30 | } 31 | 32 | override fun getXPositionImpl(component: UIComponent): Float { 33 | return max((first as XConstraint).getXPosition(component), (second as XConstraint).getXPosition(component)) 34 | } 35 | 36 | override fun getYPositionImpl(component: UIComponent): Float { 37 | return max((first as YConstraint).getYPosition(component), (second as YConstraint).getYPosition(component)) 38 | } 39 | 40 | override fun to(component: UIComponent) = apply { 41 | throw UnsupportedOperationException("Constraint.to(UIComponent) is not available in this context, please apply this to the components beforehand.") 42 | } 43 | 44 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 45 | first.visit(visitor, type, setNewConstraint = false) 46 | second.visit(visitor, type, setNewConstraint = false) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/MinConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | import kotlin.math.min 6 | 7 | class MinConstraint(val first: SuperConstraint, val second: SuperConstraint) : MasterConstraint { 8 | override var cachedValue = 0f 9 | override var recalculate = true 10 | override var constrainTo: UIComponent? = null 11 | 12 | @Deprecated("See [ElementaVersion.V8].") 13 | @Suppress("DEPRECATION") 14 | override fun animationFrame() { 15 | super.animationFrame() 16 | first.animationFrame() 17 | second.animationFrame() 18 | } 19 | 20 | override fun getWidthImpl(component: UIComponent): Float { 21 | return min((first as WidthConstraint).getWidth(component), (second as WidthConstraint).getWidth(component)) 22 | } 23 | 24 | override fun getHeightImpl(component: UIComponent): Float { 25 | return min((first as HeightConstraint).getHeight(component), (second as HeightConstraint).getHeight(component)) 26 | } 27 | 28 | override fun getRadiusImpl(component: UIComponent): Float { 29 | return min((first as RadiusConstraint).getRadius(component), (second as RadiusConstraint).getRadius(component)) 30 | } 31 | 32 | override fun getXPositionImpl(component: UIComponent): Float { 33 | return min((first as XConstraint).getXPosition(component), (second as XConstraint).getXPosition(component)) 34 | } 35 | 36 | override fun getYPositionImpl(component: UIComponent): Float { 37 | return min((first as YConstraint).getYPosition(component), (second as YConstraint).getYPosition(component)) 38 | } 39 | 40 | override fun to(component: UIComponent) = apply { 41 | throw UnsupportedOperationException("Constraint.to(UIComponent) is not available in this context, please apply this to the components beforehand.") 42 | } 43 | 44 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 45 | first.visit(visitor, type, setNewConstraint = false) 46 | second.visit(visitor, type, setNewConstraint = false) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/impl/Impl.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.impl 2 | 3 | import gg.essential.elementa.state.v2.ReferenceHolder 4 | import gg.essential.elementa.unstable.state.v2.DelegatingMutableState 5 | import gg.essential.elementa.unstable.state.v2.DelegatingState 6 | import gg.essential.elementa.unstable.state.v2.MutableState 7 | import gg.essential.elementa.unstable.state.v2.Observer 8 | import gg.essential.elementa.unstable.state.v2.ReferenceHolderImpl 9 | import gg.essential.elementa.unstable.state.v2.State 10 | import gg.essential.elementa.unstable.state.v2.mutableStateOf 11 | 12 | internal interface Impl { 13 | fun mutableState(value: T): MutableState 14 | fun memo(func: Observer.() -> T): State 15 | fun effect(referenceHolder: ReferenceHolder, func: Observer.() -> Unit): () -> Unit 16 | 17 | fun stateDelegatingTo(state: State): DelegatingState = 18 | object : DelegatingState { 19 | private val target = mutableStateOf(state) 20 | override fun rebind(newState: State) = target.set(newState) 21 | override fun Observer.get(): T = target()() 22 | } 23 | 24 | fun mutableStateDelegatingTo(state: MutableState): DelegatingMutableState = 25 | object : DelegatingMutableState { 26 | private val target = mutableStateOf(state) 27 | override fun set(mapper: (T) -> T) = target.getUntracked().set(mapper) 28 | override fun rebind(newState: MutableState) = target.set(newState) 29 | override fun Observer.get(): T = target()() 30 | } 31 | 32 | fun derivedState( 33 | initialValue: T, 34 | builder: (owner: ReferenceHolder, derivedState: MutableState) -> Unit, 35 | ): State = 36 | object : State { 37 | val referenceHolder = ReferenceHolderImpl() // keep this alive for at least as long as the returned state 38 | val derivedState = mutableStateOf(initialValue) 39 | init { 40 | builder(referenceHolder, derivedState) 41 | } 42 | 43 | override fun Observer.get(): T = derivedState() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/dsl/basicConstraints.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.dsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.* 5 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 6 | import java.awt.Color 7 | 8 | abstract class BasicConstraint(defaultValue: T) : SuperConstraint { 9 | override var cachedValue = defaultValue 10 | override var recalculate = true 11 | override var constrainTo: UIComponent? = null 12 | 13 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) {} 14 | } 15 | 16 | fun basicXConstraint(calculator: (component: UIComponent) -> Float): XConstraint = 17 | object : BasicConstraint(0f), XConstraint { 18 | override fun getXPositionImpl(component: UIComponent) = calculator(component) 19 | } 20 | 21 | fun basicYConstraint(calculator: (component: UIComponent) -> Float): YConstraint = 22 | object : BasicConstraint(0f), YConstraint { 23 | override fun getYPositionImpl(component: UIComponent) = calculator(component) 24 | } 25 | 26 | fun basicWidthConstraint(calculator: (component: UIComponent) -> Float): WidthConstraint = 27 | object : BasicConstraint(0f), WidthConstraint { 28 | override fun getWidthImpl(component: UIComponent) = calculator(component) 29 | } 30 | 31 | fun basicHeightConstraint(calculator: (component: UIComponent) -> Float): HeightConstraint = 32 | object : BasicConstraint(0f), HeightConstraint { 33 | override fun getHeightImpl(component: UIComponent) = calculator(component) 34 | } 35 | 36 | fun basicRadiusConstraint(calculator: (component: UIComponent) -> Float): RadiusConstraint = 37 | object : BasicConstraint(0f), RadiusConstraint { 38 | override fun getRadiusImpl(component: UIComponent) = calculator(component) 39 | } 40 | 41 | fun basicTextScaleConstraint(calculator: (component: UIComponent) -> Float) = basicHeightConstraint(calculator) 42 | 43 | fun basicColorConstraint(calculator: (component: UIComponent) -> Color): ColorConstraint = 44 | object : BasicConstraint(Color.WHITE), ColorConstraint { 45 | override fun getColorImpl(component: UIComponent) = calculator(component) 46 | } 47 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/dsl/componentConstraints.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.dsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.* 5 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 6 | import java.awt.Color 7 | 8 | abstract class ComponentConstraint(defaultValue: T) : SuperConstraint { 9 | override var cachedValue = defaultValue 10 | override var recalculate = true 11 | override var constrainTo: UIComponent? = null 12 | 13 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) {} 14 | } 15 | 16 | fun componentXConstraint(boundComponent: UIComponent): XConstraint = 17 | object : ComponentConstraint(0f), XConstraint { 18 | override fun getXPositionImpl(component: UIComponent) = boundComponent.getLeft() - boundComponent.parent.getLeft() 19 | } 20 | 21 | fun componentYConstraint(boundComponent: UIComponent): YConstraint = 22 | object : ComponentConstraint(0f), YConstraint { 23 | override fun getYPositionImpl(component: UIComponent) = boundComponent.getTop() - boundComponent.parent.getTop() 24 | } 25 | 26 | fun componentWidthConstraint(boundComponent: UIComponent): WidthConstraint = 27 | object : ComponentConstraint(0f), WidthConstraint { 28 | override fun getWidthImpl(component: UIComponent) = boundComponent.getWidth() 29 | } 30 | 31 | fun componentHeightConstraint(boundComponent: UIComponent): HeightConstraint = 32 | object : ComponentConstraint(0f), HeightConstraint { 33 | override fun getHeightImpl(component: UIComponent) = boundComponent.getHeight() 34 | } 35 | 36 | fun componentRadiusConstraint(boundComponent: UIComponent): RadiusConstraint = 37 | object : ComponentConstraint(0f), RadiusConstraint { 38 | override fun getRadiusImpl(component: UIComponent) = boundComponent.getRadius() 39 | } 40 | 41 | fun componentTextScaleConstraint(boundComponent: UIComponent) = componentHeightConstraint(boundComponent) 42 | 43 | fun componentColorConstraint(boundComponent: UIComponent): ColorConstraint = 44 | object : ComponentConstraint(Color.WHITE), ColorConstraint { 45 | override fun getColorImpl(component: UIComponent) = boundComponent.getColor() 46 | } 47 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/basicModifiers.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.* 5 | 6 | infix fun Modifier.then(other: UIComponent.() -> () -> Unit) = this then BasicModifier(other) 7 | 8 | private class BasicModifier(private val setup: UIComponent.() -> () -> Unit) : Modifier { 9 | override fun applyToComponent(component: UIComponent): () -> Unit { 10 | return component.setup() 11 | } 12 | } 13 | 14 | class BasicXModifier(private val constraint: () -> XConstraint) : Modifier { 15 | override fun applyToComponent(component: UIComponent): () -> Unit { 16 | val oldX = component.constraints.x 17 | component.setX(constraint()) 18 | return { 19 | component.setX(oldX) 20 | } 21 | } 22 | } 23 | 24 | class BasicYModifier(private val constraint: () -> YConstraint) : Modifier { 25 | override fun applyToComponent(component: UIComponent): () -> Unit { 26 | val oldY = component.constraints.y 27 | component.setY(constraint()) 28 | return { 29 | component.setY(oldY) 30 | } 31 | } 32 | } 33 | 34 | class BasicWidthModifier(private val constraint: () -> WidthConstraint) : Modifier { 35 | override fun applyToComponent(component: UIComponent): () -> Unit { 36 | val oldWidth = component.constraints.width 37 | component.setWidth(constraint()) 38 | return { 39 | component.setWidth(oldWidth) 40 | } 41 | } 42 | } 43 | 44 | class BasicHeightModifier(private val constraint: () -> HeightConstraint) : Modifier { 45 | override fun applyToComponent(component: UIComponent): () -> Unit { 46 | val oldHeight = component.constraints.height 47 | component.setHeight(constraint()) 48 | return { 49 | component.setHeight(oldHeight) 50 | } 51 | } 52 | } 53 | 54 | class BasicColorModifier(private val constraint: () -> ColorConstraint) : Modifier { 55 | override fun applyToComponent(component: UIComponent): () -> Unit { 56 | val oldColor = component.constraints.color 57 | component.setColor(constraint()) 58 | return { 59 | component.setColor(oldColor) 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/components/input/UIPasswordInput.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.components.input 2 | 3 | import gg.essential.elementa.dsl.pixels 4 | import gg.essential.elementa.dsl.width 5 | import gg.essential.elementa.state.BasicState 6 | import gg.essential.elementa.state.MappedState 7 | import gg.essential.elementa.state.State 8 | import java.awt.Color 9 | 10 | open class UIPasswordInput @JvmOverloads constructor( 11 | passwordChar: Char = '*', 12 | placeholder: String = "", 13 | shadow: Boolean = true, 14 | selectionBackgroundColor: Color = Color.WHITE, 15 | selectionForegroundColor: Color = Color(64, 139, 229), 16 | allowInactiveSelection: Boolean = false, 17 | inactiveSelectionBackgroundColor: Color = Color(176, 176, 176), 18 | inactiveSelectionForegroundColor: Color = Color.WHITE, 19 | cursorColor: Color = Color.WHITE 20 | ) : UITextInput( 21 | placeholder, 22 | shadow, 23 | selectionBackgroundColor, 24 | selectionForegroundColor, 25 | allowInactiveSelection, 26 | inactiveSelectionBackgroundColor, 27 | inactiveSelectionForegroundColor, 28 | cursorColor 29 | ) { 30 | private val protected: MappedState = BasicState(true).map { it } 31 | private val passwordCharAsString: String = passwordChar.toString() 32 | 33 | override fun getTextForRender(): String = if (protected.get()) getProtectedString(getText()) else getText() 34 | 35 | override fun setCursorPos() { 36 | if (protected.get()) { 37 | cursorComponent.unhide() 38 | val x = passwordCharAsString.repeat(cursor.toVisualPos().column) 39 | .width(getTextScale()) - horizontalScrollingOffset 40 | cursorComponent.setX(x.pixels()) 41 | } else { 42 | super.setCursorPos() 43 | } 44 | } 45 | 46 | fun bindProtection(protectedState: BasicState) = apply { 47 | protected.rebind(protectedState) 48 | } 49 | 50 | fun isProtected(): Boolean = protected.get() 51 | fun setProtection(protected: Boolean): Unit = this.protected.set(protected) 52 | 53 | fun getProtectedString(text: String): String = passwordCharAsString.repeat(text.length) 54 | 55 | companion object { 56 | @JvmStatic 57 | fun getProtectedString(text: String, char: Char): String = char.toString().repeat(text.length) 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/resolution/ConstraintResolver.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.resolution 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.UIConstraints 5 | import gg.essential.elementa.components.Window 6 | import gg.essential.elementa.constraints.* 7 | 8 | class ConstraintResolver(window: Window) { 9 | private val graph = DirectedAcyclicGraph() 10 | 11 | init { 12 | window.forEachChild { 13 | // Color constraints are not added because they are always resolvable 14 | graph.addVertices( 15 | ResolverNode(it, it.constraints.x, ConstraintType.X), 16 | ResolverNode(it, it.constraints.y, ConstraintType.Y), 17 | ResolverNode(it, it.constraints.width, ConstraintType.WIDTH), 18 | ResolverNode(it, it.constraints.height, ConstraintType.HEIGHT), 19 | ResolverNode(it, it.constraints.textScale, ConstraintType.TEXT_SCALE), 20 | ResolverNode(it, it.constraints.radius, ConstraintType.RADIUS) 21 | ) 22 | } 23 | 24 | window.forEachChild { 25 | val visitor = ConstraintVisitor(graph, it) 26 | 27 | it.constraints.x.visit(visitor, ConstraintType.X) 28 | it.constraints.y.visit(visitor, ConstraintType.Y) 29 | it.constraints.width.visit(visitor, ConstraintType.WIDTH) 30 | it.constraints.height.visit(visitor, ConstraintType.HEIGHT) 31 | it.constraints.textScale.visit(visitor, ConstraintType.TEXT_SCALE) 32 | it.constraints.radius.visit(visitor, ConstraintType.RADIUS) 33 | } 34 | } 35 | 36 | fun getCyclicNodes() = graph.getCyclicLoop() 37 | } 38 | 39 | fun UIConstraints.getConstraint(type: ConstraintType) = when (type) { 40 | ConstraintType.X -> x 41 | ConstraintType.Y -> y 42 | ConstraintType.WIDTH -> width 43 | ConstraintType.HEIGHT -> height 44 | ConstraintType.RADIUS -> radius 45 | ConstraintType.COLOR -> color 46 | ConstraintType.TEXT_SCALE -> textScale 47 | ConstraintType.FONT_PROVIDER -> fontProvider 48 | } 49 | 50 | fun Window.forEachChild(action: (UIComponent) -> Unit) { 51 | fun helper(component: UIComponent) { 52 | action(component) 53 | component.children.forEach(::helper) 54 | } 55 | 56 | helper(this) 57 | } 58 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/utils/Matrix2.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.utils 2 | 3 | open class Matrix2( 4 | val numRows: Int, 5 | val numColumns: Int, 6 | private val defaultT: () -> T 7 | ) { 8 | private val data: List> 9 | 10 | val rows: List> 11 | get() = data 12 | 13 | val columns: List> 14 | get() = (0 until numColumns).map { c -> 15 | data.map { it[c] } 16 | } 17 | 18 | val items: List 19 | get() = data.flatten() 20 | 21 | init { 22 | if (numRows <= 0) 23 | throw IllegalArgumentException("Rows must be an integer greater than 0") 24 | if (numColumns <= 0) 25 | throw IllegalArgumentException("Column must be an integer greater than 0") 26 | 27 | data = (0 until numRows).map { 28 | (0 until numColumns).map { defaultT() }.toMutableList() 29 | } 30 | } 31 | 32 | constructor(rows: Int, columns: Int, initialValue: T) : this(rows, columns, { initialValue }) 33 | 34 | fun row(row: Int): List { 35 | rowBoundsCheck(row) 36 | return data[row] 37 | } 38 | 39 | fun column(column: Int): List { 40 | columnBoundsCheck(column) 41 | return data.map { it[column] } 42 | } 43 | 44 | operator fun get(row: Int, column: Int): T { 45 | boundsCheck(row, column) 46 | return data[row][column] 47 | } 48 | 49 | operator fun set(row: Int, column: Int, value: T) = apply { 50 | boundsCheck(row, column) 51 | data[row][column] = value 52 | } 53 | 54 | private fun boundsCheck(row: Int, column: Int) { 55 | rowBoundsCheck(row) 56 | columnBoundsCheck(column) 57 | } 58 | 59 | private fun rowBoundsCheck(row: Int) { 60 | if (row < 0 || row >= numRows) 61 | throw IllegalStateException("row must be in the range [0, ${numRows-1}]") 62 | } 63 | 64 | private fun columnBoundsCheck(column: Int) { 65 | if (column < 0 || column >= numColumns) 66 | throw IllegalStateException("column must be in the range [0, ${numColumns-1}]") 67 | } 68 | } 69 | 70 | class Matrix2b(rows: Int, columns: Int) : Matrix2(rows, columns, false) 71 | class Matrix2f(rows: Int, columns: Int) : Matrix2(rows, columns, 0f) 72 | class Matrix2d(rows: Int, columns: Int) : Matrix2(rows, columns, 0.0) 73 | class Matrix2i(rows: Int, columns: Int) : Matrix2(rows, columns, 0) 74 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/ScaleConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | import gg.essential.elementa.state.BasicState 6 | import gg.essential.elementa.state.MappedState 7 | import gg.essential.elementa.state.State 8 | 9 | class ScaleConstraint(val constraint: SuperConstraint, value: State) : MasterConstraint { 10 | constructor(constraint: SuperConstraint, value: Float) : this(constraint, BasicState(value)) 11 | override var cachedValue = 0f 12 | override var recalculate = true 13 | override var constrainTo: UIComponent? = null 14 | 15 | private val valueState: MappedState = value.map { it } 16 | var value: Float 17 | get() = valueState.get() 18 | set(value) = valueState.set(value) 19 | 20 | fun bindValue(newState: State) = apply { 21 | valueState.rebind(newState) 22 | } 23 | 24 | @Deprecated("See [ElementaVersion.V8].") 25 | @Suppress("DEPRECATION") 26 | override fun animationFrame() { 27 | super.animationFrame() 28 | constraint.animationFrame() 29 | } 30 | 31 | override fun getXPositionImpl(component: UIComponent): Float { 32 | return (constraint as XConstraint).getXPosition(component) * valueState.get() 33 | } 34 | 35 | override fun getYPositionImpl(component: UIComponent): Float { 36 | return (constraint as YConstraint).getYPosition(component) * valueState.get() 37 | } 38 | 39 | override fun getWidthImpl(component: UIComponent): Float { 40 | return (constraint as WidthConstraint).getWidth(component) * valueState.get() 41 | } 42 | 43 | override fun getHeightImpl(component: UIComponent): Float { 44 | return (constraint as HeightConstraint).getHeight(component) * valueState.get() 45 | } 46 | 47 | override fun getRadiusImpl(component: UIComponent): Float { 48 | return (constraint as RadiusConstraint).getRadius(component) * valueState.get() 49 | } 50 | 51 | override fun to(component: UIComponent): SuperConstraint { 52 | throw UnsupportedOperationException("Constraint.to(UIComponent) is not available in this context, please apply this to the components beforehand.") 53 | } 54 | 55 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 56 | constraint.visit(visitor, type, setNewConstraint = false) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/AdditiveConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | 6 | class AdditiveConstraint( 7 | val constraint1: SuperConstraint, 8 | val constraint2: SuperConstraint 9 | ) : MasterConstraint { 10 | override var cachedValue = 0f 11 | override var recalculate = true 12 | override var constrainTo: UIComponent? = null 13 | 14 | @Deprecated("See [ElementaVersion.V8].") 15 | @Suppress("DEPRECATION") 16 | override fun animationFrame() { 17 | super.animationFrame() 18 | constraint1.animationFrame() 19 | constraint2.animationFrame() 20 | } 21 | 22 | override fun getXPositionImpl(component: UIComponent): Float { 23 | return (constraint1 as XConstraint).getXPosition(component) + 24 | (constraint2 as XConstraint).getXPosition(component) - 25 | (constrainTo ?: component.parent).getLeft() 26 | } 27 | 28 | override fun getYPositionImpl(component: UIComponent): Float { 29 | return (constraint1 as YConstraint).getYPosition(component) + 30 | (constraint2 as YConstraint).getYPosition(component) - 31 | (constrainTo ?: component.parent).getTop() 32 | } 33 | 34 | override fun getWidthImpl(component: UIComponent): Float { 35 | return (constraint1 as WidthConstraint).getWidth(component) + 36 | (constraint2 as WidthConstraint).getWidth(component) 37 | } 38 | 39 | override fun getHeightImpl(component: UIComponent): Float { 40 | return (constraint1 as HeightConstraint).getHeight(component) + 41 | (constraint2 as HeightConstraint).getHeight(component) 42 | } 43 | 44 | override fun getRadiusImpl(component: UIComponent): Float { 45 | return (constraint1 as RadiusConstraint).getRadius(component) + 46 | (constraint2 as RadiusConstraint).getRadius(component) 47 | } 48 | 49 | override fun to(component: UIComponent): SuperConstraint { 50 | throw UnsupportedOperationException("Constraint.to(UIComponent) is not available in this context, please apply this to the components beforehand.") 51 | } 52 | 53 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 54 | constraint1.visit(visitor, type, setNewConstraint = false) 55 | constraint2.visit(visitor, type, setNewConstraint = false) 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/SubtractiveConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | 6 | class SubtractiveConstraint( 7 | val constraint1: SuperConstraint, 8 | val constraint2: SuperConstraint 9 | ) : MasterConstraint { 10 | override var cachedValue = 0f 11 | override var recalculate = true 12 | override var constrainTo: UIComponent? = null 13 | 14 | @Deprecated("See [ElementaVersion.V8].") 15 | @Suppress("DEPRECATION") 16 | override fun animationFrame() { 17 | super.animationFrame() 18 | constraint1.animationFrame() 19 | constraint2.animationFrame() 20 | } 21 | 22 | override fun getXPositionImpl(component: UIComponent): Float { 23 | return (constraint1 as XConstraint).getXPosition(component) - 24 | (constraint2 as XConstraint).getXPosition(component) + 25 | (constrainTo ?: component.parent).getLeft() 26 | } 27 | 28 | override fun getYPositionImpl(component: UIComponent): Float { 29 | return (constraint1 as YConstraint).getYPosition(component) - 30 | (constraint2 as YConstraint).getYPosition(component) + 31 | (constrainTo?: component.parent).getTop() 32 | } 33 | 34 | override fun getWidthImpl(component: UIComponent): Float { 35 | return (constraint1 as WidthConstraint).getWidth(component) - 36 | (constraint2 as WidthConstraint).getWidth(component) 37 | } 38 | 39 | override fun getHeightImpl(component: UIComponent): Float { 40 | return (constraint1 as HeightConstraint).getHeightImpl(component) - 41 | (constraint2 as HeightConstraint).getHeightImpl(component) 42 | } 43 | 44 | override fun getRadiusImpl(component: UIComponent): Float { 45 | return (constraint1 as RadiusConstraint).getRadiusImpl(component) - 46 | (constraint2 as RadiusConstraint).getRadiusImpl(component) 47 | } 48 | 49 | override fun to(component: UIComponent) = apply { 50 | throw UnsupportedOperationException("Constraint.to(UIComponent) is not available in this context, please apply this to the components beforehand.") 51 | } 52 | 53 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 54 | constraint1.visit(visitor, type, setNewConstraint = false) 55 | constraint2.visit(visitor, type, setNewConstraint = false) 56 | } 57 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/gg/essential/elementa/example/ExamplesGui.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.example 2 | 3 | import gg.essential.elementa.ElementaVersion 4 | import gg.essential.elementa.WindowScreen 5 | import gg.essential.elementa.components.* 6 | import gg.essential.elementa.constraints.* 7 | import gg.essential.elementa.constraints.animation.Animations 8 | import gg.essential.elementa.dsl.* 9 | import gg.essential.elementa.unstable.layoutdsl.Modifier 10 | import gg.essential.elementa.unstable.layoutdsl.gradient 11 | import gg.essential.universal.UMinecraft 12 | import gg.essential.universal.UScreen 13 | import java.awt.Color 14 | 15 | /** 16 | * List of buttons to open a specific example gui. 17 | * See ExampleGui (singular) for a well-commented example gui. 18 | */ 19 | class ExamplesGui : WindowScreen(ElementaVersion.V6) { 20 | private val container by ScrollComponent().constrain { 21 | y = 3.pixels() 22 | width = 100.percent() 23 | height = 100.percent() - (3 * 2).pixels() 24 | } childOf window 25 | 26 | init { 27 | for ((name, action) in examples) { 28 | val button = UIBlock().constrain { 29 | x = CenterConstraint() 30 | y = SiblingConstraint(padding = 3f) 31 | width = 200.pixels() 32 | height = 20.pixels() 33 | color = Color(255, 255, 255, 102).toConstraint() 34 | }.onMouseEnter { 35 | animate { 36 | setColorAnimation(Animations.OUT_EXP, 0.5f, Color(255, 255, 255, 150).toConstraint()) 37 | } 38 | }.onMouseLeave { 39 | animate { 40 | setColorAnimation(Animations.OUT_EXP, 0.5f, Color(255, 255, 255, 102).toConstraint()) 41 | } 42 | }.onMouseClick { 43 | try { 44 | UMinecraft.currentScreenObj = action() 45 | } catch (e: Exception) { 46 | e.printStackTrace() 47 | } 48 | } childOf container 49 | 50 | UIText(name).constrain { 51 | x = CenterConstraint() 52 | y = CenterConstraint() 53 | } childOf button 54 | } 55 | 56 | // Fancy background 57 | Modifier.gradient(top = Color(0x091323), Color.BLACK).applyToComponent(window) 58 | } 59 | 60 | companion object { 61 | val examples = mutableMapOf UScreen>( 62 | "ExampleGui" to ::ExampleGui, 63 | "ComponentsGui" to ::ComponentsGui, 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/alignment.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.dsl.basicXConstraint 5 | import gg.essential.elementa.dsl.basicYConstraint 6 | import gg.essential.elementa.utils.roundToRealPixels 7 | import kotlin.math.ceil 8 | import kotlin.math.floor 9 | 10 | fun interface Alignment { 11 | fun align(parentSize: Float, childSize: Float): Float 12 | 13 | fun toXConstraint() = basicXConstraint { it.parent.getLeft() + align(it.parent.getWidth(), it.getWidth()) } 14 | fun toYConstraint() = basicYConstraint { it.parent.getTop() + align(it.parent.getHeight(), it.getHeight()) } 15 | 16 | fun applyHorizontal(component: UIComponent): () -> Unit = BasicXModifier { toXConstraint() }.applyToComponent(component) 17 | fun applyVertical(component: UIComponent): () -> Unit = BasicYModifier { toYConstraint() }.applyToComponent(component) 18 | 19 | companion object { 20 | @Suppress("FunctionName") 21 | fun Start(padding: Float): Alignment = Alignment { _, _ -> padding } 22 | @Suppress("FunctionName") 23 | fun Center(roundUp: Boolean): Alignment = Alignment { parent, child -> 24 | val center = parent / 2 - child / 2 25 | if (roundUp) ceil(center) else floor(center) 26 | } 27 | @Suppress("FunctionName") 28 | fun End(padding: Float): Alignment = Alignment { parent, child -> parent - padding - child } 29 | 30 | val Start: Alignment = Start(0f) 31 | val Center: Alignment = Center(false) 32 | val End: Alignment = End(0f) 33 | 34 | val TrueCenter: Alignment = Alignment { parent, child -> (parent / 2 - child / 2).roundToRealPixels() } 35 | } 36 | } 37 | 38 | fun Modifier.alignBoth(alignment: Alignment) = alignHorizontal(alignment).alignVertical(alignment) 39 | 40 | fun Modifier.alignHorizontal(alignment: Alignment) = this then HorizontalAlignmentModifier(alignment) 41 | 42 | fun Modifier.alignVertical(alignment: Alignment) = this then VerticalAlignmentModifier(alignment) 43 | 44 | private class HorizontalAlignmentModifier(private val alignment: Alignment) : Modifier { 45 | override fun applyToComponent(component: UIComponent): () -> Unit { 46 | return alignment.applyHorizontal(component) 47 | } 48 | } 49 | 50 | private class VerticalAlignmentModifier(private val alignment: Alignment) : Modifier { 51 | override fun applyToComponent(component: UIComponent): () -> Unit { 52 | return alignment.applyVertical(component) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/collections/TrackedSet.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.collections 2 | 3 | /** 4 | * An immutable Set type that remembers the changes that have been applied to construct it, allowing one to very 5 | * cheaply obtain a "diff" between it and one of the sets it has been constructed from. 6 | * 7 | * The exact meaning of "very cheaply" may differ depending on the specific implementation but should never be worse 8 | * than `O(n+m)` where `n` is the size of both sets and `m` is the amount of changes that have happened between 9 | * two sets. In the best case it should just be `O(m)`. 10 | * 11 | * If two unrelated tracked sets are compared with each other, the result will usually just equal [Change.estimate], 12 | * which takes `O(n)`. 13 | * 14 | * Beware that even though sets of this type appear to be immutable, they are not guaranteed to be internally immutable 15 | * (for performance reasons) and as such are not generally thread-safe. 16 | */ 17 | interface TrackedSet : Set { 18 | /** Returns changes one would have to apply to [other] to obtain `this`. */ 19 | fun getChangesSince(other: TrackedSet<@UnsafeVariance E>): Sequence> 20 | 21 | data class Add(val element: E) : Change 22 | data class Remove(val element: E) : Change 23 | data class Clear(val oldElements: Set) : Change 24 | 25 | sealed interface Change { 26 | companion object { 27 | /** 28 | * Estimates the changes one would have to apply to [oldSet] to obtain [newSet]. 29 | */ 30 | fun estimate(oldSet: Set, newSet: Set): List> { 31 | return if (newSet.isEmpty()) { 32 | if (oldSet.isEmpty()) { 33 | emptyList() 34 | } else { 35 | listOf(Clear(oldSet)) 36 | } 37 | } else { 38 | val changes = mutableListOf>() 39 | 40 | for (newValue in newSet) { 41 | if (newValue !in oldSet) { 42 | changes.add(Add(newValue)) 43 | } 44 | } 45 | for (oldValue in oldSet) { 46 | if (oldValue !in newSet) { 47 | changes.add(Remove(oldValue)) 48 | } 49 | } 50 | 51 | changes 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/resolution/ConstraintVisitor.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.resolution 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.UIConstraints 5 | import gg.essential.elementa.components.Window 6 | import gg.essential.elementa.constraints.ConstraintType 7 | import gg.essential.elementa.constraints.SuperConstraint 8 | 9 | class ConstraintVisitor( 10 | private val graph: DirectedAcyclicGraph, 11 | val component: UIComponent 12 | ) { 13 | private lateinit var currentConstraint: SuperConstraint<*> 14 | private lateinit var currentConstraintType: ConstraintType 15 | 16 | fun setConstraint(constraint: SuperConstraint<*>, type: ConstraintType) { 17 | currentConstraint = constraint 18 | currentConstraintType = type 19 | } 20 | 21 | fun visitParent(type: ConstraintType) { 22 | if (!component.hasParent || component is Window || component.parent is Window) 23 | return 24 | 25 | graph.addEdge( 26 | ResolverNode(component, currentConstraint, currentConstraintType), 27 | ResolverNode(component.parent, component.parent.constraints.getConstraint(type), type) 28 | ) 29 | } 30 | 31 | fun visitSelf(type: ConstraintType) { 32 | graph.addEdge( 33 | ResolverNode(component, currentConstraint, currentConstraintType), 34 | ResolverNode(component, component.constraints.getConstraint(type), type) 35 | ) 36 | } 37 | 38 | fun visitSibling(type: ConstraintType, index: Int) { 39 | if (!component.hasParent) { 40 | throw IllegalStateException(""" 41 | Error during Elementa constraint validation: the current component has no parent, 42 | but visitSibling was called. This shouldn't be possible -- if you are seeing this, 43 | please notify an Elementa developer ASAP! 44 | """.trimIndent()) 45 | } 46 | 47 | val sibling = component.parent.children[index] 48 | 49 | graph.addEdge( 50 | ResolverNode(component, currentConstraint, currentConstraintType), 51 | ResolverNode(sibling, sibling.constraints.getConstraint(type), type) 52 | ) 53 | } 54 | 55 | fun visitChildren(type: ConstraintType) { 56 | val currNode = ResolverNode(component, currentConstraint, currentConstraintType) 57 | 58 | component.children.forEach { 59 | graph.addEdge( 60 | currNode, 61 | ResolverNode(it, it.constraints.getConstraint(type), type) 62 | ) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/transitions/ShrinkTransition.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.transitions 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.HeightConstraint 5 | import gg.essential.elementa.constraints.WidthConstraint 6 | import gg.essential.elementa.constraints.XConstraint 7 | import gg.essential.elementa.constraints.YConstraint 8 | import gg.essential.elementa.constraints.animation.AnimatingConstraints 9 | import gg.essential.elementa.constraints.animation.Animations 10 | import gg.essential.elementa.dsl.pixels 11 | import gg.essential.elementa.dsl.plus 12 | 13 | /** 14 | * Shrinks a component towards its center. 15 | */ 16 | class ShrinkTransition @JvmOverloads constructor( 17 | private val time: Float = 1f, 18 | private val animationType: Animations = Animations.OUT_EXP, 19 | private val restoreConstraints: Boolean = false 20 | ) : Transition() { 21 | private val xConstraints = mutableMapOf() 22 | private val yConstraints = mutableMapOf() 23 | private val widthConstraints = mutableMapOf() 24 | private val heightConstraints = mutableMapOf() 25 | 26 | override fun beforeTransition(component: UIComponent) { 27 | xConstraints[component] = component.constraints.x 28 | yConstraints[component] = component.constraints.y 29 | widthConstraints[component] = component.constraints.width 30 | heightConstraints[component] = component.constraints.height 31 | } 32 | 33 | override fun doTransition(component: UIComponent, constraints: AnimatingConstraints) { 34 | constraints.apply { 35 | setXAnimation(animationType, time, xConstraints[component]!! + (component.getWidth() / 2f).pixels()) 36 | setYAnimation(animationType, time, yConstraints[component]!! + (component.getHeight() / 2f).pixels()) 37 | setWidthAnimation(animationType, time, 0.pixels()) 38 | setHeightAnimation(animationType, time, 0.pixels()) 39 | } 40 | } 41 | 42 | override fun afterTransition(component: UIComponent) { 43 | if (restoreConstraints) { 44 | component.setX(xConstraints[component]!!) 45 | component.setY(yConstraints[component]!!) 46 | component.setWidth(widthConstraints[component]!!) 47 | component.setHeight(heightConstraints[component]!!) 48 | } 49 | xConstraints.remove(component) 50 | yConstraints.remove(component) 51 | widthConstraints.remove(component) 52 | heightConstraints.remove(component) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/collections/utils.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2.collections 2 | 3 | import gg.essential.elementa.state.v2.ReferenceHolder 4 | import gg.essential.elementa.unstable.state.v2.ListState 5 | import gg.essential.elementa.unstable.state.v2.effect 6 | 7 | // FIXME this is assuming there are no duplicate keys (good enough for now) 8 | fun ListState.asMap(owner: ReferenceHolder, block: (T) -> Pair): Map { 9 | var oldList = get() 10 | val map = oldList.associateTo(mutableMapOf(), block) 11 | val keys = map.keys.toMutableList() 12 | onSetValue(owner) { newList -> 13 | val changes = newList.getChangesSince(oldList).also { oldList = newList } 14 | for (change in changes) { 15 | when (change) { 16 | is TrackedList.Add -> { 17 | val (k, v) = block(change.element.value) 18 | keys.add(change.element.index, k) 19 | map[k] = v 20 | } 21 | is TrackedList.Remove -> { 22 | map.remove(keys.removeAt(change.element.index)) 23 | } 24 | is TrackedList.Clear -> { 25 | map.clear() 26 | keys.clear() 27 | } 28 | } 29 | } 30 | } 31 | return map 32 | } 33 | 34 | fun ListState.effectOnChange( 35 | referenceHolder: ReferenceHolder, 36 | onChange: (TrackedList.Change) -> Unit, 37 | ) { 38 | var oldList = trackedListOf() 39 | effect(referenceHolder) { 40 | val newList = this@effectOnChange() 41 | val changes = newList.getChangesSince(oldList).also { oldList = newList } 42 | for (change in changes) { 43 | onChange(change) 44 | } 45 | } 46 | } 47 | 48 | fun ListState.effectOnChange( 49 | referenceHolder: ReferenceHolder, 50 | add: (IndexedValue) -> Unit, 51 | remove: (IndexedValue) -> Unit, 52 | clear: (List) -> Unit = { list -> list.forEach { remove(IndexedValue(0, it)) } }, 53 | ) { 54 | effectOnChange(referenceHolder) { change -> 55 | when (change) { 56 | is TrackedList.Add -> add(change.element) 57 | is TrackedList.Remove -> remove(change.element) 58 | is TrackedList.Clear -> clear(change.oldElements) 59 | } 60 | } 61 | } 62 | 63 | fun trackedListOf(vararg elements: T) : TrackedList = MutableTrackedList(elements.toMutableList()) 64 | 65 | fun mutableTrackedListOf(vararg elements: T): MutableTrackedList = MutableTrackedList(elements.toMutableList()) -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build & Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - unstable/import 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | env: 14 | ORG_GRADLE_PROJECT_branch: ${{ github.head_ref || github.ref_name }} 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: actions/setup-java@v4 19 | with: 20 | distribution: temurin 21 | java-version: | 22 | 8 23 | 16 24 | 17 25 | 26 | # Can't use setup-java for this because https://github.com/actions/setup-java/issues/366 27 | - uses: actions/cache@v4 28 | with: 29 | path: ~/.gradle/wrapper 30 | key: gradle-wrapper-${{ hashFiles('**/gradle-wrapper.properties') }} 31 | - uses: actions/cache@v4 32 | with: 33 | path: | 34 | ~/.gradle/caches 35 | **/loom-cache 36 | key: gradle-caches-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle.properties', 'gradle/*.versions.toml') }} 37 | restore-keys: | 38 | gradle-caches-${{ hashFiles('**/*.gradle*') }} 39 | gradle-caches- 40 | 41 | - name: Setup environment 42 | run: | 43 | echo "ORG_GRADLE_PROJECT_BUILD_ID=$(expr ${{ github.run_number }} + 605)" >> "$GITHUB_ENV" 44 | # GitHub runners are limited to 7GB of RAM, so we'll limit our Gradle Daemon process to about half of that 45 | # which is enough so long as parallel task execution is limited. 46 | # We also need to limit the Kotlin Compiler Daemon to its default value (which it seems to be perfectly 47 | # fine with) as otherwise it inherits the Gradle Daemon's jvmargs putting us above the runner limit. 48 | # We also pin the amount of workers, so it doesn't break should GitHub increase the default available vCPUs. 49 | # We write these to GRADLE_USER_HOME to overrule the local "gradle.properties" of the project. 50 | mkdir -p "${GRADLE_USER_HOME:=$HOME/.gradle}" 51 | echo "org.gradle.jvmargs=-Xmx2G -Dkotlin.daemon.jvm.options=-Xmx512M" >> "$GRADLE_USER_HOME/gradle.properties" 52 | echo "org.gradle.workers.max=2" >> "$GRADLE_USER_HOME/gradle.properties" 53 | 54 | - name: Build 55 | run: ./gradlew build --stacktrace 56 | 57 | - name: Publish 58 | run: ./gradlew publish --stacktrace 59 | if: env.ORG_GRADLE_PROJECT_nexus_user != null 60 | env: 61 | ORG_GRADLE_PROJECT_nexus_user: ${{ secrets.NEXUS_USER }} 62 | ORG_GRADLE_PROJECT_nexus_password: ${{ secrets.NEXUS_PASSWORD }} 63 | -------------------------------------------------------------------------------- /unstable/statev2/src/main/kotlin/gg/essential/elementa/unstable/state/v2/list.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.state.v2 2 | 3 | import gg.essential.elementa.unstable.state.v2.collections.MutableTrackedList 4 | import gg.essential.elementa.unstable.state.v2.collections.TrackedList 5 | 6 | typealias ListState = State> 7 | typealias MutableListState = MutableState> 8 | 9 | fun State>.toListState(): ListState { 10 | var oldList = MutableTrackedList() 11 | return memo { 12 | val newList = get() 13 | oldList.applyChanges(TrackedList.Change.estimate(oldList, newList)).also { oldList = it } 14 | } 15 | } 16 | 17 | fun ListState.mapChanges(init: (TrackedList) -> U, update: (old: U, changes: Sequence>) -> U): State { 18 | var trackedList: TrackedList? = null 19 | var trackedValue: U? = null 20 | return memo { 21 | val newList = get() 22 | val oldList = trackedList 23 | val newValue = 24 | if (oldList == null) { 25 | init(newList) 26 | } else { 27 | @Suppress("UNCHECKED_CAST") 28 | update(trackedValue as U, newList.getChangesSince(oldList)) 29 | } 30 | 31 | trackedList = newList 32 | trackedValue = newValue 33 | 34 | newValue 35 | } 36 | } 37 | 38 | fun ListState.mapChange(init: (TrackedList) -> U, update: (old: U, change: TrackedList.Change) -> U): State = 39 | mapChanges(init) { old, changes -> changes.fold(old, update) } 40 | 41 | fun listStateOf(vararg elements: T): ListState = 42 | stateOf(MutableTrackedList(mutableListOf(*elements))) 43 | 44 | fun mutableListStateOf(vararg elements: T): MutableListState = 45 | mutableStateOf(MutableTrackedList(mutableListOf(*elements))) 46 | 47 | fun MutableListState.set(index: Int, element: T) = set { it.set(index, element) } 48 | fun MutableListState.setAll(newList: List) = set { it.applyChanges(TrackedList.Change.estimate(it, newList)) } 49 | fun MutableListState.add(element: T) = set { it.add(element) } 50 | fun MutableListState.add(index: Int, element: T) = set { it.add(index, element) } 51 | fun MutableListState.addAll(elements: List) = set { it.addAll(elements) } 52 | fun MutableListState.remove(element: T) = set { it.remove(element) } 53 | fun MutableListState.removeAt(index: Int) = set { it.removeAt(index) } 54 | fun MutableListState.removeAll(predicate: (T) -> Boolean) = set { it.removeAll(it.filter(predicate)) } 55 | fun MutableListState.clear() = set { it.clear() } 56 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/resolution/ConstraintResolverV2.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.resolution 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.components.Window 5 | import gg.essential.elementa.constraints.ConstraintType 6 | import gg.essential.elementa.constraints.SuperConstraint 7 | import gg.essential.elementa.constraints.debug.ConstraintDebugger 8 | import gg.essential.elementa.constraints.debug.withDebugger 9 | 10 | internal class ConstraintResolverV2(private val window: Window) { 11 | fun getCyclicNodes(): List? { 12 | val debugger = CycleSearchingDebugger() 13 | withDebugger(debugger) { 14 | window.forEachChild { 15 | it.constraints.x.getXPosition(it) 16 | it.constraints.y.getYPosition(it) 17 | it.constraints.width.getWidth(it) 18 | it.constraints.height.getHeight(it) 19 | it.constraints.radius.getRadius(it) 20 | } 21 | } 22 | return debugger.cycles.firstOrNull() 23 | } 24 | } 25 | 26 | private class CycleSearchingDebugger : ConstraintDebugger { 27 | private val visited = mutableSetOf>() 28 | private val stack = mutableSetOf() 29 | 30 | val cycles = mutableListOf>() 31 | 32 | override fun evaluate(constraint: SuperConstraint, type: ConstraintType, component: UIComponent): Float { 33 | if (visited.add(constraint)) { 34 | // If this is the first time we see this constraint, mark it dirty. 35 | // Usually this is done by the animationFrame call, but we cannot issue one of those because that may 36 | // advance animations and give us a different result than what we are looking for. 37 | constraint.recalculate = true 38 | } 39 | 40 | if (constraint.recalculate) { 41 | val node = ResolverNode(component, constraint, type) 42 | 43 | if (node in stack) { 44 | // Found a cycle! 45 | 46 | // remove the non-cyclic beginnings of the stack and store the cycle for later 47 | cycles.add(stack.dropWhile { it != node } + node) 48 | 49 | // let's just return some arbitrary value so we can continue 50 | return constraint.cachedValue 51 | } 52 | 53 | stack.add(node) 54 | constraint.cachedValue = invokeImpl(constraint, type, component) 55 | stack.remove(node) 56 | 57 | constraint.recalculate = false 58 | } 59 | 60 | return constraint.cachedValue 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/common/constraints/AlternateConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.common.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.ConstraintType 5 | import gg.essential.elementa.constraints.SizeConstraint 6 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 7 | 8 | /** 9 | * Constraint which tries to evaluate the given constraint but falls back to another constraint if the first constraint 10 | * results in a circular constraint chain. 11 | * 12 | * You probably shouldn't use this. With great power comes great responsibility. 13 | * Be sure to fully understand how this works and interacts with other constraints before using, otherwise you may see 14 | * undefined behavior such as unstable results, stack overflow, etc. if any of the involved constraints are not pure 15 | * or more generally not safe to evaluate recursively (this one for example isn't, so don't ever use multiple). 16 | */ 17 | class AlternateConstraint( 18 | val primary: SizeConstraint, 19 | val fallback: SizeConstraint, 20 | ) : SizeConstraint { 21 | override var recalculate: Boolean = true 22 | override var cachedValue: Float = 0f 23 | override var constrainTo: UIComponent? 24 | get() = null 25 | set(value) = throw UnsupportedOperationException() 26 | 27 | private var tryingPrimary = false 28 | private var primaryWasRecursive = false 29 | 30 | override fun animationFrame() { 31 | primary.animationFrame() 32 | fallback.animationFrame() 33 | 34 | super.animationFrame() 35 | } 36 | 37 | private inline fun eval(eval: (SizeConstraint) -> Float): Float { 38 | if (!tryingPrimary) { 39 | tryingPrimary = true 40 | try { 41 | primaryWasRecursive = false 42 | val value = eval(primary) 43 | if (!primaryWasRecursive) { 44 | return value 45 | } 46 | } finally { 47 | tryingPrimary = false 48 | } 49 | } else { 50 | primaryWasRecursive = true 51 | } 52 | return eval(fallback) 53 | } 54 | 55 | 56 | override fun getWidthImpl(component: UIComponent): Float = 57 | eval { it.getWidth(component) } 58 | 59 | override fun getHeightImpl(component: UIComponent): Float = 60 | eval { it.getHeight(component) } 61 | 62 | override fun getRadiusImpl(component: UIComponent): Float = 63 | eval { it.getRadius(component) } 64 | 65 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) {} 66 | } -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/RelativeConstraint.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.resolution.ConstraintVisitor 5 | import gg.essential.elementa.state.BasicState 6 | import gg.essential.elementa.state.MappedState 7 | import gg.essential.elementa.state.State 8 | 9 | /** 10 | * Sets this component's X/Y position or width/height to be some 11 | * multiple of its parents. 12 | */ 13 | class RelativeConstraint constructor(value: State) : PositionConstraint, SizeConstraint { 14 | @JvmOverloads constructor(value: Float = 1f) : this(BasicState(value)) 15 | override var cachedValue = 0f 16 | override var recalculate = true 17 | override var constrainTo: UIComponent? = null 18 | 19 | private val valueState: MappedState = value.map { it } 20 | 21 | var value: Float 22 | get() = valueState.get() 23 | set(value) { valueState.set(value) } 24 | 25 | fun bindValue(newState: State) = apply { 26 | valueState.rebind(newState) 27 | } 28 | 29 | override fun getXPositionImpl(component: UIComponent): Float { 30 | return (constrainTo ?: component.parent).getLeft() + getWidth(component) 31 | } 32 | 33 | override fun getYPositionImpl(component: UIComponent): Float { 34 | return (constrainTo ?: component.parent).getTop() + getHeight(component) 35 | } 36 | 37 | override fun getWidthImpl(component: UIComponent): Float { 38 | return (constrainTo ?: component.parent).getWidth() * valueState.get() 39 | } 40 | 41 | override fun getHeightImpl(component: UIComponent): Float { 42 | return (constrainTo ?: component.parent).getHeight() * valueState.get() 43 | } 44 | 45 | override fun getRadiusImpl(component: UIComponent): Float { 46 | return ((constrainTo ?: component.parent).getWidth() * valueState.get()) / 2f 47 | } 48 | 49 | override fun visitImpl(visitor: ConstraintVisitor, type: ConstraintType) { 50 | when (type) { 51 | ConstraintType.X -> { 52 | visitor.visitParent(ConstraintType.X) 53 | visitor.visitParent(ConstraintType.WIDTH) 54 | } 55 | ConstraintType.Y -> { 56 | visitor.visitParent(ConstraintType.Y) 57 | visitor.visitParent(ConstraintType.HEIGHT) 58 | } 59 | ConstraintType.WIDTH -> visitor.visitParent(ConstraintType.WIDTH) 60 | ConstraintType.HEIGHT -> visitor.visitParent(ConstraintType.HEIGHT) 61 | ConstraintType.RADIUS -> visitor.visitParent(ConstraintType.WIDTH) 62 | else -> throw IllegalArgumentException(type.prettyName) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/resources/shaders/font.fsh: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | varying vec2 pos; 4 | 5 | uniform sampler2D msdf; 6 | uniform vec4 fgColor; 7 | 8 | uniform float doffset; 9 | uniform float hint_amount; 10 | uniform float subpixel_amount; 11 | 12 | uniform vec2 sdf_texel; 13 | 14 | float median(float r, float g, float b) { 15 | return max(min(r, g), min(max(r, g), b)); 16 | } 17 | 18 | float smoother(float edge0, float edge1, float x) { 19 | // Scale, bias and saturate x to 0..1 range 20 | x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); 21 | // Evaluate polynomial 22 | return x * x * (3.0 - 2.0 * x); 23 | } 24 | 25 | float getDistanceFromSDF(sampler2D msdf, vec2 pos) { 26 | vec3 raw = texture2D(msdf, pos).rgb; 27 | return median(raw.r, raw.g, raw.b);// - 0.5; 28 | } 29 | 30 | vec3 subpixel(float v, float a) { 31 | float vt = 0.6 * v;// 1.0 will make your eyes bleed 32 | vec3 rgb_max = vec3(-vt, 0.0, vt); 33 | float top = abs(vt); 34 | float bottom = -top - 1.0; 35 | float cfloor = mix(top, bottom, a); 36 | vec3 res = clamp(rgb_max - vec3(cfloor), 0.0, 1.0); 37 | return res; 38 | } 39 | void main() { 40 | // Sampling the texture, L pattern 41 | float sdf = getDistanceFromSDF( msdf, pos ); 42 | float sdf_north = getDistanceFromSDF( msdf, pos + vec2( 0.0, sdf_texel.y ) ); 43 | float sdf_east = getDistanceFromSDF( msdf, pos + vec2( sdf_texel.x, 0.0 ) ); 44 | 45 | // Estimating stroke direction by the distance field gradient vector 46 | vec2 sgrad = vec2( sdf_east - sdf, sdf_north - sdf ); 47 | float sgrad_len = max( length( sgrad ), 1.0 / 128.0 ); 48 | vec2 grad = sgrad / vec2( sgrad_len ); 49 | float vgrad = abs( grad.y ); // 0.0 - vertical stroke, 1.0 - horizontal one 50 | 51 | float horz_scale = 1.1; // Blurring vertical strokes along the X axis a bit 52 | float vert_scale = 0.8; // While adding some contrast to the horizontal strokes 53 | float hdoffset = mix( doffset * horz_scale, doffset * vert_scale, vgrad ); 54 | float res_doffset = mix( doffset, hdoffset, hint_amount ); 55 | 56 | float alpha = smoother( 0.5 - res_doffset, 0.5 + res_doffset, sdf ); 57 | // Additional contrast 58 | // alpha = pow( alpha, 1.0 + 0.2 * vgrad * hint_amount ); 59 | 60 | // Discarding pixels beyond a threshold to minimise possible artifacts. 61 | if ( alpha < 20.0 / 256.0 ) discard; 62 | 63 | gl_FragColor = vec4(fgColor.rgb, alpha); 64 | 65 | /*vec3 channels = subpixel( grad.x * 0.5 * subpixel_amount, alpha ); 66 | 67 | float subpixelOverride = min(1.0, max(0.0, 1.5 - 0.5*(channels.x + channels.y + channels.z))); 68 | 69 | vec4 fgColorBlended = fgColor; 70 | fgColorBlended.rgb += subpixelOverride; 71 | 72 | float finalAlpha = min(1.0, channels.r + channels.g + channels.b); 73 | gl_FragColor = fgColorBlended * vec4(channels, finalAlpha);*/ 74 | } -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/color.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.constraints.ColorConstraint 5 | import gg.essential.elementa.constraints.animation.Animations 6 | import gg.essential.elementa.dsl.animate 7 | import gg.essential.elementa.dsl.toConstraint 8 | import gg.essential.elementa.state.State 9 | import gg.essential.elementa.state.toConstraint 10 | import gg.essential.elementa.unstable.state.v2.color.toConstraint 11 | import gg.essential.elementa.unstable.state.v2.effect 12 | import gg.essential.elementa.unstable.state.v2.stateOf 13 | import gg.essential.elementa.unstable.state.v2.toV2 14 | import gg.essential.elementa.unstable.util.hasWindow 15 | import java.awt.Color 16 | import gg.essential.elementa.unstable.state.v2.State as StateV2 17 | 18 | fun Modifier.color(color: Color) = this then BasicColorModifier { color.toConstraint() } 19 | 20 | @Deprecated("Using StateV1 is discouraged, use StateV2 instead") 21 | fun Modifier.color(color: State) = this then BasicColorModifier { color.toConstraint() } 22 | 23 | fun Modifier.color(color: StateV2) = this then BasicColorModifier { color.toConstraint() } 24 | 25 | fun Modifier.hoverColor(color: Color, duration: Float = 0f) = hoverColor(stateOf(color), duration) 26 | 27 | @Deprecated("Using StateV1 is discouraged, use StateV2 instead") 28 | fun Modifier.hoverColor(color: State, duration: Float = 0f) = whenHovered(if (duration == 0f) Modifier.color(color) else Modifier.animateColor(color, duration)) 29 | 30 | fun Modifier.hoverColor(color: StateV2, duration: Float = 0f) = whenHovered(if (duration == 0f) Modifier.color(color) else Modifier.animateColor(color, duration)) 31 | 32 | fun Modifier.animateColor(color: Color, duration: Float = .3f) = animateColor(stateOf(color), duration) 33 | 34 | @Deprecated("Using StateV1 is discouraged, use StateV2 instead") 35 | fun Modifier.animateColor(color: State, duration: Float = .3f) = animateColor(color.toV2(), duration) 36 | 37 | fun Modifier.animateColor(color: StateV2, duration: Float = .3f) = this then AnimateColorModifier(color, duration) 38 | 39 | private class AnimateColorModifier(private val colorState: StateV2, private val duration: Float) : Modifier { 40 | override fun applyToComponent(component: UIComponent): () -> Unit { 41 | val oldColor = component.constraints.color 42 | 43 | fun animate(color: ColorConstraint) { 44 | if (component.hasWindow) { 45 | component.animate { 46 | setColorAnimation(Animations.OUT_EXP, duration, color) 47 | } 48 | } else { 49 | component.setColor(color) 50 | } 51 | } 52 | 53 | val removeListenerCallback = effect(component) { 54 | animate(colorState().toConstraint()) 55 | } 56 | 57 | return { 58 | removeListenerCallback() 59 | animate(oldColor) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/events.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.events.UIClickEvent 5 | import gg.essential.elementa.unstable.util.* 6 | 7 | import gg.essential.elementa.state.State as StateV1 8 | import gg.essential.elementa.unstable.state.v2.State as StateV2 9 | 10 | inline fun Modifier.onLeftClick(crossinline callback: UIComponent.(UIClickEvent) -> Unit) = this then { 11 | val listener: UIComponent.(event: UIClickEvent) -> Unit = { 12 | if (it.mouseButton == 0) { 13 | callback(it) 14 | } 15 | } 16 | onMouseClick(listener) 17 | return@then { mouseClickListeners.remove(listener) } 18 | } 19 | 20 | inline fun Modifier.onMouseRelease(crossinline callback: UIComponent.() -> Unit) = this then { 21 | val listener: UIComponent.() -> Unit = { 22 | callback() 23 | } 24 | onMouseRelease(listener) 25 | return@then { mouseReleaseListeners.remove(listener) } 26 | } 27 | 28 | /** Declare this component and its children to be in a hover scope. See [makeHoverScope]. */ 29 | fun Modifier.hoverScope(state: StateV1? = null) = 30 | then { makeHoverScope(state); { throw NotImplementedError() } } 31 | 32 | /** Declare this component and its children to be in a hover scope. See [makeHoverScope]. */ 33 | fun Modifier.hoverScope(state: StateV2) = 34 | then { makeHoverScope(state); { throw NotImplementedError() } } 35 | 36 | /** 37 | * Replaces the existing hover scope declared on this component with one which simply inherits from the parent scope. 38 | * Can effectively be used to remove a scope from an otherwise self-contained component to join it with other custom 39 | * components surrounding it. 40 | */ 41 | fun Modifier.inheritHoverScope() = 42 | then { makeHoverScope(hoverScope(parentOnly = true)); { throw NotImplementedError() } } 43 | 44 | /** 45 | * Applies [hoverModifier] while the component is hovered, otherwise applies [noHoverModifier] (or nothing by default). 46 | * 47 | * Whether a component is considered "hovered" depends solely on whether its [hoverScope] says that it is. 48 | * It is not necessarily related to whether the mouse cursor is on top of the component (e.g. the label of a button may 49 | * be considered hovered when the overall button is hovered, even when the cursor isn't on the text itself). 50 | * 51 | * A [Modifier.hoverScope] is **require** on the component or one of its parents. 52 | */ 53 | fun Modifier.whenHovered(hoverModifier: Modifier, noHoverModifier: Modifier = Modifier): Modifier = 54 | then { Modifier.whenTrue(hoverScopeV2(), hoverModifier, noHoverModifier).applyToComponent(this) } 55 | 56 | /** 57 | * Provides the [hoverScope] to be evaluated in a lambda which returns a modifier 58 | */ 59 | fun Modifier.withHoverState(func: (StateV2) -> Modifier) = 60 | then { func(hoverScopeV2()).applyToComponent(this) } 61 | 62 | /** Applies a Tag to this component. See [UIComponent.addTag]. */ 63 | fun Modifier.tag(tag: Tag) = then { addTag(tag); { removeTag(tag) } } 64 | -------------------------------------------------------------------------------- /unstable/layoutdsl/src/main/kotlin/gg/essential/elementa/unstable/layoutdsl/util.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.unstable.layoutdsl 2 | 3 | import gg.essential.elementa.UIComponent 4 | import gg.essential.elementa.components.UIBlock 5 | import gg.essential.elementa.components.inspector.Inspector 6 | import gg.essential.elementa.dsl.boundTo 7 | import gg.essential.elementa.dsl.percent 8 | import gg.essential.elementa.dsl.pixels 9 | import gg.essential.elementa.effects.Effect 10 | import gg.essential.elementa.utils.elementaDev 11 | import gg.essential.elementa.unstable.common.Spacer 12 | import java.awt.Color 13 | 14 | @Suppress("FunctionName") 15 | fun TransparentBlock() = UIBlock(Color(0, 0, 0, 0)) 16 | 17 | fun LayoutScope.spacer(width: Float, height: Float) = Spacer(width = width.pixels, height = height.pixels)() 18 | fun LayoutScope.spacer(width: Float, _desc: WidthDesc = Desc) = spacer(width, 0f) 19 | fun LayoutScope.spacer(height: Float, _desc: HeightDesc = Desc) = spacer(0f, height) 20 | fun LayoutScope.spacer(width: UIComponent, height: UIComponent) = Spacer(100.percent boundTo width, 100.percent boundTo height)() 21 | fun LayoutScope.spacer(width: UIComponent, _desc: WidthDesc = Desc) = Spacer(100.percent boundTo width, 0f.pixels)() 22 | fun LayoutScope.spacer(height: UIComponent, _desc: HeightDesc = Desc) = Spacer(0f.pixels, 100.percent boundTo height)() 23 | 24 | sealed interface WidthDesc 25 | sealed interface HeightDesc 26 | private object Desc : WidthDesc, HeightDesc 27 | 28 | @Suppress("unused") 29 | private val init = run { 30 | Inspector.registerComponentFactory(null) 31 | } 32 | 33 | // How is this not in the stdlib? 34 | internal inline fun Iterable.sumOf(selector: (T) -> Float): Float { 35 | var sum = 0f 36 | for (element in this) { 37 | sum += selector(element) 38 | } 39 | return sum 40 | } 41 | 42 | fun UIComponent.automaticComponentName(default: String) { 43 | if (!elementaDev) return 44 | 45 | componentName = Throwable().stackTrace 46 | .asSequence() 47 | .filterNot { it.lineNumber == 1 } // synthetic accessor methods 48 | .filterNot { it.methodName.endsWith("\$default") } // synthetic Kotlin defaults methods 49 | .map { it.methodName } 50 | .distinct() // collapse overloads 51 | .drop(1) // "automaticComponentName" 52 | .drop(1) // caller method (e.g. "box") 53 | .firstOrNull() 54 | ?.takeUnless { it == "invoke" } // anonymous component (the `block` of `LayoutScope.invoke`) 55 | ?.takeUnless { it == "" } // anonymous component (likely direct child of a class component) 56 | ?: default 57 | } 58 | 59 | fun UIComponent.getChildModifier() = 60 | effects 61 | .filterIsInstance() 62 | .map { it.childModifier } 63 | .reduceOrNull { acc, it -> acc then it } 64 | ?: Modifier 65 | 66 | fun UIComponent.addChildModifier(modifier: Modifier) { 67 | enableEffect(ChildModifierMarker(modifier)) 68 | } 69 | 70 | // Serves as a marker only. FIXME: integrate directly into the component class when we transition this DSL to Elementa? 71 | private class ChildModifierMarker(val childModifier: Modifier) : Effect() -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/constraints/resolution/DirectedAcyclicGraph.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.constraints.resolution 2 | 3 | import gg.essential.elementa.utils.Matrix2b 4 | import java.util.* 5 | 6 | class DirectedAcyclicGraph { 7 | private val vertices = mutableListOf() 8 | private var hasAddedEdge = false 9 | private val edges by lazy { Matrix2b(vertices.size, vertices.size) } 10 | 11 | fun addVertex(vertex: T) = apply { 12 | if (hasAddedEdge) 13 | throw IllegalStateException("Cannot add a vertex after adding an edge!") 14 | vertices.add(vertex) 15 | } 16 | 17 | fun addVertices(vararg vertices: T) = apply { 18 | vertices.forEach { addVertex(it) } 19 | } 20 | 21 | fun addEdge(first: T, second: T): DirectedAcyclicGraph { 22 | hasAddedEdge = true 23 | if (first == second) 24 | throw IllegalArgumentException("Cannot add edge between a vertex and itself: $first") 25 | 26 | val firstIndex = vertices.indexOf(first) 27 | val secondIndex = vertices.indexOf(second) 28 | 29 | if (firstIndex == -1) 30 | throw IllegalArgumentException("$first is not a vertex!") 31 | if (secondIndex == -1) 32 | throw IllegalArgumentException("$second is not a vertex!") 33 | 34 | edges[firstIndex, secondIndex] = true 35 | 36 | return this 37 | } 38 | 39 | fun addEdges(vararg edges: Pair): DirectedAcyclicGraph { 40 | edges.forEach { (first, second) -> addEdge(first, second) } 41 | return this 42 | } 43 | 44 | fun getCyclicLoop() = CyclicHelper().cyclicLoop() 45 | 46 | // Algorithm taken from https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search 47 | private inner class CyclicHelper { 48 | private val temporaryMarks = vertices.indices.map { false }.toMutableList() 49 | private val permanentMarks = vertices.indices.map { false }.toMutableList() 50 | private val nodeStack = Stack() 51 | 52 | fun cyclicLoop(): List? { 53 | while (true) { 54 | val indexOfNonMarked = permanentMarks.indexOfFirst { !it } 55 | if (indexOfNonMarked == -1) 56 | return null 57 | if (!visit(indexOfNonMarked)) 58 | return nodeStack.dropWhile { 59 | it != nodeStack.last() 60 | }.map { vertices[it] } 61 | } 62 | } 63 | 64 | private fun visit(index: Int): Boolean { 65 | if (permanentMarks[index]) 66 | return true 67 | 68 | nodeStack.push(index) 69 | if (temporaryMarks[index]) 70 | return false 71 | 72 | temporaryMarks[index] = true 73 | 74 | for ((edgeIndex, connects) in edges.row(index).withIndex()) { 75 | if (connects && !visit(edgeIndex)) 76 | return false 77 | } 78 | 79 | temporaryMarks[index] = false 80 | permanentMarks[index] = true 81 | nodeStack.pop() 82 | 83 | return true 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/markdown/drawables/HeaderDrawable.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.markdown.drawables 2 | 3 | import gg.essential.elementa.components.UIBlock 4 | import gg.essential.elementa.markdown.DrawState 5 | import gg.essential.elementa.markdown.MarkdownComponent 6 | import gg.essential.universal.UMatrixStack 7 | 8 | class HeaderDrawable( 9 | md: MarkdownComponent, 10 | private val level: Int, 11 | private val paragraph: ParagraphDrawable 12 | ) : Drawable(md) { 13 | override val children: List get() = listOf(paragraph) 14 | internal val id = paragraph.textDrawables.joinToString(separator = " ") { it.plainText() } 15 | 16 | var dividerWidth: Double? = null 17 | 18 | init { 19 | paragraph.parent = this 20 | } 21 | 22 | private val headerConfig = when (level) { 23 | 1 -> config.headerConfig.level1 24 | 2 -> config.headerConfig.level2 25 | 3 -> config.headerConfig.level3 26 | 4 -> config.headerConfig.level4 27 | 5 -> config.headerConfig.level5 28 | 6 -> config.headerConfig.level6 29 | else -> throw IllegalStateException() 30 | } 31 | 32 | init { 33 | paragraph.headerConfig = headerConfig 34 | trim(paragraph) 35 | } 36 | 37 | override fun layoutImpl(x: Float, y: Float, width: Float): Layout { 38 | val spaceBefore = if (insertSpaceBefore) headerConfig.verticalSpaceBefore else 0f 39 | val spaceAfter = if (insertSpaceAfter) headerConfig.verticalSpaceAfter else 0f 40 | paragraph.layout(x, y + spaceBefore, width) 41 | 42 | val height = spaceBefore + paragraph.height + spaceAfter + if (headerConfig.hasDivider) { 43 | headerConfig.spaceBeforeDivider + headerConfig.dividerWidth 44 | } else 0f 45 | 46 | return Layout( 47 | x, 48 | y, 49 | width, 50 | height, 51 | Margin(0f, spaceBefore, 0f, spaceAfter) 52 | ) 53 | } 54 | 55 | override fun draw(matrixStack: UMatrixStack, state: DrawState) { 56 | paragraph.drawCompat(matrixStack, state) 57 | 58 | if (headerConfig.hasDivider) { 59 | val y = layout.bottom - layout.margin.bottom - headerConfig.dividerWidth 60 | UIBlock.drawBlockSized( 61 | matrixStack, 62 | headerConfig.dividerColor, 63 | (x + state.xShift).toDouble(), 64 | (y + state.yShift).toDouble(), 65 | dividerWidth ?: width.toDouble(), 66 | headerConfig.dividerWidth.toDouble() 67 | ) 68 | } 69 | } 70 | 71 | override fun cursorAt(mouseX: Float, mouseY: Float, dragged: Boolean, mouseButton: Int) = paragraph.cursorAt(mouseX, mouseY, dragged, mouseButton) 72 | override fun cursorAtStart() = paragraph.cursorAtStart() 73 | override fun cursorAtEnd() = paragraph.cursorAtEnd() 74 | 75 | override fun selectedText(asMarkdown: Boolean): String { 76 | if (!hasSelectedText()) 77 | return "" 78 | 79 | val text = paragraph.selectedText(asMarkdown) 80 | return if (asMarkdown) { 81 | "#".repeat(level) + " $text" 82 | } else text 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/kotlin/gg/essential/elementa/font/data/FontInfo.kt: -------------------------------------------------------------------------------- 1 | package gg.essential.elementa.font.data 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonObject 5 | import com.google.gson.annotations.SerializedName 6 | 7 | data class FontInfo( 8 | val atlas: Atlas, 9 | val metrics: Metrics, 10 | val glyphs: Map 11 | ) { 12 | companion object { 13 | private val gson = Gson() 14 | 15 | fun fromJson(json: JsonObject): FontInfo { 16 | val atlas = gson.fromJson(json.getAsJsonObject("atlas"), Atlas::class.java) 17 | val metrics = gson.fromJson(json.getAsJsonObject("metrics"), Metrics::class.java) 18 | val glyphs = json.getAsJsonArray("glyphs").associate { glyphElement -> 19 | val glyph = gson.fromJson(glyphElement, Glyph::class.java) 20 | glyph.unicode to glyph 21 | } 22 | 23 | return FontInfo(atlas, metrics, glyphs) 24 | } 25 | } 26 | } 27 | 28 | data class Atlas( 29 | val type: String, 30 | val distanceRange: Float, 31 | val size: Float, 32 | val width: Float, 33 | val height: Float, 34 | val yOrigin: String, 35 | val baseCharHeight: Float, 36 | val belowLineHeight: Float, 37 | val shadowHeight: Float 38 | ) 39 | 40 | data class Metrics( 41 | val lineHeight: Float, 42 | val ascender: Float, 43 | val descender: Float, 44 | val underlineY: Float, 45 | val underlineThickness: Float 46 | ) 47 | 48 | class Glyph( 49 | val unicode: Int, 50 | val advance: Float, 51 | val planeBounds: PlaneBounds? = null, 52 | val atlasBounds: AtlasBounds? = null 53 | ) 54 | 55 | data class PlaneBounds( 56 | @SerializedName("left") 57 | private val _left: Float, 58 | @SerializedName("bottom") 59 | private val _bottom: Float, 60 | @SerializedName("right") 61 | private val _right: Float, 62 | @SerializedName("top") 63 | private val _top: Float 64 | ) { 65 | /** 66 | * msdfgen exports the plane locations with .025 subtracted from the 67 | * Y coordinate of each glyph, so we must correct for this 68 | */ 69 | val left: Float 70 | get() = _left 71 | val bottom: Float 72 | get() = _bottom + 0.025f 73 | val right: Float 74 | get() = _right 75 | val top: Float 76 | get() = _top + 0.025f 77 | } 78 | 79 | 80 | data class AtlasBounds( 81 | @SerializedName("left") 82 | private val _left: Float, 83 | @SerializedName("bottom") 84 | private val _bottom: Float, 85 | @SerializedName("right") 86 | private val _right: Float, 87 | @SerializedName("top") 88 | private val _top: Float 89 | ) { 90 | /** 91 | * msdfgen exports UV locations in the middle of pixels. 92 | * This causes the rendering to occur slightly of from 93 | * where you would expect it and incorrect texel mapping. 94 | */ 95 | val left: Float 96 | get() = _left + .5f 97 | val bottom: Float 98 | get() = _bottom + .5f 99 | val right: Float 100 | get() = _right + .5f 101 | val top: Float 102 | get() = _top + .5f 103 | } 104 | --------------------------------------------------------------------------------