├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── kotlin │ └── net │ └── prismclient │ └── aether │ ├── Fiddle.kt │ ├── core │ ├── Aether.kt │ ├── animation │ │ ├── Animation.kt │ │ ├── AnimationContext.kt │ │ ├── AnimationProvider.kt │ │ ├── AnimationUnit.kt │ │ ├── Animation_.kt │ │ └── type │ │ │ └── ComposableAnimation.kt │ ├── color │ │ ├── UIAlpha.kt │ │ └── UIColor.kt │ ├── debug │ │ └── Logger.kt │ ├── ease │ │ ├── UIEase.kt │ │ ├── UIEaseDirection.kt │ │ ├── UIEaseType.kt │ │ └── impl │ │ │ ├── UICircle.kt │ │ │ ├── UICubic.kt │ │ │ ├── UIExponential.kt │ │ │ ├── UILinear.kt │ │ │ ├── UIQuad.kt │ │ │ ├── UIQuart.kt │ │ │ ├── UIQuint.kt │ │ │ └── UISine.kt │ ├── event │ │ ├── CompositionEvents.kt │ │ ├── CustomEvent.kt │ │ ├── FocusableEvents.kt │ │ ├── UIEvent.kt │ │ └── UIEventBus.kt │ ├── input │ │ ├── MouseButtonType.kt │ │ ├── UIKey.kt │ │ ├── UIKeyAction.kt │ │ └── UIModifierKey.kt │ ├── metrics │ │ ├── Position.kt │ │ └── Size.kt │ └── util │ │ ├── extensions │ │ └── FontExtension.kt │ │ ├── other │ │ └── ComposableGroup.kt │ │ ├── property │ │ ├── Animatable.kt │ │ ├── Copyable.kt │ │ ├── Focusable.kt │ │ ├── Mergable.kt │ │ ├── UIProperty.kt │ │ └── Updatable.kt │ │ └── shorthands │ │ ├── Color.kt │ │ ├── Default.kt │ │ ├── FontUtil.kt │ │ ├── General.kt │ │ ├── Internal.kt │ │ └── Unit.kt │ └── ui │ ├── alignment │ ├── Alignment.kt │ └── UITextAlignment.kt │ ├── component │ ├── Components.kt │ ├── UIComponent.kt │ ├── style │ │ └── Style.kt │ └── type │ │ ├── Button.kt │ │ ├── DefaultConstruct.kt │ │ ├── ImageComponent.kt │ │ └── UILabel.kt │ ├── composer │ ├── ComposeContext.kt │ ├── Composer.kt │ ├── ComposerData.kt │ ├── ComposerHint.kt │ └── README │ ├── composition │ ├── Composable.kt │ ├── Composition.kt │ └── util │ │ ├── UIBackground.kt │ │ └── UIBorder.kt │ ├── controller │ └── UIControl.kt │ ├── dsl │ ├── ComposeDSL.kt │ ├── ConstructionDSL.kt │ ├── UIAssetDSL.kt │ ├── UIPathDSL.kt │ └── UIRendererDSL.kt │ ├── effect │ └── UIEffect.kt │ ├── font │ ├── Font.kt │ ├── FontFaceType.kt │ ├── FontType.kt │ └── UIFontFamily.kt │ ├── image │ ├── ImageProvider.kt │ ├── ImageUtil.kt │ └── UIImage.kt │ ├── layout │ ├── AutoLayout.kt │ ├── BoxLayout.kt │ ├── CustomLayout.kt │ ├── Layout.kt │ ├── UIListLayout.kt │ ├── scroll │ │ └── Scrollbar.kt │ └── util │ │ ├── LayoutDirection.kt │ │ ├── LayoutOrder.kt │ │ └── Overflow.kt │ ├── modifier │ └── UIModifier.kt │ ├── registry │ └── UIRegistry.kt │ ├── renderer │ ├── UIFramebuffer.kt │ ├── UIRenderer.kt │ └── UIStrokeDirection.kt │ ├── resource │ ├── Resource.kt │ └── ResourceProvider.kt │ ├── screen │ └── UIScreen.kt │ ├── shape │ ├── ComposableShape.kt │ └── Shape.kt │ ├── style │ └── Style.kt │ └── unit │ ├── Units.kt │ ├── other │ ├── AnchorPoint.kt │ ├── Margin.kt │ ├── Padding.kt │ └── UIRadius.kt │ └── type │ ├── DependableUnit.kt │ ├── RelativeUnit.kt │ ├── SizeUnit.kt │ ├── other │ └── OperationUnit.kt │ ├── range │ └── RangeUnit.kt │ └── solid │ └── PixelUnit.kt └── test ├── java └── test.java ├── kotlin └── net │ └── prismclient │ └── aether │ └── example │ ├── ComposableTesting.kt │ ├── Game.kt │ ├── Renderer.kt │ ├── Runner.kt │ ├── components │ └── Logo.kt │ ├── screens │ ├── Animations.kt │ ├── MacrosExample.kt │ ├── Menus.kt │ └── PrismDesign.kt │ └── util │ └── Window.kt └── resources ├── fonts ├── Montserrat │ ├── Montserrat-Black.ttf │ ├── Montserrat-BlackItalic.ttf │ ├── Montserrat-Bold.ttf │ ├── Montserrat-BoldItalic.ttf │ ├── Montserrat-ExtraBold.ttf │ ├── Montserrat-ExtraBoldItalic.ttf │ ├── Montserrat-ExtraLight.ttf │ ├── Montserrat-ExtraLightItalic.ttf │ ├── Montserrat-Italic.ttf │ ├── Montserrat-Light.ttf │ ├── Montserrat-LightItalic.ttf │ ├── Montserrat-Medium.ttf │ ├── Montserrat-MediumItalic.ttf │ ├── Montserrat-Regular.ttf │ ├── Montserrat-SemiBold.ttf │ ├── Montserrat-SemiBoldItalic.ttf │ ├── Montserrat-Thin.ttf │ └── Montserrat-ThinItalic.ttf └── Poppins │ ├── Poppins-Black.ttf │ ├── Poppins-BlackItalic.ttf │ ├── Poppins-Bold.ttf │ ├── Poppins-BoldItalic.ttf │ ├── Poppins-ExtraBold.ttf │ ├── Poppins-ExtraBoldItalic.ttf │ ├── Poppins-ExtraLight.ttf │ ├── Poppins-ExtraLightItalic.ttf │ ├── Poppins-Italic.ttf │ ├── Poppins-Light.ttf │ ├── Poppins-LightItalic.ttf │ ├── Poppins-Medium.ttf │ ├── Poppins-MediumItalic.ttf │ ├── Poppins-Regular.ttf │ ├── Poppins-SemiBold.ttf │ ├── Poppins-SemiBoldItalic.ttf │ ├── Poppins-Thin.ttf │ └── Poppins-ThinItalic.ttf ├── icons ├── gradient │ ├── bag.svg │ ├── folder.svg │ ├── home.svg │ ├── medal-star-1.svg │ ├── messages.svg │ ├── people.svg │ ├── profile.svg │ ├── settings.svg │ └── video.svg ├── mods │ └── mouse.svg ├── outline │ ├── close.svg │ ├── frame.svg │ ├── more-option.svg │ ├── sort.svg │ └── star.svg └── solid │ ├── bag.svg │ ├── copy.svg │ ├── folder.svg │ ├── home.svg │ ├── medal-star.svg │ ├── messages.svg │ ├── people.svg │ ├── premium-star.svg │ ├── profile.svg │ ├── settings.svg │ ├── star.svg │ └── video.svg ├── images ├── Character.png └── Prism-Logo.png └── licenses ├── OFL └── Vuesax /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | **/build/ 3 | !src/**/build/ 4 | gradle-app.setting 5 | !gradle-wrapper.jar 6 | !gradle-wrapper.properties 7 | .gradletasknamecache 8 | .project 9 | .classpath 10 | .idea 11 | *.DS_STORE 12 | *.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Aether rewrite. 2 | 3 | ## See the original one [here](https://github.com/Prism-Client/Aether-UI) 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.internal.os.OperatingSystem 2 | 3 | plugins { 4 | id 'org.jetbrains.kotlin.jvm' version '1.7.0' 5 | id 'java' 6 | id 'maven-publish' 7 | } 8 | 9 | group 'net.prismclient' 10 | version '1.0-dev' 11 | 12 | project.ext.lwjglVersion = "3.3.1" 13 | switch (OperatingSystem.current()) { 14 | case OperatingSystem.LINUX: 15 | def osArch = System.getProperty("os.arch") 16 | project.ext.lwjglNatives = osArch.startsWith("arm") || osArch.startsWith("aarch64") ? "natives-linux-${osArch.contains("64") || osArch.startsWith("armv8") ? "arm64" : "arm32"}" : "natives-linux" 17 | break 18 | case OperatingSystem.MAC_OS: 19 | project.ext.lwjglNatives = System.getProperty("os.arch").startsWith("aarch64") ? "natives-macos-arm64" : "natives-macos" 20 | break 21 | case OperatingSystem.WINDOWS: 22 | def osArch = System.getProperty("os.arch") 23 | project.ext.lwjglNatives = osArch.contains("64") ? "natives-windows${osArch.startsWith("aarch64") ? "-arm64" : ""}" : "natives-windows-x86" 24 | break 25 | } 26 | 27 | repositories { 28 | mavenCentral() 29 | } 30 | 31 | dependencies { 32 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0' 33 | implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.0' 34 | implementation 'commons-io:commons-io:2.11.0' 35 | 36 | testImplementation 'org.apache.commons:commons-collections4:4.4' 37 | 38 | testImplementation "org.lwjgl:lwjgl:$lwjglVersion" 39 | testImplementation "org.lwjgl:lwjgl-glfw:$lwjglVersion" 40 | testImplementation "org.lwjgl:lwjgl-nanovg:$lwjglVersion" 41 | testImplementation "org.lwjgl:lwjgl-opengl:$lwjglVersion" 42 | testImplementation "org.lwjgl:lwjgl-stb:$lwjglVersion" 43 | testRuntimeOnly "org.lwjgl:lwjgl:$lwjglVersion:$lwjglNatives" 44 | testRuntimeOnly "org.lwjgl:lwjgl-glfw:$lwjglVersion:$lwjglNatives" 45 | testRuntimeOnly "org.lwjgl:lwjgl-nanovg:$lwjglVersion:$lwjglNatives" 46 | testRuntimeOnly "org.lwjgl:lwjgl-opengl:$lwjglVersion:$lwjglNatives" 47 | testRuntimeOnly "org.lwjgl:lwjgl-stb:$lwjglVersion:$lwjglNatives" 48 | } 49 | 50 | allprojects { 51 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 52 | kotlinOptions { 53 | jvmTarget = '1.8' 54 | freeCompilerArgs += '-Xjvm-default=enable' 55 | } 56 | } 57 | } 58 | 59 | publishing { 60 | publications { 61 | maven(MavenPublication) { 62 | groupId = 'com.github.Prism-Client' 63 | artifactId = 'Aether' 64 | version = version 65 | from components.kotlin 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Aether' 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/Fiddle.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether 2 | 3 | import net.prismclient.aether.core.util.shorthands.Block 4 | import net.prismclient.aether.ui.component.type.UIButton 5 | import net.prismclient.aether.ui.composition.Composable 6 | import net.prismclient.aether.ui.dsl.UIRendererDSL 7 | 8 | 9 | /** 10 | * used to test out design patterns and kotlin features 11 | */ 12 | class fiddle { 13 | fun Control(block: Block) { 14 | 15 | } 16 | 17 | fun func() { 18 | // Control( 19 | // active = Button("HUD") 20 | // ) { 21 | // Button("Performance") 22 | // Button("Server") 23 | // } 24 | // Control( 25 | // 26 | // ) { 27 | // button("Hello world!") 28 | // } 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/animation/Animation.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.animation 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.util.property.Animatable 5 | import net.prismclient.aether.core.util.shorthands.copy 6 | import net.prismclient.aether.ui.modifier.UIModifier 7 | 8 | /** 9 | * @author sen 10 | * @since 1.0 11 | */ 12 | abstract class Animation> { 13 | open var context: AnimationContext? = null 14 | 15 | open var keyframes: ArrayList = arrayListOf() 16 | open var activeKeyframe: Keyframe? = null 17 | open var nextKeyframe: Keyframe? = null 18 | 19 | open var completionAction: Runnable? = null 20 | 21 | open var animating = false 22 | protected set 23 | 24 | open fun start() { 25 | if (keyframes.isEmpty()) throw IllegalStateException("No keyframes to animate") 26 | 27 | activeKeyframe = keyframes[0] 28 | nextKeyframe = keyframes.getOrNull(1) 29 | 30 | activeKeyframe!!.ease.start() 31 | 32 | animating = true 33 | } 34 | 35 | open fun next() { 36 | activeKeyframe = nextKeyframe 37 | nextKeyframe = keyframes.getOrNull(keyframes.indexOf(activeKeyframe) + 1) 38 | activeKeyframe?.ease?.start() 39 | 40 | if (activeKeyframe == null || nextKeyframe == null) { 41 | complete() 42 | return 43 | } 44 | } 45 | 46 | open fun complete() { 47 | completionAction?.run() 48 | } 49 | 50 | open fun update(obj: T) { 51 | val context = context ?: AnimationContext(obj.copy) 52 | 53 | if (activeKeyframe == null || nextKeyframe == null) { 54 | complete() 55 | return 56 | } 57 | 58 | if (activeKeyframe!!.ease.finished) { 59 | next() 60 | context.updateContext(obj.copy) 61 | if (activeKeyframe == null || nextKeyframe == null) { 62 | obj.animate(context, context.snapshot, null, keyframes.last().animatable, 1f, true) 63 | complete() 64 | return 65 | } 66 | } 67 | 68 | obj.animate( 69 | context, 70 | context.snapshot, 71 | activeKeyframe?.animatable, 72 | nextKeyframe?.animatable, 73 | (activeKeyframe?.ease?.getValue() ?: 0.0).toFloat(), 74 | false 75 | ) 76 | } 77 | 78 | open fun createKeyframe(ease: UIEase, animatable: T) = Keyframe(ease, animatable) 79 | 80 | inner class Keyframe(val ease: UIEase, val animatable: T) 81 | 82 | // var context: AnimationContext? = null 83 | // 84 | // val keyframes: ArrayList> = arrayListOf() 85 | // var activeKeyframe: Pair? = null 86 | // var nextKeyframe: Pair? = null 87 | // 88 | // var animating = false 89 | // 90 | // open fun start() { 91 | // if (keyframes.isEmpty()) throw IllegalStateException("No keyframes to animate") 92 | // 93 | // activeKeyframe = keyframes[0] 94 | // nextKeyframe = keyframes.getOrNull(1) 95 | // 96 | // activeKeyframe!!.first.start() 97 | // 98 | // animating = true 99 | // } 100 | // 101 | // open fun pause() {} 102 | // 103 | // open fun resume() {} 104 | // 105 | // open fun stop() {} 106 | // 107 | // open fun complete() { 108 | // animating = false 109 | // } 110 | // 111 | // abstract fun update(obj: T) 112 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/animation/AnimationContext.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.animation 2 | 3 | import net.prismclient.aether.core.util.property.Animatable 4 | 5 | /** 6 | * [AnimationContext] stores the state of the current animation. 7 | * 8 | * @author sen 9 | * @since 1.0 10 | */ 11 | class AnimationContext>(snapshot: T?) { 12 | /** 13 | * [snapshot] represents a copy of the active [Animatable], [T]. It is intended to 14 | * store the initial values of an animation so that if the given property is null, 15 | * the property will reflect the initial value. 16 | */ 17 | var snapshot: T? = snapshot 18 | 19 | var changesLayout: Boolean = false 20 | 21 | /** 22 | * Resets the active context and changes the [snapshot]. 23 | */ 24 | fun updateContext(snapshot: T?) { 25 | this.snapshot = snapshot 26 | changesLayout = false 27 | } 28 | 29 | /** 30 | * Indicates to the active animation that the property change requires the composition 31 | * to be recomposed. 32 | */ 33 | fun recompose() { 34 | changesLayout = true 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/animation/AnimationProvider.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.animation 2 | 3 | /** 4 | * @author sen 5 | * @since 1.0 6 | */ 7 | class AnimationProvider { 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/animation/AnimationUnit.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.animation 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | import net.prismclient.aether.ui.unit.UIUnit 5 | 6 | /** 7 | * [AnimationUnit] acts identical to a [PixelUnit] however, it is used to indicate an animation 8 | * and the modifier's position or size properties will be set to this during the animation and 9 | * restored after to the necessary unit. 10 | */ 11 | class AnimationUnit(val actualUnit: UIUnit<*>?, value: Float) : UIUnit(value) { 12 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float = value 13 | 14 | override fun copy(): AnimationUnit = AnimationUnit(actualUnit, value) 15 | 16 | override fun toString(): String = "AnimationUnit($actualUnit, $value)" 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/animation/Animation_.kt: -------------------------------------------------------------------------------- 1 | //package net.prismclient.aether.core.animation 2 | // 3 | //import net.prismclient.aether.ui.component.UIComponent 4 | // 5 | //enum class AnimationDirection { 6 | // OBVERSE, REVERSE 7 | //} 8 | // 9 | //// TODO: Determine animation type 10 | //class Animation>(val component: C) { 11 | // 12 | // var paused: Boolean = false 13 | // 14 | // var duration: Long = 0L 15 | // 16 | // var startTime: Long = 0L 17 | // private set 18 | // 19 | // var remainingTime: Long = 0L 20 | // private set 21 | // 22 | // var endTime: Long = 0L 23 | // private set 24 | // 25 | // var direction: AnimationDirection = AnimationDirection.OBVERSE 26 | // set(value) { 27 | // field = value 28 | // internalReverse() 29 | // } 30 | // 31 | // fun begin() { 32 | // remainingTime = duration 33 | // startTime = System.currentTimeMillis() 34 | // endTime = startTime + remainingTime 35 | // } 36 | // 37 | // fun pause() { 38 | // paused = true 39 | // endTime = 0L 40 | // } 41 | // 42 | // fun resume() { 43 | // endTime = startTime + remainingTime 44 | // } 45 | // 46 | // fun reverse() { 47 | // direction = if (direction == AnimationDirection.OBVERSE) AnimationDirection.REVERSE else AnimationDirection.OBVERSE 48 | // } 49 | // 50 | // /** 51 | // * Process reverse logic. 52 | // */ 53 | // private fun internalReverse() { 54 | // remainingTime = duration - remainingTime 55 | // } 56 | // 57 | // fun doAnimation() { 58 | // val currentTime = System.currentTimeMillis() 59 | // val hasFinished = remainingTime <= 0L 60 | // 61 | // if (!paused && !hasFinished) { 62 | // // calculate the new remaining time of the animation. 63 | // remainingTime = endTime - currentTime 64 | // } 65 | // 66 | // val progress = (duration / remainingTime).toFloat() 67 | // 68 | // 69 | // } 70 | // 71 | //} -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/animation/type/ComposableAnimation.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.animation.type 2 | 3 | import net.prismclient.aether.core.animation.Animation 4 | import net.prismclient.aether.core.event.PreRenderEvent 5 | import net.prismclient.aether.core.event.UIEventBus 6 | import net.prismclient.aether.core.util.property.Animatable 7 | import net.prismclient.aether.ui.composition.Composable 8 | import java.lang.RuntimeException 9 | 10 | class ComposableAnimation> : Animation() { 11 | var composable: Composable? = null 12 | var obj: T? = null 13 | 14 | fun start(composable: Composable, obj: T) { 15 | this.composable = composable 16 | this.obj = obj 17 | start() 18 | UIEventBus.register(toString()) { update(obj) } 19 | } 20 | 21 | override fun complete() { 22 | UIEventBus.unregister(toString()) 23 | super.complete() 24 | } 25 | 26 | override fun start() { 27 | if (composable == null || obj == null) throw RuntimeException("Use start(composable: Composable) instead.") 28 | super.start() 29 | } 30 | 31 | override fun update(obj: T) { 32 | super.update(obj) 33 | composable!!.composition?.compose() 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/color/UIAlpha.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.color 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | import net.prismclient.aether.core.util.property.Animatable 5 | import net.prismclient.aether.core.util.property.Copyable 6 | import net.prismclient.aether.core.util.property.Mergable 7 | 8 | /** 9 | * [UIAlpha] represents a alpha value between 0 and 1. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | class UIAlpha(var value: Float) : Copyable, Mergable, Animatable { 15 | /** 16 | * Creates a new alpha value from an Integer varying from the range 0 to 255 17 | */ 18 | constructor(value: Int) : this((value and 0xFF) / 255f) 19 | 20 | override fun animate( 21 | context: AnimationContext<*>, 22 | initial: UIAlpha?, 23 | start: UIAlpha?, 24 | end: UIAlpha?, 25 | progress: Float, 26 | completed: Boolean 27 | ) { 28 | TODO("Not yet implemented") 29 | } 30 | 31 | override fun copy(): UIAlpha = UIAlpha(value) 32 | 33 | override fun merge(other: UIAlpha?) { 34 | if (other != null) { 35 | value = other.value 36 | } 37 | } 38 | 39 | override fun toString(): String = "UIAlpha($value)" 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/color/UIColor.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.color 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | import net.prismclient.aether.core.util.property.Animatable 5 | import net.prismclient.aether.core.util.property.Copyable 6 | import net.prismclient.aether.core.util.property.Mergable 7 | import net.prismclient.aether.core.util.shorthands.* 8 | import java.awt.Color 9 | 10 | /** 11 | * [UIColor] represents an RGBA color. Behind the scenes, the value is represented as an Int 12 | * where the bytes are stored as ARGB with 1 byte per channel respectively. It also provides 13 | * utility functions to convert the color into different formats. 14 | * 15 | * The color could be stored as an Int, as the color stores exactly enough bytes to support a 16 | * 32-bit color, but, because of how animations are designed, the color property must be nullable. 17 | * Theoretically, the Int could be represented as it's class type, Integer, however there is 18 | * overhead to boxing and unboxing the integer. Using a wrapper avoids the boxing and unboxing, 19 | * reducing the overhead, though probably slower than having it as a normal Int. However, because 20 | * it is wrapped in a class, certain optimizations can now be used especially when converting to HSV, 21 | * as the values can be cached, thus reducing overhead from calculations. 22 | * 23 | * @author sen 24 | * @since 1.0 25 | */ 26 | class UIColor(color: Int) : Copyable, Mergable, Animatable { 27 | /** 28 | * Creates the [UIColor] from an HSV color type. 29 | */ 30 | constructor(hue: Float, saturation: Float, brightness: Float) : this(Color.HSBtoRGB(hue, saturation, brightness)) 31 | 32 | var rgba: Int = color 33 | set(value) { 34 | field = value 35 | // Update the HSV value if it is not null 36 | if (hsv != null) 37 | Color.RGBtoHSB(rgba.red, rgba.green, rgba.blue, hsv) 38 | } 39 | var hsv: FloatArray? = null 40 | 41 | fun getHSV(): FloatArray { 42 | if (hsv == null) { 43 | hsv = FloatArray(3) 44 | Color.RGBtoHSB(rgba.red, rgba.green, rgba.blue, hsv) 45 | } 46 | return hsv!! 47 | } 48 | 49 | fun getRed() = rgba.red 50 | 51 | fun getGreen() = rgba.green 52 | 53 | fun getBlue() = rgba.blue 54 | 55 | fun getAlpha() = rgba.alpha 56 | 57 | override fun copy(): UIColor = UIColor(rgba) 58 | 59 | override fun merge(other: UIColor?) { 60 | if (other != null) { 61 | rgba = other.rgba 62 | } 63 | } 64 | 65 | override fun animate( 66 | context: AnimationContext<*>, 67 | initial: UIColor?, 68 | start: UIColor?, 69 | end: UIColor?, 70 | progress: Float, 71 | completed: Boolean 72 | ) { 73 | rgba = colorLerp(start?.rgba ?: initial?.rgba ?: 0, end?.rgba ?: initial?.rgba ?: 0, progress) 74 | } 75 | 76 | override fun toString(): String = 77 | "UIColor(color=$rgba, hsv=${hsv.contentToString()}, r:${rgba.red}, g:${rgba.green}, b:${rgba.blue}, a:${rgba.alpha})" 78 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/debug/Logger.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.debug 2 | 3 | enum class LogLevel { 4 | DEBUG, 5 | GLOBAL 6 | } 7 | 8 | @PublishedApi 9 | internal inline fun timed(message: String, level: LogLevel = LogLevel.DEBUG, block: () -> Unit) { 10 | if (level != LogLevel.DEBUG) { 11 | println("[UIDebug]: TIMED -> $message") 12 | val start = System.currentTimeMillis() 13 | block() 14 | println("[UIDebug]: TIMED -> took ${System.currentTimeMillis() - start}ms") 15 | } 16 | } 17 | 18 | @PublishedApi 19 | internal fun inform(message: String, level: LogLevel = LogLevel.DEBUG) { 20 | if (level != LogLevel.DEBUG) 21 | println("[UIDebug]: INFO -> $message") 22 | } 23 | 24 | @PublishedApi 25 | internal fun debug(message: String) { 26 | println("[UIDebug]: DEBUG -> $message") 27 | } 28 | 29 | @PublishedApi 30 | internal fun warn(message: String, level: LogLevel = LogLevel.DEBUG) { 31 | if (level != LogLevel.DEBUG) 32 | println("[UIDebug]: WARNING -> $message") 33 | } 34 | 35 | @PublishedApi 36 | internal fun error(message: String) { 37 | println("[UIDebug]: ERR -> $message") 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/UIEase.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease 2 | 3 | import net.prismclient.aether.core.ease.UIEaseDirection.* 4 | import net.prismclient.aether.core.ease.UIEaseType.* 5 | import net.prismclient.aether.core.ease.impl.* 6 | import net.prismclient.aether.core.util.property.Copyable 7 | 8 | abstract class UIEase(var duration: Long = 1000L, var animationDirection: UIEaseDirection = INOUT) : Copyable { 9 | var animating: Boolean = false 10 | var finished: Boolean = false 11 | var startTime: Long = 0L 12 | var endTime: Long = 0L 13 | var delay: Long = 0L 14 | 15 | fun start() { 16 | startTime = System.currentTimeMillis() + delay 17 | endTime = startTime + duration 18 | animating = true 19 | finished = false 20 | } 21 | 22 | private fun finish() { 23 | animating = false 24 | finished = true 25 | } 26 | 27 | @JvmOverloads 28 | fun reset(duration: Long = this.duration) { 29 | this.duration = duration 30 | startTime = 0L 31 | endTime = 0L 32 | animating = false 33 | finished = false 34 | } 35 | 36 | protected fun get(): Float { 37 | if (!animating && !finished) return 0f 38 | if (finished) return 1f 39 | 40 | return ((System.currentTimeMillis() - startTime).toFloat() / duration.toFloat()) 41 | .coerceAtMost(1f) 42 | .coerceAtLeast(0f) 43 | .also { 44 | if (it >= 1f) { 45 | finish() 46 | } 47 | } 48 | } 49 | 50 | abstract fun getValue(): Double 51 | 52 | companion object { 53 | fun typeFromString(easeType: String): UIEaseType { 54 | return when (easeType.lowercase()) { 55 | "linear" -> LINEAR 56 | "sine" -> SINE 57 | "quad" -> QUAD 58 | "cubic" -> CUBIC 59 | "quart" -> QUART 60 | "quint" -> QUINT 61 | "expo", "exponential" -> EXPO 62 | "circ", "circle" -> CIRC 63 | else -> LINEAR 64 | } 65 | } 66 | 67 | fun directionFromString(easeDirection: String): UIEaseDirection { 68 | return when (easeDirection.lowercase()) { 69 | "in" -> IN 70 | "out" -> OUT 71 | "inout" -> INOUT 72 | else -> INOUT 73 | } 74 | } 75 | 76 | fun getEase(duration: Long = 1000L, easeType: UIEaseType, easeDirection: UIEaseDirection = INOUT): UIEase { 77 | return when (easeType) { 78 | LINEAR -> UILinear(duration, easeDirection) 79 | SINE -> UISine(duration, easeDirection) 80 | QUAD -> UIQuad(duration, easeDirection) 81 | CUBIC -> UICubic(duration, easeDirection) 82 | QUART -> UIQuart(duration, easeDirection) 83 | QUINT -> UIQuint(duration, easeDirection) 84 | EXPO -> UIExponential(duration, easeDirection) 85 | CIRC -> UICircle(duration, easeDirection) 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/UIEaseDirection.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease 2 | 3 | enum class UIEaseDirection { 4 | IN, 5 | OUT, 6 | INOUT 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/UIEaseType.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease 2 | 3 | enum class UIEaseType { 4 | LINEAR, 5 | SINE, 6 | QUAD, 7 | CUBIC, 8 | QUART, 9 | QUINT, 10 | EXPO, 11 | CIRC 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/impl/UICircle.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease.impl 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.ease.UIEaseDirection 5 | import kotlin.math.pow 6 | import kotlin.math.sqrt 7 | 8 | class UICircle(duration: Long = 1000L, animationDirection: UIEaseDirection = UIEaseDirection.INOUT) : 9 | UIEase(duration, animationDirection) { 10 | override fun getValue(): Double { 11 | val x = this.get().toDouble() 12 | 13 | return when (animationDirection) { 14 | UIEaseDirection.IN -> 1.0 - sqrt(1.0 - x.pow(2)) 15 | UIEaseDirection.OUT -> sqrt(1.0 - (x - 1.0).pow(2)) 16 | UIEaseDirection.INOUT -> if (x < 0.5) (1.0 - sqrt(1.0 - (2.0 * x).pow(2))) / 2.0 else (sqrt( 17 | 1.0 - (-2.0 * x + 2.0).pow( 18 | 2 19 | ) 20 | ) + 1.0) / 2.0 21 | } 22 | } 23 | 24 | override fun copy(): UIEase = UICircle(duration, animationDirection) 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/impl/UICubic.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease.impl 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.ease.UIEaseDirection 5 | import kotlin.math.pow 6 | 7 | class UICubic(duration: Long = 1000L, animationDirection: UIEaseDirection = UIEaseDirection.INOUT) : 8 | UIEase(duration, animationDirection) { 9 | override fun getValue(): Double { 10 | val x = this.get().toDouble() 11 | 12 | return when (animationDirection) { 13 | UIEaseDirection.IN -> x * x * x 14 | UIEaseDirection.OUT -> 1 - (1 - x).pow(3.0) 15 | UIEaseDirection.INOUT -> if (x < 0.5) 4 * x * x * x else 1 - (-2 * x + 2).pow(3) / 2.0 16 | } 17 | } 18 | 19 | override fun copy(): UIEase = UICubic(duration, animationDirection) 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/impl/UIExponential.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease.impl 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.ease.UIEaseDirection 5 | import kotlin.math.pow 6 | 7 | class UIExponential(duration: Long = 1000L, animationDirection: UIEaseDirection = UIEaseDirection.INOUT) : 8 | UIEase(duration, animationDirection) { 9 | override fun getValue(): Double { 10 | val x = this.get().toDouble() 11 | 12 | return when (animationDirection) { 13 | UIEaseDirection.IN -> if (x == 0.0) 0.0 else 2.0.pow(10 * x - 10) 14 | UIEaseDirection.OUT -> if (x == 1.0) 1.0 else 1.0 - 2.0.pow(-10 * x) 15 | UIEaseDirection.INOUT -> if (x == 0.0) { 16 | 0.0 17 | } else { 18 | if (x == 1.0) { 19 | 1.0 20 | } else if (x < 0.5) { 21 | 2.0.pow(20.0 * x - 10.0) / 2.0 22 | } else { 23 | (2.0 - 2.0.pow(-20.0 * x + 10.0)) / 2.0 24 | } 25 | } 26 | } 27 | } 28 | 29 | override fun copy(): UIEase = UIExponential(duration, animationDirection) 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/impl/UILinear.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease.impl 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.ease.UIEaseDirection 5 | 6 | class UILinear(duration: Long = 1000L, animationDirection: UIEaseDirection = UIEaseDirection.INOUT) : 7 | UIEase(duration, animationDirection) { 8 | override fun getValue(): Double = get().toDouble() 9 | 10 | override fun copy(): UIEase = UILinear(duration, animationDirection) 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/impl/UIQuad.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease.impl 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.ease.UIEaseDirection 5 | import kotlin.math.pow 6 | 7 | class UIQuad(duration: Long = 1000L, animationDirection: UIEaseDirection = UIEaseDirection.INOUT) : 8 | UIEase(duration, animationDirection) { 9 | override fun getValue(): Double { 10 | val x = this.get().toDouble() 11 | 12 | return when (animationDirection) { 13 | UIEaseDirection.IN -> x * x 14 | UIEaseDirection.OUT -> 1.0 - (1.0 - x) * (1.0 - x) 15 | UIEaseDirection.INOUT -> if (x < 0.5) 2.0 * x * x else 1.0 - (-2.0 * x + 2.0).pow(2.0) / 2.0 16 | } 17 | } 18 | 19 | override fun copy(): UIEase = UIQuad(duration, animationDirection) 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/impl/UIQuart.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease.impl 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.ease.UIEaseDirection 5 | import kotlin.math.pow 6 | 7 | class UIQuart(duration: Long = 1000L, animationDirection: UIEaseDirection = UIEaseDirection.INOUT) : 8 | UIEase(duration, animationDirection) { 9 | override fun getValue(): Double { 10 | val x = this.get().toDouble() 11 | 12 | return when (animationDirection) { 13 | UIEaseDirection.IN -> x * x * x * x 14 | UIEaseDirection.OUT -> 1.0 - (1.0 - x).pow(4.0) 15 | UIEaseDirection.INOUT -> (if (x < 0.5f) (8.0 * x * x * x * x) else (1.0 - (-2.0 * x + 2.0).pow(4.0) / 2.0)) 16 | } 17 | } 18 | 19 | override fun copy(): UIEase = UIQuart(duration, animationDirection) 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/impl/UIQuint.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease.impl 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.ease.UIEaseDirection 5 | import kotlin.math.pow 6 | 7 | class UIQuint(duration: Long = 1000L, animationDirection: UIEaseDirection = UIEaseDirection.INOUT) : 8 | UIEase(duration, animationDirection) { 9 | override fun getValue(): Double { 10 | val x = this.get().toDouble() 11 | 12 | return when (animationDirection) { 13 | UIEaseDirection.IN -> x * x * x * x * x 14 | UIEaseDirection.OUT -> 1.0 - (1 - x).pow(5) 15 | UIEaseDirection.INOUT -> if (x < 0.5) 16.0 * x * x * x * x * x else 1.0 - (-2 * x + 2).pow(5) / 2.0 16 | } 17 | } 18 | 19 | override fun copy(): UIEase = UIQuint(duration, animationDirection) 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/ease/impl/UISine.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.ease.impl 2 | 3 | import net.prismclient.aether.core.ease.UIEase 4 | import net.prismclient.aether.core.ease.UIEaseDirection 5 | import kotlin.math.PI 6 | import kotlin.math.cos 7 | import kotlin.math.sin 8 | 9 | class UISine(duration: Long = 1000L, animationDirection: UIEaseDirection = UIEaseDirection.INOUT) : 10 | UIEase(duration, animationDirection) { 11 | override fun getValue(): Double { 12 | val x = this.get().toDouble() 13 | 14 | return when (animationDirection) { 15 | UIEaseDirection.IN -> 1.0 - cos((x * PI) / 2.0) 16 | UIEaseDirection.OUT -> sin((x * PI) / 2.0) 17 | UIEaseDirection.INOUT -> -(cos(PI * x) - 1.0) / 2.0 18 | } 19 | } 20 | 21 | override fun copy(): UIEase = UISine(duration, animationDirection) 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/event/CompositionEvents.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.event 2 | 3 | import net.prismclient.aether.core.input.MouseButtonType 4 | import net.prismclient.aether.ui.composition.Composable 5 | import net.prismclient.aether.ui.composition.Composition 6 | 7 | /** 8 | * Indicates an event where a singular composable is invoked, and the event moves (propagates) up the 9 | * composition tree until the composition. [propagate] informs this to propagate up. 10 | * 11 | * //todo: better documentatino for PropagatingEvents 12 | * 13 | * @author sen 14 | * @since 1.0 15 | */ 16 | abstract class PropagatingEvent(val initialComposable: Composable) : CustomEvent(initialComposable) { 17 | /** 18 | * The amount of [Composable] (nodes) which this [PropagatingEvent] has passed through; the amount 19 | * of times this has been propagated 20 | */ 21 | var propagationIndex: Int = 0 22 | 23 | /** 24 | * The active [Composable] which is being propagated 25 | */ 26 | var currentComposable: Composable = initialComposable 27 | 28 | /** 29 | * The [Composable] which propagated to the [currentComposable] level. 30 | */ 31 | var previousComposable: Composable = initialComposable 32 | 33 | var requiresRecompose: Boolean = false 34 | 35 | // TODO: Cancellable Propagating Events 36 | 37 | /** 38 | * Updates the [currentComposable] and [previousComposable] up one node of the tree and publishes this to the new [Composable]. 39 | */ 40 | fun propagate() { 41 | propagationIndex++ 42 | if (currentComposable is Composition && (currentComposable as Composition).isTopLayer()) { 43 | if (requiresRecompose) currentComposable.compose() 44 | return 45 | } 46 | previousComposable = currentComposable 47 | currentComposable = currentComposable.parent ?: currentComposable.composition!! 48 | currentComposable.publish(this) 49 | } 50 | 51 | /** 52 | * Returns true if the [initialComposable] is equal to the [currentComposable], and the [propagationIndex] 53 | * is 0. In other words, if this is at the first propagation level, this will return true. 54 | */ 55 | fun isInitial(): Boolean = currentComposable == initialComposable && propagationIndex == 0 56 | 57 | /** 58 | * Indicates to the composition that this even is going to propagate to that the layout needs to be recomposed. 59 | */ 60 | override fun recompose() { 61 | requiresRecompose = true 62 | } 63 | } 64 | 65 | /** 66 | * Invoked prior to rendering. 67 | * 68 | * @see RenderEvent 69 | */ 70 | class PreRenderEvent : UIEvent 71 | 72 | /** 73 | * Invoked after rendering the compositions. 74 | * 75 | * @see PreRenderEvent 76 | */ 77 | class RenderEvent : UIEvent 78 | 79 | /** 80 | * Invoked when the mouse is moved. [mouseX] and [mouseY] represent the mouse coordinates. 81 | */ 82 | class MouseMove(val mouseX: Float, val mouseY: Float) : UIEvent 83 | 84 | /** 85 | * A [PropagatingEvent] which holds the [mouseX] and [mouseY] coordinates as well as the [button] pressed. Because 86 | * this is a [PropagatingEvent], a specific composable is invoked and then the parent of that composable is invoked. 87 | */ 88 | class MousePress(val mouseX: Float, val mouseY: Float, val button: MouseButtonType, composable: Composable) : 89 | PropagatingEvent(composable) 90 | 91 | /** 92 | * Invoked when the mouse is released. [mouseX] and [mouseY] represent the mouse coordinates, and [button] 93 | * represents the button of which was released. 94 | */ 95 | class MouseRelease(val mouseX: Float, val mouseY: Float, val button: MouseButtonType) : UIEvent 96 | 97 | // MouseScrolled && KeyPressed are within FocusableEvents.kt !!! 98 | // ~ sen ~ -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/event/CustomEvent.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.event 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | 5 | /** 6 | * [CustomEvent] is used to indicate to Aether to disable automatically registering 7 | * a global listener. Behind the scenes, when an event first is registered, an event 8 | * is registered to the global event bus so that it can be listened as 9 | * 10 | * You -> Global Eventbus -> Composable Listeners 11 | * 12 | * This process automatically happens to all events the first time they are registered 13 | * to any Composable. However, in certain cases, such as with [PropagatingEvent]s, it is 14 | * handled in such a way that does not require this. Implementing this interface informs 15 | * Aether that the event is not intended to automatically allocate that listener. 16 | * 17 | * PLEASE NOTE THE [Composable.recompose] SHOULD BE INVOKED AFTER THE EVENT COMPLETION. 18 | * 19 | * 20 | * @author sen 21 | * @since 1.0 22 | */ 23 | abstract class CustomEvent(val composable: Composable) : UIEvent { 24 | open fun recompose() { 25 | composable.recompose() 26 | } 27 | } 28 | 29 | // TODO: Convert to annotation -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/event/FocusableEvents.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.event 2 | 3 | import net.prismclient.aether.core.Aether 4 | import net.prismclient.aether.core.input.UIKey 5 | import net.prismclient.aether.ui.composition.Composable 6 | 7 | /** 8 | * A type of event which indicates that the only composable invoked is the focused component, [Aether.focusedComponent]. 9 | */ 10 | abstract class FocusedEvent(val focusedComposable: Composable) : CustomEvent(focusedComposable) 11 | 12 | /** 13 | * A [FocusedEvent] which invokes the focused composable. [dstX] and [dstY] represents the distance 14 | * the mouse or trackpad was scrolled in the given axis. 15 | * 16 | * @author sen 17 | * @since 1.0 18 | */ 19 | class MouseScrolled(val dstX: Float, val dstY: Float, focusedComposable: Composable) : FocusedEvent(focusedComposable) 20 | 21 | /** 22 | * A [FocusedEvent] which invokes the focused composable. If the key cannot be mapped to a character, 23 | * [character] will equal to '\u0000'. 24 | * 25 | * @author sen 26 | * @since 1.0 27 | */ 28 | class KeyPressed(val character: Char, val key: UIKey, focusedComposable: Composable) : FocusedEvent(focusedComposable) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/event/UIEvent.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.event 2 | 3 | /** 4 | * @author sen 5 | * @since 1.0 6 | */ 7 | interface UIEvent { 8 | companion object 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/event/UIEventBus.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.event 2 | 3 | import net.prismclient.aether.core.debug.warn 4 | import java.util.concurrent.ConcurrentHashMap 5 | import java.util.function.Consumer 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * [UIEventBus] handles all events within Aether. This may vary from user input to state events such as a change 10 | * within a component. The structure of this event bus follows the general concept, where an event is registered, 11 | * and somewhere the event is published, along with all of its data which the registered events can listen. 12 | * 13 | * @author sen, Decencies 14 | * @since 1.0 15 | */ 16 | object UIEventBus { 17 | @JvmStatic 18 | val events: HashMap, ConcurrentHashMap>> = hashMapOf() 19 | 20 | /** 21 | * Registers the given the [event] to the given event type [T]. 22 | */ 23 | inline fun register(eventName: String? = null, event: Consumer) { 24 | events.computeIfAbsent(T::class) { ConcurrentHashMap() }[eventName ?: event.toString()] = event 25 | } 26 | 27 | /** 28 | * The Java version of [register]. Registers the given [event] to the even type [type]. 29 | */ 30 | @JvmStatic 31 | @JvmOverloads 32 | fun register(eventName: String? = null, type: Class, event: Consumer) { 33 | events.computeIfAbsent(type.kotlin) { ConcurrentHashMap() }[eventName ?: event.toString()] = event 34 | } 35 | 36 | /** 37 | * Unregisters the [eventName] associated with the event, [T]. The event can be unregistered 38 | * during publishing; however, it will not actually be removed until after the event is published. 39 | */ 40 | inline fun unregister(eventName: String) { 41 | events[T::class]?.remove(eventName) 42 | } 43 | 44 | /** 45 | * The Java version of [unregister]. Unregisters the [eventName] associated with the event, 46 | * [event]. The event can be unregistered during publishing; however, it will not actually 47 | * be removed until after the event is published. 48 | */ 49 | @JvmStatic 50 | fun unregister(eventName: String, event: Class) { 51 | events[event.kotlin]?.remove(eventName) 52 | } 53 | 54 | /** 55 | * Invokes all listeners of the given [event]. 56 | * 57 | * @see register 58 | */ 59 | @JvmStatic 60 | @Suppress("Unchecked_Cast") 61 | fun publish(event: T) { 62 | if (events.containsKey(event::class)) { 63 | events[event::class]!!.forEach { (_, consumer) -> 64 | try { 65 | (consumer as Consumer).accept(event) 66 | } catch (e: Exception) { 67 | warn("Error while publishing event: ${event::class.simpleName}") 68 | e.printStackTrace() 69 | } 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/input/MouseButtonType.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.input 2 | 3 | /** 4 | * [MouseButtonType] represents the buttons on the mouse. 5 | * 6 | * @author sen 7 | * @since 1.0 8 | */ 9 | enum class MouseButtonType { 10 | /** 11 | * None of the given mouse buttons. Pass this when the mouse is moved. 12 | */ 13 | None, 14 | 15 | /** 16 | * The left button of the mouse, generally represented as 0. 17 | */ 18 | Primary, 19 | 20 | /** 21 | * The scroll / middle button, generally represented as 1. 22 | */ 23 | Middle, 24 | 25 | /** 26 | * The right button of the mouse, generally represented as 2. 27 | */ 28 | Secondary 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/input/UIKey.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.input 2 | 3 | /** 4 | * [UIKey] is an enum class which represents the keys on a keyboard. When accepting input, 5 | * the key pressed from whatever library should be mapped to the given values 6 | * 7 | * @author sen 8 | * @since 1.0 9 | */ 10 | enum class UIKey { 11 | UNKOWN, 12 | KEY_SPACE, 13 | KEY_APOSTROPHE, 14 | KEY_COMMA, 15 | KEY_MINUS, 16 | KEY_PERIOD, 17 | KEY_SLASH, 18 | KEY_0, 19 | KEY_1, 20 | KEY_2, 21 | KEY_3, 22 | KEY_4, 23 | KEY_5, 24 | KEY_6, 25 | KEY_7, 26 | KEY_8, 27 | KEY_9, 28 | KEY_SEMICOLON, 29 | KEY_EQUAL, 30 | KEY_A, 31 | KEY_B, 32 | KEY_C, 33 | KEY_D, 34 | KEY_E, 35 | KEY_F, 36 | KEY_G, 37 | KEY_H, 38 | KEY_I, 39 | KEY_J, 40 | KEY_K, 41 | KEY_L, 42 | KEY_M, 43 | KEY_N, 44 | KEY_O, 45 | KEY_P, 46 | KEY_Q, 47 | KEY_R, 48 | KEY_S, 49 | KEY_T, 50 | KEY_U, 51 | KEY_V, 52 | KEY_W, 53 | KEY_X, 54 | KEY_Y, 55 | KEY_Z, 56 | KEY_LEFT_BRACKET, 57 | KEY_BACKSLASH, 58 | KEY_RIGHT_BRACKET, 59 | KEY_GRAVE_ACCENT, 60 | KEY_WORLD, 61 | KEY_ESCAPE, 62 | KEY_ENTER, 63 | KEY_TAB, 64 | KEY_BACKSPACE, 65 | KEY_INSERT, 66 | KEY_DELETE, 67 | KEY_RIGHT, 68 | KEY_LEFT, 69 | KEY_DOWN, 70 | KEY_UP, 71 | KEY_PAGE_UP, 72 | KEY_PAGE_DOWN, 73 | KEY_HOME, 74 | KEY_END, 75 | KEY_CAPS_LOCK, 76 | KEY_SCROLL_LOCK, 77 | KEY_NUM_LOCK, 78 | KEY_PRINT_SCREEN, 79 | KEY_PAUSE, 80 | KEY_F1, 81 | KEY_F2, 82 | KEY_F3, 83 | KEY_F4, 84 | KEY_F5, 85 | KEY_F6, 86 | KEY_F7, 87 | KEY_F8, 88 | KEY_F9, 89 | KEY_F10, 90 | KEY_F11, 91 | KEY_F12, 92 | KEY_F13, 93 | KEY_F14, 94 | KEY_F15, 95 | KEY_F16, 96 | KEY_F17, 97 | KEY_F18, 98 | KEY_F19, 99 | KEY_F20, 100 | KEY_F21, 101 | KEY_F22, 102 | KEY_F23, 103 | KEY_F24, 104 | KEY_F25, 105 | KEY_KP_0, 106 | KEY_KP_1, 107 | KEY_KP_2, 108 | KEY_KP_3, 109 | KEY_KP_4, 110 | KEY_KP_5, 111 | KEY_KP_6, 112 | KEY_KP_7, 113 | KEY_KP_8, 114 | KEY_KP_9, 115 | KEY_KP_DECIMAL, 116 | KEY_KP_DIVIDE, 117 | KEY_KP_MULTIPLY, 118 | KEY_KP_SUBTRACT, 119 | KEY_KP_ADD, 120 | KEY_KP_ENTER, 121 | KEY_KP_EQUAL, 122 | KEY_LEFT_SHIFT, 123 | KEY_LEFT_CONTROL, 124 | KEY_LEFT_ALT, 125 | KEY_LEFT_SUPER, 126 | KEY_RIGHT_SHIFT, 127 | KEY_RIGHT_CONTROL, 128 | KEY_RIGHT_ALT, 129 | KEY_RIGHT_SUPER, 130 | KEY_MENU 131 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/input/UIKeyAction.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.input 2 | 3 | /** 4 | * [UIKeyAction] describes the action of a key press. See 5 | * the respected enums for documentation. 6 | * 7 | * @author sen 8 | * @since 5/13/2022 9 | */ 10 | enum class UIKeyAction { 11 | /** 12 | * The initial press of any key 13 | */ 14 | PRESS, 15 | 16 | /** 17 | * The release of a key that was pressed 18 | */ 19 | RELEASE, 20 | 21 | /** 22 | * Like key press, excepts that the key is held, and it is being invoked again. 23 | */ 24 | REPEAT 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/input/UIModifierKey.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.input 2 | 3 | /** 4 | * [UIModifierKey] are keys which cannot be converted into characters. They are keys 5 | * such as Shift, Alt, Ctrl etc... 6 | * 7 | * @author sen 8 | * @since 6/15/2022 9 | */ 10 | enum class UIModifierKey { 11 | LEFT_CTRL, 12 | RIGHT_CTRL, 13 | LEFT_SHIFT, 14 | RIGHT_SHIFT, 15 | LEFT_ALT, 16 | RIGHT_ALT, 17 | ARROW_LEFT, 18 | ARROW_RIGHT, 19 | ARROW_UP, 20 | ARROW_DOWN, 21 | TAB, 22 | ESCAPE, 23 | CAPS_LOCK, 24 | BACKSPACE, 25 | ENTER, 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/metrics/Position.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.metrics 2 | 3 | /** 4 | * Represents a 2D coordinate. 5 | * 6 | * @author sen 7 | * @since 1.0 8 | */ 9 | data class Position(val x: Float, val y: Float) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/metrics/Size.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.metrics 2 | 3 | /** 4 | * Represents a size, which is a class that holds a width and height. 5 | * 6 | * @author sen 7 | * @since 1.0 8 | */ 9 | data class Size(val width: Float, val height: Float) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/extensions/FontExtension.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.extensions 2 | 3 | import net.prismclient.aether.core.Aether 4 | import org.apache.commons.io.IOUtils 5 | import java.io.FileNotFoundException 6 | import java.io.InputStream 7 | import java.nio.ByteBuffer 8 | import java.nio.ByteOrder 9 | 10 | /** 11 | * Returns a [ByteBuffer] from the given input stream. An exception is thrown if the stream is not readable. 12 | */ 13 | fun InputStream.byteBuffer(): ByteBuffer { 14 | val bytes: ByteArray = IOUtils.toByteArray(this) 15 | val buff = ByteBuffer.allocateDirect(bytes.size + 1) 16 | buff.order(ByteOrder.nativeOrder()) 17 | buff.put(bytes) 18 | // Null terminating 19 | buff.put(0) 20 | buff.flip() 21 | return buff 22 | } 23 | 24 | /** 25 | * Returns a [ByteBuffer] from the given input stream. If it fails to read the stream, it will return null. 26 | */ 27 | fun InputStream.safeByteBuffer(): ByteBuffer? { 28 | return try { 29 | this.byteBuffer() 30 | } catch (e: Exception) { 31 | null 32 | } 33 | } 34 | 35 | /** 36 | * Returns a ByteBuffer from the given class path file location. An exception is 37 | * thrown if the file is not found. 38 | */ 39 | fun String.toByteBuffer(): ByteBuffer = 40 | (Aether::class.java.getResourceAsStream(this) 41 | ?: throw FileNotFoundException("[$this] was not found within the classpath.")).byteBuffer() 42 | 43 | /** 44 | * Returns a ByteBuffer from the given class path location. If it fails to read the file, it will return null. 45 | */ 46 | fun String.safeByteBuffer(): ByteBuffer? { 47 | try { 48 | return this.toByteBuffer() 49 | } catch (ignored: Exception) { 50 | } 51 | return null 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/other/ComposableGroup.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.other 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | import net.prismclient.aether.ui.dsl.ComposeDSL 5 | 6 | /** 7 | * Indicates to Aether, specifically [ComposeDSL] that this contains a group of [Composable]s, known as [children]. 8 | * 9 | * @author sen 10 | * @since 1.0 11 | */ 12 | interface ComposableGroup { 13 | val children: ArrayList 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/property/Animatable.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.property 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | 5 | /** 6 | * [Animatable] is an interface of which indicates an object which can be animated 7 | * given two of itself and a progress value. [T] represents the class of which this 8 | * interface is implemented. The [Copyable] must also be implemented when implementing 9 | * this. It is used to create "snapshots" a.k.a. initial values for the animation. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | interface Animatable : Copyable { 15 | /** 16 | * Linearly interpolates (lerp) the properties of this with the given [start] and 17 | * [end] based on the [progress]. 18 | * 19 | * If a property is changed of which the layout needs to be recomposed, invoke 20 | * [AnimationContext.recompose] to indicate this. 21 | */ 22 | fun animate(context: AnimationContext<*>, initial: T?, start: T?, end: T?, progress: Float, completed: Boolean) 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/property/Copyable.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.property 2 | 3 | /** 4 | * [Copyable] is used for creating deep copies (same properties, different references) of an object. 5 | * 6 | * @author sen 7 | * @since 1.0 8 | * @see UIProperty 9 | */ 10 | interface Copyable { 11 | /** 12 | * Every property within this, [T], is copied to a new object of this. 13 | */ 14 | fun copy(): T 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/property/Focusable.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.property 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | 5 | 6 | /** 7 | * [Focusable] is an interface to inform Aether that this is a focusable [Composable]. A focusable 8 | * composable is a composable eligible for events which target specifically focused composables, such 9 | * as a scrolling or keyPress event. The intended purpose is to ensure that multiple layouts, for 10 | * example, are not being scrolled at the same time, or multiple text fields being typed at once. 11 | * 12 | * Implementing this makes it immediately eligible for focusability. 13 | * 14 | * @author sen 15 | * @since 1.0 16 | */ 17 | interface Focusable { 18 | /** 19 | * If Aether determines that this is a valid [Composable] for focus, it will first ask 20 | * this function if it wants to be focused. 21 | */ 22 | fun wantsFocus(): Boolean 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/property/Mergable.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.property 2 | 3 | /** 4 | * [Mergable] is a single function interface which accepts [T], the class to be merged. The 5 | * function [merge] expects the paramater other to apply all the non-null fields to this. The 6 | * new properties should be a deep copy of the old properties. 7 | * 8 | * @author sen 9 | * @since 1.0 10 | * @see UIProperty 11 | */ 12 | @Suppress("SpellCheckingInspection") 13 | interface Mergable { 14 | /** 15 | * Applies the non-null fields of [other] to this. The fields should be a deep copy (if applicable). 16 | */ 17 | fun merge(other: T?) 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/property/UIProperty.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.property 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | 5 | /** 6 | * [UIProperty] provides a shorthand for implementing the 4 key single function interfaces 7 | * for copying, merging, animating, and updating. Shapes and properties, such as the padding 8 | * or radius require these interfaces to provide this necessary features to make Aether work. 9 | * 10 | * @author sen 11 | * @since 1.0 12 | * @see UIUniqueProperty 13 | */ 14 | interface UIProperty : Updatable, Copyable, Mergable, Animatable 15 | 16 | /** 17 | * A [UIProperty], where the second generic type, [C] represents the [Composable] for [Updatable] 18 | * 19 | * @author sen 20 | * @since 1.0 21 | * @see UIProperty 22 | */ 23 | interface UIUniqueProperty : Updatable, Copyable, Mergable, Animatable -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/property/Updatable.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.property 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | 5 | /** 6 | * [Updatable] is an interface which provides an update function that expects a nullable [T] which is 7 | * a [Composable] or a subclass of it. 8 | * 9 | * @authors sen 10 | * @since 1.0 11 | * @see UIProperty 12 | */ 13 | interface Updatable { 14 | /** 15 | * Invoked when the composition is updated. 16 | */ 17 | fun compose(composable: T?) 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/shorthands/Color.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.shorthands 2 | 3 | import net.prismclient.aether.core.color.UIColor 4 | 5 | /** 6 | * ColorKt provides a set of shorthands for obtaining information and creating colors. Colors 7 | * internally are stored as a UIColor, and within are stored as an Int formatted as ARGB or 8 | * 0xAARRGGBB / 0xRRGGBB in hex. See UIColor for more documentation on this choice. 9 | * 10 | * @author sen 11 | * @since 1.0 12 | */ 13 | 14 | /** 15 | * Returns a String from the given [Int] with the format of (R.G,B,A) 16 | */ 17 | fun Int.toColorString() = "${this.red},${this.green},${this.blue},${this.alpha}" 18 | 19 | /** 20 | * Returns the red color of the given color. The provided Int is expected to store the color as RGBA. 21 | */ 22 | @get:JvmName("red") 23 | inline val Int.red: Int get() = this shr 16 and 0xFF 24 | 25 | /** 26 | * Returns the green color of the given color. The provided Int is expected to store the color as RGBA. 27 | */ 28 | @get:JvmName("green") 29 | inline val Int.green: Int get() = this shr 8 and 0xFF 30 | 31 | /** 32 | * Returns the blue color of the given color. The provided Int is expected to store the color as RGBA. 33 | */ 34 | @get:JvmName("blue") 35 | inline val Int.blue: Int get() = this and 0xFF 36 | 37 | /** 38 | * Returns the alpha color of the given color. The provided Int is expected to store the color as RGBA. 39 | */ 40 | @get:JvmName("alpha") 41 | inline val Int.alpha: Int get() = this shr 24 and 0xFF 42 | 43 | /** 44 | * Returns a [UIColor] from the given Int stored in the RGB format. The alpha is assumed to be 255. Use 45 | * this instead of [Number.rgba] for hex values, as the alpha is considered to be 0. 46 | */ 47 | // Mask the alpha value and set it to 0xFF (255). 48 | @get:JvmName("rgb") 49 | inline val Number.rgb: UIColor get() = UIColor((this.toInt() and 0x00FFFFFF) or (0xFF shl 24)) 50 | 51 | /** 52 | * Returns a [UIColor] from the given Int stored in the RGBA format. For hex values use [Number.rgb] as the alpha 53 | * will be 255 instead of 0. 54 | */ 55 | @get:JvmName("rgba") 56 | inline val Number.rgba: UIColor get() = UIColor(this.toInt()) 57 | 58 | /** 59 | * Returns the given [color] with 255 alpha. 60 | */ 61 | fun RGB(color: Int): Int = (color and 0x00FFFFFF) or (0xFF shl 24) 62 | 63 | /** 64 | * Returns the given [color] with the applied [alpha] 65 | */ 66 | fun RGBA(color: Int, alpha: Int): Int = (color and 0x00FFFFFF) or (alpha shl 24) 67 | 68 | fun RGBA(color: Int, alpha: Float): Int = RGBA(color, (alpha * 255 + 0.5f).toInt()) 69 | 70 | /** 71 | * Returns an ARGB formatted Int from the given [r], [g], [b] and [a]. 72 | */ 73 | @JvmOverloads 74 | fun RGBA(r: Int, g: Int, b: Int, a: Int = 255) = 75 | (r shl 16) or (g shl 8) or (b) or (a shl 24) 76 | 77 | /** 78 | * Returns an ARGB formatted Int from the given [r], [g], [b] and [a]. 79 | */ 80 | @JvmOverloads 81 | fun RGBA(r: Float, g: Float, b: Float, a: Float = 1f): Int = 82 | RGBA((r * 255 + 0.5f).toInt(), (g * 255 + 0.5f).toInt(), (b * 255 + 0.5f).toInt(), (a * 255 + 0.5f).toInt()) 83 | 84 | /** 85 | * Returns an ARGB formatted Int from the given [r], [g], [b] and [a]. 86 | */ 87 | fun RGBA(r: Int, g: Int, b: Int, a: Float) = RGBA(r, g, b, (a * 255 + 0.5).toInt()) 88 | 89 | /** 90 | * Creates a [UIColor] from the given [r], [g], [b] and [a] represented as a value between 0 and 255 each. 91 | */ 92 | @JvmOverloads 93 | fun ColorOf(r: Int, g: Int, b: Int, a: Int = 255) = RGBA(r, g, b, a).rgba 94 | 95 | /** 96 | * Creates a [UIColor] from the given [r], [g], [b] and [a] represented as a value between 0 and 1 each. 97 | */ 98 | @JvmOverloads 99 | fun ColorOf(r: Float, g: Float, b: Float, a: Float = 1f) = RGBA(r, g, b, a).rgba 100 | 101 | /** 102 | * Creates a [UIColor] from the given [r], [g], [b] represented as a value between 0 and 255 each and 103 | * [a] represented as a value between 0 and 1. 104 | */ 105 | fun ColorOf(r: Int, g: Int, b: Int, a: Float) = RGBA(r, g, b, a).rgba 106 | 107 | // TODO: RGB(A) functions for UIColor 108 | // fun cRGBA(r: Float, g: Float, b: Float, a: Float = 1f): UIColor = RGBA(r, g, b, a).rgb -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/shorthands/Default.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.shorthands 2 | 3 | import net.prismclient.aether.core.color.UIColor 4 | 5 | // Provides extensions for creating defaults of various classes if the given is null. 6 | 7 | inline val UIColor?.default: UIColor get() = this ?: UIColor(0) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/shorthands/FontUtil.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.shorthands 2 | 3 | import net.prismclient.aether.core.Aether 4 | import net.prismclient.aether.ui.dsl.UIRendererDSL 5 | import net.prismclient.aether.ui.renderer.UIRenderer 6 | 7 | /** 8 | * [Font] provides utilities for obtaining metrics for fonts. 9 | * 10 | * @author sen 11 | * @since 1.0 12 | */ 13 | private class Font 14 | 15 | /** 16 | * Returns an array of the metrics from the most recent text render call. The returned value 17 | * is a FloatArray formatted as: 18 | * 19 | * minX - The minimum x coordinate of the text. 20 | * minY - The minimum y coordinate of the text. 21 | * maxX - The maximum x coordinate of the text. 22 | * maxY - The maximum y coordinate of the text. 23 | * advance - The x coordinate of the next character's glyph position 24 | * 25 | * @see [UIRenderer.fontBounds] 26 | */ 27 | fun fontBounds(): FloatArray = Aether.renderer.fontBounds() 28 | 29 | /** 30 | * Returns an array of the metrics from the given String at the coordinate 31 | * point (0, 0). See [fontBounds] for the returned array format. 32 | */ 33 | inline val String.bounds: FloatArray get() = Aether.renderer.fontBounds(this) 34 | 35 | /** 36 | * Returns the minimum x coordinate of the given text array. 37 | * 38 | * @see fontBounds 39 | */ 40 | inline val FloatArray.minX: Float get() = this[0] 41 | 42 | /** 43 | * Returns the minimum y coordinate of the given text array. 44 | * 45 | * @see fontBounds 46 | */ 47 | inline val FloatArray.minY: Float get() = this[1] 48 | 49 | /** 50 | * Returns the maximum x coordinate of the given text array. 51 | * 52 | * @see fontBounds 53 | */ 54 | inline val FloatArray.maxX: Float get() = this[2] 55 | 56 | /** 57 | * Returns the maximum y coordinate of the given text array. 58 | * 59 | * @see fontBounds 60 | */ 61 | inline val FloatArray.maxY: Float get() = this[3] 62 | 63 | /** 64 | * Returns the x coordinate of the next character's glyph position. 65 | * 66 | * @see fontBounds 67 | */ 68 | inline val FloatArray.advance: Float get() = this[4] 69 | 70 | /** 71 | * Returns the width of the most recent text render call. 72 | * 73 | * @see fontBounds 74 | */ 75 | fun fontWidth(): Float = fontBounds().maxX - fontBounds().minX 76 | 77 | /** 78 | * Returns the height of the most recent text render call. 79 | * 80 | * @see fontBounds 81 | */ 82 | fun fontHeight(): Float = fontBounds().maxY - fontBounds().minY 83 | 84 | /** 85 | * Returns the ascender of the most recent text render call. 86 | */ 87 | fun fontAscender(): Float = UIRendererDSL.renderer.fontAscender() 88 | 89 | /** 90 | * Returns the descender of the most recent text render call. 91 | */ 92 | fun fontDescender(): Float = UIRendererDSL.renderer.fontDescender() -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/shorthands/General.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.shorthands 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/shorthands/Internal.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.shorthands 2 | 3 | import net.prismclient.aether.core.util.property.Copyable 4 | import net.prismclient.aether.core.util.property.Mergable 5 | import net.prismclient.aether.ui.unit.UIUnit 6 | import kotlin.math.roundToInt 7 | 8 | // Internal shorthands for Aether to reduce the amount of boilerplate code. 9 | 10 | /** 11 | * Indicates a high order function which specifies the receiver object as [T], accepts no 12 | * parameters / arguments and returns nothing. In other words it is a function which is 13 | * applied to [T] and is executed with no argument and returns nothing. 14 | */ 15 | internal typealias Block = T.() -> Unit 16 | 17 | // -- Null Checking -- // 18 | 19 | internal fun Any?.isNull() = this == null 20 | 21 | internal fun Any?.notNull() = this != null 22 | 23 | /** 24 | * Returns a deep copy of the receiver if not null, else other. If other is null, return null. 25 | * 26 | * This is used for [Mergable] to return the other value else this. 27 | */ 28 | @Suppress("Unchecked_Cast") 29 | internal infix fun > T?.or(other: T?): T? = (this?.copy() ?: other) as T? 30 | 31 | /** 32 | * Runs [block] if either [value] or [value1] are not null. 33 | */ 34 | internal inline fun ifNotNull(value: Any?, value1: Any? = null, block: () -> Unit) { 35 | if (value != null || value1 != null) block() 36 | } 37 | 38 | // -- Bounds -- // 39 | 40 | /** 41 | * Returns ture if [positionX] and [positionY] are within or equal to the bounds of [x], [y], [width] and [height]. 42 | */ 43 | internal fun within( 44 | positionX: Float, 45 | positionY: Float, 46 | x: Float, 47 | y: Float, 48 | width: Float, 49 | height: Float 50 | ) = positionX >= x && positionY >= y && positionX <= x + width && positionY <= y + height 51 | 52 | // -- Lerping -- // 53 | 54 | /** 55 | * Linearly interpolates between [start] and [end] by [progress] which are Ints, and returns an Int. 56 | */ 57 | internal fun lerp(start: Int, end: Int, progress: Float): Int = start + ((end - start) * progress).toInt() 58 | 59 | /** 60 | * Linearly interpolates between [start] and [end] by [progress] which are Floats, and returns a Float. 61 | */ 62 | internal fun lerp(start: Float, end: Float, progress: Float) = start + (end - start) * progress 63 | 64 | /** 65 | * Splits the given [color]s into four channels and lerps them individually to provide 66 | * a semi-accurate, fast lerping between two colors. 67 | */ 68 | internal fun colorLerp(startColor: Int, endColor: Int, progress: Float) = RGBA( 69 | startColor.red + ((endColor.red - startColor.red) * progress + 0.5f).toInt(), 70 | startColor.green + ((endColor.green - startColor.green) * progress + 0.5f).toInt(), 71 | startColor.blue + ((endColor.blue - startColor.blue) * progress + 0.5f).toInt(), 72 | startColor.alpha + ((endColor.alpha - startColor.alpha) * progress + 0.5f).toInt() 73 | ) 74 | 75 | /** 76 | * Linearly interpolates between [start] and [end] by [progress] which are Units, and adjust the 77 | * cached value to the output. If [start] or [end] are null, the value of [initial] is used. The 78 | * value of [initial] is considered 0f if null. 79 | */ 80 | internal fun UIUnit<*>.lerp(initial: UIUnit<*>?, start: UIUnit<*>?, end: UIUnit<*>?, progress: Float) { 81 | this.cachedValue = (start?.dp ?: initial.dp) + ((end?.dp ?: initial.dp) - (start?.dp ?: initial.dp)) * progress 82 | } 83 | 84 | // -- Other -- // 85 | 86 | /** 87 | * Creates a deep copy of the [Copyable] or null if it is null. 88 | */ 89 | @Suppress("unchecked_cast") 90 | internal inline val > T?.copy: T? get() = this?.copy() as? T -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/core/util/shorthands/Unit.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.core.util.shorthands 2 | 3 | import net.prismclient.aether.ui.unit.UIUnit 4 | import net.prismclient.aether.ui.unit.other.Margin 5 | import net.prismclient.aether.ui.unit.other.Padding 6 | import net.prismclient.aether.ui.unit.other.UIRadius 7 | import net.prismclient.aether.ui.unit.type.RelativeUnit 8 | import net.prismclient.aether.ui.unit.type.SizeUnit 9 | import net.prismclient.aether.ui.unit.type.other.OperationUnit 10 | import net.prismclient.aether.ui.unit.type.range.RangeUnit 11 | import net.prismclient.aether.ui.unit.type.solid.PixelUnit 12 | 13 | /** 14 | * UnitKt provides utility functions for creating and reading units. 15 | * 16 | * @author sen 17 | * @since 1.0 18 | */ 19 | 20 | // -- Kotlin Functions -- // 21 | 22 | /** 23 | * Creates a [PixelUnit] of the given value. A [PixelUnit] is a 1:1 unit of pixels. 24 | * 25 | * @see PixelUnit 26 | */ 27 | @get:JvmName("px") 28 | inline val Number.px: PixelUnit get() = PixelUnit(this.toFloat()) 29 | 30 | /** 31 | * Creates a [RelativeUnit] of the given value. A relative unit scales based on the width/height of the 32 | * composable's parent, or the width / height of the screen. If composable or it's parent is null. 33 | * 34 | * @see RelativeUnit 35 | */ 36 | @get:JvmName("rel") 37 | inline val Number.rel: RelativeUnit get() = RelativeUnit(this.toFloat()) 38 | 39 | /** 40 | * Creates a [SizeUnit] of the given value. A composable relative unit scales based on 41 | * the width / height of the composable which is passed through on update. 42 | * 43 | * @see SizeUnit 44 | */ 45 | @get:JvmName("crel") 46 | inline val Number.crel: SizeUnit get() = SizeUnit(this.toFloat()) 47 | 48 | /** 49 | * Creates a [UIRadius] with the given value for each corner. 50 | */ 51 | @get:JvmName("radius") 52 | inline val Number.radius: UIRadius get() = px.radii 53 | 54 | /** 55 | * Creates a [UIRadius] with the given unit. The unit is copied for each corner except the topLeft, where 56 | * the original UIUnit reference is placed. 57 | */ 58 | @get:JvmName("radii") 59 | inline val UIUnit<*>.radii: UIRadius get() = UIRadius(this, this.copy(), this.copy(), this.copy()) 60 | 61 | // -- Util -- // 62 | 63 | /** 64 | * Returns the cached value of the given unit, or 0f if the unit is null. 65 | */ 66 | @get:JvmName("dp") 67 | inline val UIUnit<*>?.dp: Float get() = this?.cachedValue ?: 0f 68 | 69 | // -- Operator Functions -- // 70 | 71 | operator fun UIUnit<*>?.plus(other: UIUnit<*>?): OperationUnit = OperationUnit(this, other, OperationUnit.Operation.ADD) 72 | 73 | operator fun UIUnit<*>?.minus(other: UIUnit<*>?): OperationUnit = 74 | OperationUnit(this, other, OperationUnit.Operation.SUBTRACT) 75 | 76 | operator fun UIUnit<*>?.times(other: UIUnit<*>?): OperationUnit = 77 | OperationUnit(this, other, OperationUnit.Operation.MULTIPLY) 78 | 79 | operator fun UIUnit<*>?.div(other: UIUnit<*>?): OperationUnit = 80 | OperationUnit(this, other, OperationUnit.Operation.DIVIDE) 81 | 82 | operator fun Number.plus(other: UIUnit<*>?): OperationUnit = OperationUnit(this.px, other, OperationUnit.Operation.ADD) 83 | 84 | operator fun Number.minus(other: UIUnit<*>?): OperationUnit = 85 | OperationUnit(this.px, other, OperationUnit.Operation.SUBTRACT) 86 | 87 | operator fun Number.times(other: UIUnit<*>?): OperationUnit = 88 | OperationUnit(this.px, other, OperationUnit.Operation.MULTIPLY) 89 | 90 | operator fun Number.div(other: UIUnit<*>?): OperationUnit = 91 | OperationUnit(this.px, other, OperationUnit.Operation.DIVIDE) 92 | 93 | // -- Range Operator Functions -- // 94 | 95 | /** 96 | * Creates a [RangeUnit] and ensures that this is at least the minimum value. If this is a range unit, 97 | * the minimum value is set to [min] and this is returned. 98 | */ 99 | fun UIUnit<*>.atLeast(min: UIUnit<*>): RangeUnit { 100 | if (this is RangeUnit) { 101 | this.min = min 102 | return this 103 | } 104 | return RangeUnit(this, min, null) 105 | } 106 | 107 | /** 108 | * Creates a [RangeUnit] and ensures that this is at most the maximum value. If this is a range unit, 109 | * the maximum value is set to [max] and this is returned. 110 | */ 111 | fun UIUnit<*>.atMost(max: UIUnit<*>): RangeUnit { 112 | if (this is RangeUnit) { 113 | this.max = max 114 | return this 115 | } 116 | return RangeUnit(this, null, max) 117 | } 118 | 119 | /** 120 | * Creates a [RangeUnit] and ensures that this is between the minimum and maximum values. If this is a 121 | * [RangeUnit], the minimum and maximum values are set to [min] and [max] and this is returned. 122 | */ 123 | fun UIUnit<*>.range(min: UIUnit<*>? = null, max: UIUnit<*>? = null): RangeUnit { 124 | if (this is RangeUnit) { 125 | this.min = min 126 | this.max = max 127 | return this 128 | } 129 | return RangeUnit(this, min, max) 130 | } 131 | 132 | // -- Padding and Margin Functions -- // 133 | 134 | /** 135 | * Creates a [Padding] with the given value for each side in pixels. 136 | */ 137 | @get:JvmName("padding") 138 | inline val Number.padding: Padding get() = Padding(this.px, this.px, this.px, this.px) 139 | 140 | /** 141 | * Creates a [Margin] with the given value for each side in pixels. 142 | */ 143 | @get:JvmName("margin") 144 | inline val Number.margin: Margin get() = Margin(this.px, this.px, this.px, this.px) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/alignment/Alignment.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.alignment 2 | 3 | /** 4 | * Represents the alignment of the horizontal and vertical axis. 5 | * 6 | * @author sen 7 | * @since 1.0 8 | * 9 | * @see HorizontalAlignment 10 | * @see VerticalAlignment 11 | */ 12 | @Suppress("Unused", "SpellCheckingInspection") 13 | enum class Alignment { 14 | TOPLEFT, TOPCENTER, TOPRIGHT, 15 | MIDDLELEFT, CENTER, MIDDLERIGHT, 16 | BOTTOMLEFT, BOTTOMCENTER, BOTTOMRIGHT 17 | } 18 | 19 | /** 20 | * Represents alignment on the horizontal (x) axis. 21 | * 22 | * @author sen 23 | * @since 1.0 24 | * 25 | * @see Alignment 26 | * @see VerticalAlignment 27 | */ 28 | enum class HorizontalAlignment { 29 | LEFT, CENTER, RIGHT 30 | } 31 | 32 | /** 33 | * Represents alignment on the vertical (y) axis. 34 | * 35 | * @author sen 36 | * @since 1.0 37 | * 38 | * @see Alignment 39 | * @see HorizontalAlignment 40 | */ 41 | enum class VerticalAlignment { 42 | TOP, MIDDLE, BOTTOM 43 | } 44 | 45 | /** 46 | * Returns the [Alignment] version of [HorizontalAlignment] 47 | */ 48 | @PublishedApi 49 | internal fun horizontalConvert(alignment: HorizontalAlignment): Alignment = when (alignment) { 50 | HorizontalAlignment.LEFT -> Alignment.TOPLEFT 51 | HorizontalAlignment.CENTER -> Alignment.TOPCENTER 52 | HorizontalAlignment.RIGHT -> Alignment.TOPRIGHT 53 | } 54 | 55 | /** 56 | * Returns the [Alignment] version of [VerticalAlignment] 57 | */ 58 | @PublishedApi 59 | internal fun verticalConvert(alignment: VerticalAlignment): Alignment = when (alignment) { 60 | VerticalAlignment.TOP -> Alignment.TOPLEFT 61 | VerticalAlignment.MIDDLE -> Alignment.MIDDLELEFT 62 | VerticalAlignment.BOTTOM -> Alignment.BOTTOMLEFT 63 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/alignment/UITextAlignment.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.alignment 2 | 3 | /** 4 | * The equivalent of Vertical and Horizontal text alignment in Figma. Certain properties cannot be used 5 | * on the certain axis. See the enum documentation for more information. 6 | * 7 | * @author sen 8 | * @since 1.0 9 | */ 10 | enum class UITextAlignment { 11 | /** 12 | * Aligns to the left on the x-axis 13 | */ 14 | LEFT, 15 | 16 | /** 17 | * Aligns to the center / middle on the x/y-axis 18 | */ 19 | CENTER, 20 | 21 | /** 22 | * Aligns to the right of the x-axis 23 | */ 24 | RIGHT, 25 | 26 | /** 27 | * Aligns the text to the top on the y-axis 28 | */ 29 | TOP, 30 | 31 | //CENTER// 32 | 33 | /** 34 | * Aligns to the text to the bottom on the y-axis 35 | */ 36 | BOTTOM, 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/component/UIComponent.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.component 2 | 3 | import net.prismclient.aether.core.util.property.Copyable 4 | import net.prismclient.aether.ui.composition.Composable 5 | import net.prismclient.aether.ui.modifier.UIModifier 6 | 7 | /** 8 | * [UIComponent] is the core of all components. With that said, all components must extend this. 9 | * 10 | * @author sen 11 | * @since 1.0 12 | */ 13 | abstract class UIComponent>(modifier: UIModifier<*>) : Composable(modifier), Copyable { 14 | override var parent: Composable? = null 15 | 16 | override fun compose() { 17 | modifier.preCompose(this) 18 | update() 19 | modifier.compose(this) 20 | } 21 | 22 | /** 23 | * Invoked after the components bounds have been updated. 24 | */ 25 | open fun update() {} 26 | 27 | override fun render() { 28 | modifier.preRender() 29 | renderComponent() 30 | modifier.render() 31 | } 32 | 33 | abstract fun renderComponent() 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/component/style/Style.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.component.style 2 | 3 | interface Style -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/component/type/Button.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.component.type 2 | 3 | import net.prismclient.aether.ui.component.UIComponent 4 | import net.prismclient.aether.ui.font.Font 5 | import net.prismclient.aether.ui.font.FontStyle 6 | import net.prismclient.aether.ui.font.UIFont 7 | import net.prismclient.aether.ui.modifier.UIModifier 8 | 9 | /** 10 | * @author sen 11 | * @since 1.0 12 | */ 13 | open class UIButton internal constructor( 14 | text: String, 15 | modifier: UIModifier<*>, 16 | override val font: UIFont 17 | ) : UIComponent(modifier), Font { 18 | @Suppress("LeakingThis") 19 | open val fontStyle: FontStyle = font.style 20 | 21 | constructor(text: String, modifier: UIModifier<*>, fontStyle: FontStyle) : this(text, modifier, UIFont(fontStyle)) 22 | 23 | open var text: String = text 24 | set(value) { 25 | field = value 26 | font.actualText = value 27 | // TODO: compose update 28 | } 29 | 30 | override fun composeSize() { 31 | super.composeSize() 32 | font.actualText = text 33 | // Calculate the font metrics, and update the size. Compose 34 | // Padding is invoked directly after updating the width. 35 | font.composeSize(this) 36 | modifier.composeAnchorPoint(this) 37 | modifier.composePadding(this) 38 | modifier.composeMargin(this) 39 | } 40 | 41 | 42 | override fun update() { 43 | // After the position is calculated compose the font 44 | font.compose(this) 45 | } 46 | 47 | override fun renderComponent() { 48 | font.render() 49 | } 50 | 51 | override fun copy(): UIButton = UIButton(text, modifier.copy(), font.copy()) 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/component/type/DefaultConstruct.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.component.type 2 | 3 | import net.prismclient.aether.core.util.shorthands.copy 4 | import net.prismclient.aether.ui.component.UIComponent 5 | import net.prismclient.aether.ui.dsl.ConstructionDSL 6 | import net.prismclient.aether.ui.modifier.UIModifier 7 | 8 | /** 9 | * [Construct] renders given instructions as a [UIComponent]. It allows for easy access 10 | * to render custom shapes without creating a custom class to handle it. It works by having 11 | * a [functional interface](https://kotlinlang.org/docs/fun-interfaces.html) which is executed 12 | * everytime it needs to be rendered. The functional interface, known as [action] is a [Runnable] 13 | * which simply is executed during [renderComponent]. This class removes the need for a lot of 14 | * simple boilerplate [UIComponent]s as drawing shapes becomes a lot easier. 15 | * 16 | * This is an abstract class, and the default implementation is [DefaultConstruct]. 17 | * 18 | * @author sen 19 | * @since 1.0 20 | * 21 | * @see [net.prismclient.aether.ui.component.ComponentsKt.construct] 22 | * @see ConstructionDSL 23 | * @see DefaultConstruct 24 | */ 25 | abstract class Construct>(modifier: UIModifier<*>) : UIComponent(modifier) { 26 | // TODO: Property Constructs: A Construction which stores a sequence of properties 27 | // TODO: Construct database to remove the need for unnecessary action duplicates 28 | 29 | /** 30 | * Runs this [Runnable] every time the [UIComponent.renderComponent] function would normally be invoked. 31 | */ 32 | open var action: Runnable? = null 33 | 34 | override fun renderComponent() { 35 | action?.run() 36 | } 37 | } 38 | 39 | /** 40 | * The default implementation of a [Construct] see [Construct] for more information 41 | * 42 | * @author sen 43 | * @since 1.0 44 | * @see Construct 45 | */ 46 | open class DefaultConstruct(modifier: UIModifier<*>) : Construct(modifier) { 47 | override fun copy(): DefaultConstruct = DefaultConstruct(modifier.copy()).also { 48 | it.action = action 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/component/type/ImageComponent.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.component.type 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | import net.prismclient.aether.core.color.UIColor 5 | import net.prismclient.aether.core.util.shorthands.* 6 | import net.prismclient.aether.ui.component.UIComponent 7 | import net.prismclient.aether.ui.dsl.Renderer 8 | import net.prismclient.aether.ui.image.ImageProvider 9 | import net.prismclient.aether.ui.image.UIImage 10 | import net.prismclient.aether.ui.modifier.UIModifier 11 | import java.io.FileNotFoundException 12 | 13 | /** 14 | * A type of [UIComponent] which renders an image. 15 | * 16 | * @author sen 17 | * @since 1.0 18 | */ 19 | class ImageComponent(image: UIImage, modifier: IconModifier) : UIComponent(modifier) { 20 | override val modifier: IconModifier = super.modifier as IconModifier 21 | 22 | var image: UIImage = image 23 | set(value) { 24 | field = value 25 | if (width > 0f && height > 0f) 26 | imageHandle = image.retrieveImage(width, height) 27 | } 28 | 29 | private var imageHandle: String = "" 30 | 31 | /** 32 | * Attempts to create an image from the [imageName]. A NPE will 33 | * be thrown if the image is not found within [ImageProvider]. 34 | */ 35 | constructor(imageName: String, modifier: IconModifier) : this( 36 | ImageProvider.obtainImage(imageName) 37 | ?: throw FileNotFoundException("Image $imageName was not found. Was it registered?"), 38 | modifier 39 | ) 40 | 41 | override fun update() { 42 | imageHandle = image.retrieveImage(width, height) 43 | } 44 | 45 | 46 | override fun renderComponent() = Renderer { 47 | color(modifier.imageColor) 48 | renderImage(imageHandle, x, y, width, height) 49 | } 50 | 51 | override fun copy(): ImageComponent = 52 | ImageComponent(image, modifier.copy()) 53 | } 54 | 55 | class IconModifier : UIModifier() { 56 | var imageColor: UIColor? = null 57 | 58 | override fun copy(): IconModifier = IconModifier().also { 59 | it.x = x.copy 60 | it.y = y.copy 61 | it.width = width.copy 62 | it.height = height.copy 63 | it.anchorPoint = anchorPoint.copy 64 | it.padding = padding.copy 65 | it.margin = margin.copy 66 | it.opacity = opacity.copy 67 | it.background = background.copy 68 | it.imageColor = imageColor.copy 69 | } 70 | 71 | override fun merge(other: IconModifier?) { 72 | if (other != null) { 73 | x = other.x or x 74 | y = other.y or y 75 | width = other.width or width 76 | height = other.height or height 77 | anchorPoint = other.anchorPoint or anchorPoint 78 | padding = other.padding or padding 79 | margin = other.margin or margin 80 | opacity = other.opacity or opacity 81 | background = other.background or background 82 | imageColor = other.imageColor or imageColor 83 | } 84 | } 85 | 86 | override fun animate( 87 | context: AnimationContext<*>, 88 | initial: IconModifier?, 89 | start: IconModifier?, 90 | end: IconModifier?, 91 | progress: Float, 92 | completed: Boolean 93 | ) { 94 | TODO("Not yet implemented") 95 | } 96 | } 97 | 98 | fun IconModifier.imageColor(color: UIColor) = apply { 99 | imageColor = color 100 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/component/type/UILabel.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.component.type 2 | 3 | import net.prismclient.aether.ui.component.UIComponent 4 | import net.prismclient.aether.ui.font.Font 5 | import net.prismclient.aether.ui.font.FontStyle 6 | import net.prismclient.aether.ui.font.UIFont 7 | import net.prismclient.aether.ui.modifier.UIModifier 8 | 9 | class UILabel internal constructor( 10 | text: String, 11 | modifier: UIModifier<*>, 12 | override val font: UIFont 13 | ) : UIComponent(modifier), Font { 14 | @Suppress("LeakingThis") 15 | open val fontStyle: FontStyle = font.style 16 | 17 | constructor(text: String, modifier: UIModifier<*>, fontStyle: FontStyle) : this(text, modifier, UIFont(fontStyle)) 18 | 19 | open var text: String = text 20 | set(value) { 21 | field = value 22 | font.actualText = value 23 | } 24 | 25 | override fun composeSize() { 26 | super.composeSize() 27 | font.actualText = text 28 | // Calculate the font metrics, and update the size. Compose 29 | // Padding is invoked directly after updating the width. 30 | font.composeSize(this) 31 | modifier.composeAnchorPoint(this) 32 | modifier.composePadding(this) 33 | modifier.composeMargin(this) 34 | } 35 | 36 | 37 | override fun update() { 38 | // After the position is calculated compose the font 39 | font.compose(this) 40 | } 41 | 42 | override fun renderComponent() { 43 | font.render() 44 | } 45 | 46 | override fun copy(): UILabel = UILabel(text, modifier.copy(), font.copy()) 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/composer/ComposeContext.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.composer 2 | 3 | class ComposeContext { 4 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/composer/Composer.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.composer 2 | 3 | // Compose -> Handles computation of the UI 4 | class Composer 5 | 6 | // Compositions.forEach(Compose) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/composer/ComposerData.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.composer 2 | 3 | // ComposerData -> a.k.a frames which store data on the active composable 4 | // as well as recompose information 5 | class ComposerData { 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/composer/ComposerHint.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.composer 2 | 3 | // Hints provided by the user, and composables to hint how the composer should act 4 | class ComposerHint { 5 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/composer/README: -------------------------------------------------------------------------------- 1 | Experimental future features for composing of the UI. This is likely to be added with a pre-processor / annotation 2 | processor to improve compose times and flexibility. -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/composition/util/UIBackground.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.composition.util 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | import net.prismclient.aether.core.color.UIColor 5 | import net.prismclient.aether.core.util.property.UIProperty 6 | import net.prismclient.aether.core.util.shorthands.* 7 | import net.prismclient.aether.ui.composition.Composable 8 | import net.prismclient.aether.ui.dsl.Renderer 9 | import net.prismclient.aether.ui.shape.ComposableShape 10 | import net.prismclient.aether.ui.unit.UIUnit 11 | import net.prismclient.aether.ui.unit.other.UIRadius 12 | 13 | /** 14 | * [UIBackground] is a property for a component's Modifier. It represents the background of a [Composable]. 15 | * 16 | * @author sen 17 | * @since 1.0 18 | */ 19 | open class UIBackground : ComposableShape(), UIProperty { 20 | override var width: UIUnit<*>? = 1.crel 21 | override var height: UIUnit<*>? = 1.crel 22 | 23 | // TODO: Border 24 | 25 | var backgroundColor: UIColor? = null 26 | var backgroundRadius: UIRadius? = null 27 | var backgroundBorder: UIBorder? = null 28 | 29 | override fun compose(composable: Composable?) { 30 | super.compose(composable) 31 | initialX = composable!!.relX 32 | initialY = composable.relY 33 | backgroundRadius?.compose(composable) 34 | backgroundBorder?.compose(this) 35 | } 36 | 37 | override fun render() { 38 | Renderer { 39 | color(backgroundColor) 40 | rect(initialX + x.dp, initialY + y.dp, width.dp, height.dp, backgroundRadius) 41 | } 42 | backgroundBorder?.render() 43 | } 44 | 45 | override fun UIUnit<*>?.compute(composable: Composable, yaxis: Boolean) { 46 | // Update based on the relative values instead of the normal 47 | this?.compute(composable, composable.relWidth, composable.relHeight, yaxis) 48 | } 49 | 50 | override fun copy(): UIBackground = UIBackground().also { 51 | it.x = x?.copy() 52 | it.y = y?.copy() 53 | it.width = width?.copy() 54 | it.height = height?.copy() 55 | it.backgroundColor = backgroundColor.copy 56 | it.backgroundRadius = backgroundRadius.copy 57 | it.backgroundBorder = backgroundBorder.copy 58 | } 59 | 60 | override fun merge(other: UIBackground?) { 61 | if (other != null) { 62 | x = other.x or x 63 | y = other.y or y 64 | width = other.width or width 65 | height = other.height or height 66 | backgroundColor = other.backgroundColor or backgroundColor 67 | backgroundRadius = other.backgroundRadius or backgroundRadius 68 | } 69 | } 70 | 71 | override fun animate( 72 | context: AnimationContext<*>, 73 | initial: UIBackground?, 74 | start: UIBackground?, 75 | end: UIBackground?, 76 | progress: Float, 77 | completed: Boolean 78 | ) { 79 | // TODO: Animate position and size 80 | ifNotNull(start?.backgroundColor, end?.backgroundColor) { 81 | backgroundColor = backgroundColor.default 82 | backgroundColor!!.animate( 83 | context, 84 | initial?.backgroundColor, 85 | start?.backgroundColor, 86 | end?.backgroundColor, 87 | progress, 88 | completed 89 | ) 90 | } 91 | } 92 | 93 | override fun toString(): String = 94 | "UIBackground(x=$x, y=$y, width=$width, height=$height, backgroundColor=$backgroundColor, backgroundRadius=$backgroundRadius)" 95 | } 96 | 97 | fun UIBackground.color(color: UIColor) = apply { 98 | backgroundColor = color 99 | } 100 | 101 | fun UIBackground.radius(radius: UIRadius) = apply { 102 | backgroundRadius = radius 103 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/composition/util/UIBorder.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.composition.util 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | import net.prismclient.aether.core.color.UIColor 5 | import net.prismclient.aether.core.util.property.Animatable 6 | import net.prismclient.aether.core.util.property.Copyable 7 | import net.prismclient.aether.core.util.property.Mergable 8 | import net.prismclient.aether.core.util.shorthands.copy 9 | import net.prismclient.aether.core.util.shorthands.dp 10 | import net.prismclient.aether.ui.composition.Composable 11 | import net.prismclient.aether.ui.dsl.Renderer 12 | import net.prismclient.aether.ui.renderer.UIStrokeDirection 13 | import net.prismclient.aether.ui.shape.Shape 14 | import net.prismclient.aether.ui.unit.UIUnit 15 | import net.prismclient.aether.ui.unit.other.UIRadius 16 | 17 | /** 18 | * [UIBorder] is intended to be paired with [UIBackground] to create borders for [Composable]s. 19 | * 20 | * The properties of Shape are unused 21 | * 22 | * @author sen 23 | * @since 1.0 24 | */ 25 | open class UIBorder : Shape(), Animatable, Copyable, Mergable { 26 | private lateinit var background: UIBackground 27 | /** 28 | * The width of the border. Represents the x-axis when computing. 29 | */ 30 | var borderWidth: UIUnit<*>? = null 31 | var borderColor: UIColor? = null 32 | var borderRadius: UIRadius? = null 33 | var borderDirection: UIStrokeDirection? = null 34 | 35 | var initialX: Float = 0f 36 | var initialY: Float = 0f 37 | var initialWidth: Float = 0f 38 | var initialHeight: Float = 0f 39 | 40 | open fun compose(background: UIBackground) { 41 | this.background = background 42 | borderWidth?.compute(null, background.width.dp, background.height.dp, false) 43 | initialX = background.x.dp + background.initialX 44 | initialY = background.y.dp + background.initialY 45 | initialWidth = background.width.dp 46 | initialHeight = background.height.dp 47 | } 48 | 49 | override fun render() { 50 | Renderer { 51 | stroke(borderWidth.dp, borderColor?.rgba ?: 0, borderDirection ?: UIStrokeDirection.CENTER) { 52 | rect(initialX, initialY, initialWidth, initialHeight, background.backgroundRadius) 53 | } 54 | } 55 | } 56 | 57 | override fun animate( 58 | context: AnimationContext<*>, 59 | initial: UIBorder?, 60 | start: UIBorder?, 61 | end: UIBorder?, 62 | progress: Float, 63 | completed: Boolean 64 | ) { 65 | TODO("Not yet implemented") 66 | } 67 | 68 | override fun copy(): UIBorder = UIBorder().also { 69 | it.borderWidth = borderWidth.copy 70 | it.borderColor = borderColor.copy 71 | it.borderDirection = borderDirection ?: it.borderDirection 72 | } 73 | 74 | override fun merge(other: UIBorder?) { 75 | TODO("Not yet implemented") 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/controller/UIControl.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.controller 2 | 3 | import net.prismclient.aether.core.util.other.ComposableGroup 4 | import net.prismclient.aether.core.util.shorthands.Block 5 | import net.prismclient.aether.ui.composition.Composable 6 | import java.util.function.Consumer 7 | import kotlin.reflect.KClass 8 | 9 | class UIControl( 10 | var selectedComposable: T, 11 | private val type: KClass 12 | ) : ComposableGroup { 13 | override val children: ArrayList = arrayListOf() 14 | 15 | private var selectionAction: Consumer? = null 16 | private var deselectionAction: Consumer? = null 17 | 18 | init { 19 | children.add(selectedComposable) 20 | } 21 | 22 | fun select(composable: T) { 23 | selectedComposable = composable 24 | selectionAction?.accept(composable) 25 | children.filter { type.isInstance(it) }.forEach { 26 | if (it != composable) deselectionAction?.accept(it as T) 27 | } 28 | } 29 | 30 | fun onSelect(action: Consumer) { 31 | selectionAction = action 32 | } 33 | 34 | fun onDeselect(action: Consumer) { 35 | deselectionAction = action 36 | } 37 | } 38 | 39 | inline fun Control(selectedComposable: T, block: Block>) = 40 | UIControl(selectedComposable, T::class).also(block) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/dsl/ComposeDSL.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.dsl 2 | 3 | import net.prismclient.aether.core.Aether 4 | import net.prismclient.aether.core.util.other.ComposableGroup 5 | import net.prismclient.aether.core.util.shorthands.Block 6 | import net.prismclient.aether.ui.composition.Composable 7 | import net.prismclient.aether.ui.composition.Composition 8 | import net.prismclient.aether.ui.composition.CompositionModifier 9 | import net.prismclient.aether.ui.composition.DefaultCompositionModifier 10 | import net.prismclient.aether.ui.dsl.ComposeDSL.begin 11 | import net.prismclient.aether.ui.dsl.ComposeDSL.end 12 | import java.lang.IllegalStateException 13 | 14 | /** 15 | * Executes the given [block] within the [ComposeDSL] scope. 16 | */ 17 | inline fun Compose(block: Block) { 18 | begin() 19 | ComposeDSL.block() 20 | end() 21 | } 22 | 23 | /** 24 | * 25 | * @author sen 26 | * @since 1.0 27 | * 28 | * @see composable 29 | */ 30 | object ComposeDSL { 31 | var activeComposition: Composition? = null 32 | var activeComposable: Composable? = null 33 | 34 | 35 | @JvmStatic 36 | fun begin() {} 37 | 38 | 39 | @JvmStatic 40 | fun end() { 41 | activeComposition = null 42 | activeComposable = null 43 | } 44 | 45 | @JvmStatic 46 | fun check() { 47 | if (Aether.instance.activeScreen == null) 48 | throw IllegalStateException("There is no active screen?") 49 | if (Aether.instance.defaultComposition == null) 50 | throw IllegalStateException("There is no default composition. How did you even get here?") 51 | } 52 | 53 | /** 54 | * 55 | */ 56 | inline fun composable(composable: T, block: Block): T = composable.apply { 57 | check() 58 | 59 | val previousComposition = activeComposition 60 | val previousComposable = activeComposable 61 | 62 | composition = if (this is Composition) { 63 | activeComposition = this 64 | previousComposition ?: this 65 | } else { 66 | activeComposition ?: Aether.instance.defaultComposition!! 67 | } 68 | 69 | parent = activeComposable 70 | 71 | if (parent is ComposableGroup) (parent as ComposableGroup).children.add(this) 72 | 73 | activeComposable = this 74 | 75 | block() 76 | 77 | activeComposable = previousComposable 78 | activeComposition = previousComposition 79 | 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/dsl/ConstructionDSL.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.dsl 2 | 3 | import net.prismclient.aether.core.util.shorthands.Block 4 | import net.prismclient.aether.ui.component.type.DefaultConstruct 5 | 6 | /** 7 | * Introduces a Kotlin DSL for the [DefaultConstruct] component which allows creating them much easier. 8 | * 9 | * @author sen 10 | * @since 1.0 11 | */ 12 | object ConstructionDSL { 13 | var activeConstructor: DefaultConstruct? = null 14 | 15 | /** 16 | * Sets the [DefaultConstruct.action] to the [block] and executes it every frame. The [block] provides 17 | * access to all of [UIRendererDSL] which allows you to use the functions it provides. 18 | */ 19 | fun Render(block: Block) { 20 | check() 21 | activeConstructor!!.action = Runnable { 22 | UIRendererDSL.block() 23 | } 24 | } 25 | 26 | /** 27 | * Throws a runtime exception if the active constructor is null. 28 | */ 29 | fun check() { 30 | if (activeConstructor == null) 31 | throw RuntimeException("The active constructor of ConstructionDSL is null. A construct must be defined with Components.kt.construct") 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/effect/UIEffect.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.effect 2 | 3 | class UIEffect { 4 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/font/FontFaceType.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.font 2 | 3 | /** 4 | * Represents the type of font within a font family. [FontStyle.fontFamily] must not be null for this to be functional. 5 | * 6 | * @see [FontStyle.fontType] 7 | * @see [UIFontFamily] 8 | * 9 | * @author sen 10 | * @since 1.0 11 | */ 12 | enum class FontFaceType { 13 | Thin, 14 | ExtraLight, 15 | Light, 16 | Regular, 17 | Medium, 18 | SemiBold, 19 | Bold, 20 | ExtraBold, 21 | Black 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/font/FontType.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.font 2 | 3 | /** 4 | * [FontType] also known as the resizing property for text in Figma is an important part of Fonts within Aether. It 5 | * determines how to format and render the text given to the font. See the enums of this for information on how this 6 | * works. Depending on the enum, the composable will become dynamic as it might resize the composable. 7 | * 8 | * @author sen 9 | * @since 1.0 10 | */ 11 | enum class FontType { 12 | /** 13 | * The given text is rendered as one line where the width grows based on the size of the text bounds. 14 | */ 15 | AutoWidth, 16 | 17 | /** 18 | * Like [AutoWidth], however it supports multi-line text and will wrap anything that 19 | * exceeds [UIFont.width]. The height will grow to fit the bounds of the multi-line text. 20 | */ 21 | AutoHeight, 22 | 23 | /** 24 | * [FixedSize] is not dynamic and does not change based on the content. Instead, any text which exceeds the 25 | * horizontal bounds (width), will be wrapped. 26 | */ 27 | FixedSize, 28 | 29 | /** 30 | * Not yet implemented 31 | */ 32 | TruncateText 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/font/UIFontFamily.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.font 2 | 3 | import net.prismclient.aether.core.util.extensions.safeByteBuffer 4 | import net.prismclient.aether.ui.resource.Resource 5 | import java.nio.ByteBuffer 6 | 7 | /** 8 | * Represents a family of fonts. 9 | * 10 | * @author sen 11 | * @since 1.0 12 | */ 13 | class UIFontFamily( 14 | val familyName: String, 15 | val thin: Font?, 16 | val extraLight: Font?, 17 | val light: Font?, 18 | val regular: Font?, 19 | val medium: Font?, 20 | val semiBold: Font?, 21 | val bold: Font?, 22 | val extraBold: Font?, 23 | val black: Font? 24 | ) { 25 | // TODO: Improve this 26 | // TODO: Dont allocate if it alreasdy exists within the UIResourceProvider 27 | 28 | constructor( 29 | familyName: String, 30 | thin: ByteBuffer?, 31 | extraLight: ByteBuffer?, 32 | light: ByteBuffer?, 33 | regular: ByteBuffer?, 34 | medium: ByteBuffer?, 35 | semiBold: ByteBuffer?, 36 | bold: ByteBuffer?, 37 | extraBold: ByteBuffer?, 38 | black: ByteBuffer? 39 | ) : this( 40 | familyName, 41 | if (thin != null) Font("$familyName-thin", thin) else null, 42 | if (extraLight != null) Font("$familyName-extralight", extraLight) else null, 43 | if (light != null) Font("$familyName-light", light) else null, 44 | if (regular != null) Font("$familyName-regular", regular) else null, 45 | if (medium != null) Font("$familyName-medium", medium) else null, 46 | if (semiBold != null) Font("$familyName-semibold", semiBold) else null, 47 | if (bold != null) Font("$familyName-bold", bold) else null, 48 | if (extraBold != null) Font("$familyName-extrabold", extraBold) else null, 49 | if (black != null) Font("$familyName-black", black) else null 50 | ) 51 | 52 | constructor(familyName: String, location: String) : this( 53 | familyName, 54 | "$location/$familyName-thin.ttf".safeByteBuffer(), 55 | "$location/$familyName-extralight.ttf".safeByteBuffer(), 56 | "$location/$familyName-light.ttf".safeByteBuffer(), 57 | "$location/$familyName-regular.ttf".safeByteBuffer(), 58 | "$location/$familyName-medium.ttf".safeByteBuffer(), 59 | "$location/$familyName-semibold.ttf".safeByteBuffer(), 60 | "$location/$familyName-bold.ttf".safeByteBuffer(), 61 | "$location/$familyName-extrabold.ttf".safeByteBuffer(), 62 | "$location/$familyName-black.ttf".safeByteBuffer() 63 | ) 64 | 65 | /** 66 | * Represents a single font with its memory, and it's [name]. 67 | * 68 | * @author sen 69 | * @since 1.0 70 | */ 71 | class Font(val name: String, val buffer: ByteBuffer) : Resource 72 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/image/ImageProvider.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.image 2 | 3 | import net.prismclient.aether.core.Aether 4 | import net.prismclient.aether.ui.resource.ResourceProvider 5 | import java.nio.ByteBuffer 6 | 7 | /** 8 | * [ImageProvider] works in unison with [ResourceProvider] to register, load, resize and rasterize 9 | * images. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | object ImageProvider { 15 | @JvmStatic 16 | val images: HashMap = hashMapOf() 17 | 18 | /** 19 | * Returns a [UIImage] based on the [imageName] or null, if that image with [imageName] is not found. 20 | */ 21 | @JvmStatic 22 | fun obtainImage(imageName: String): UIImage? = images[imageName] 23 | 24 | /** 25 | * Deletes the corresponding [UIImage] given [imageName] from this, so it is eligible for garbage collection. 26 | */ 27 | @JvmStatic 28 | fun deleteImage(imageName: String) { 29 | images.remove(imageName) 30 | } 31 | 32 | /** 33 | * Creates a new [Image], a common subtype of [UIImage] which is intended to support general 34 | * image file types, such as JPEG and PNG. [Image.create] is automatically invoked and registered 35 | * to this and [ResourceProvider]. 36 | * 37 | * @see obtainImage 38 | * @see createSVG 39 | */ 40 | @JvmStatic 41 | fun createImage(imageName: String, flags: Int, buffer: ByteBuffer): Image { 42 | val image = Aether.renderer.createImage(buffer) 43 | return Image(imageName, image.width, image.height, flags, image.buffer).also(UIImage::create) 44 | .also { images[imageName] = it } 45 | } 46 | 47 | /** 48 | * Creates a new [SVG], a subtype of [UIImage] which is intended to support SVGs. [SVG.create] is 49 | * automatically invoked and registered to this and [ResourceProvider]. 50 | * 51 | * @see obtainImage 52 | * @see createImage 53 | */ 54 | @JvmStatic 55 | @JvmOverloads 56 | fun createSVG(svgName: String, buffer: ByteBuffer, scale: Float = 1f): SVG { 57 | val svg = Aether.renderer.rasterizeSVG(buffer, scale) 58 | return SVG(svgName, svg.width, svg.height, scale, buffer, svg.buffer).also(UIImage::create) 59 | .also { images[svgName] = it } 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/image/ImageUtil.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.image 2 | 3 | import java.nio.ByteBuffer 4 | 5 | fun resizeTexture( 6 | originalWidth: Float, 7 | originalHeight: Float, 8 | width: Float, 9 | height: Float, 10 | data: ByteBuffer 11 | ): ByteBuffer { 12 | TODO("Not yet implemented") 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/layout/AutoLayout.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.layout 2 | 3 | import net.prismclient.aether.core.metrics.Size 4 | import net.prismclient.aether.core.util.shorthands.dp 5 | import net.prismclient.aether.ui.alignment.Alignment.* 6 | import net.prismclient.aether.ui.composition.Composable 7 | import net.prismclient.aether.ui.layout.util.LayoutDirection 8 | import net.prismclient.aether.ui.unit.UIUnit 9 | import kotlin.math.max 10 | 11 | /** 12 | * [AutoLayout] mimics the Figma Auto Layout feature. In layman's terms, it is a list layout, with more 13 | * control and combustibility. 14 | * 15 | * @author sen 16 | * @since 1.0 17 | */ 18 | class AutoLayout( 19 | name: String, 20 | modifier: LayoutModifier<*>, 21 | layoutStyle: BoxLayoutStyle 22 | ) : BoxLayout(name, modifier, layoutStyle) { 23 | init { 24 | overrideChildren = true 25 | } 26 | 27 | override fun updateLayout(): Size { 28 | val potential = potentialSize!! 29 | val top = layoutStyle.layoutPadding?.top.dp 30 | val right = layoutStyle.layoutPadding?.right.dp 31 | val bottom = layoutStyle.layoutPadding?.bottom.dp 32 | val left = layoutStyle.layoutPadding?.left.dp 33 | 34 | var x = this.x + left 35 | var y = this.y + top 36 | var w = 0f 37 | var h = 0f 38 | 39 | // Offset the axis of which the layout is directed 40 | // based on the alignment and the leftover space. 41 | if (layoutStyle.layoutDirection == LayoutDirection.HORIZONTAL) { 42 | x += when (layoutStyle.layoutAlignment) { 43 | TOPCENTER, CENTER, BOTTOMCENTER -> (width - potential.width - right + left) / 2f 44 | TOPRIGHT, MIDDLERIGHT, BOTTOMRIGHT -> width - potential.width - right + left 45 | else -> -right 46 | }.coerceAtLeast(0f) 47 | } else { 48 | y += when (layoutStyle.layoutAlignment) { 49 | MIDDLELEFT, CENTER, MIDDLERIGHT -> (height - potential.height - bottom + top) / 2f 50 | BOTTOMLEFT, BOTTOMCENTER, BOTTOMRIGHT -> height - potential.height - bottom + top 51 | else -> -bottom 52 | }.coerceAtLeast(0f) 53 | } 54 | 55 | for (child in children) { 56 | if (layoutStyle.layoutDirection == LayoutDirection.HORIZONTAL) { 57 | child.x = x + child.modifier.padding?.left.dp 58 | child.y = y + when (layoutStyle.layoutAlignment) { 59 | MIDDLELEFT, CENTER, MIDDLERIGHT -> (height - child.relHeight - top - bottom) / 2f 60 | BOTTOMLEFT, BOTTOMCENTER, BOTTOMRIGHT -> (height - child.relHeight - top - bottom) 61 | else -> 0f 62 | } + child.modifier.padding?.top.dp 63 | x += child.relWidth + layoutStyle.itemSpacing.dp 64 | } else { 65 | child.x = x + when (layoutStyle.layoutAlignment) { 66 | TOPCENTER, CENTER, BOTTOMCENTER -> (width - child.relWidth - left - right) / 2f 67 | TOPRIGHT, MIDDLERIGHT, BOTTOMRIGHT -> (width - child.relWidth - left - right) 68 | else -> 0f 69 | } + child.modifier.padding?.left.dp 70 | child.y = y + child.modifier.padding?.top.dp 71 | y += child.relHeight + layoutStyle.itemSpacing.dp 72 | } 73 | child.compose() 74 | w = max(child.relX + child.relWidth - this.x, w) 75 | h = max(child.relY + child.relHeight - this.y, h) 76 | } 77 | 78 | return Size(w, h) 79 | } 80 | } 81 | 82 | /** 83 | * [SpaceBetween] is an [AutoLayout] specific unit used exclusively for the [BoxLayoutStyle.itemSpacing] property. When 84 | * set, it informs the layout to evenly space the items within it based on the leftover space. 85 | * 86 | * @author sen 87 | * @since 1.0 88 | */ 89 | open class SpaceBetween : UIUnit(0f) { 90 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float { 91 | if (composable !is AutoLayout) 92 | throw RuntimeException("The SpaceBetween unit cannot be applied to a ${composable?.javaClass?.simpleName ?: "null"}") 93 | return 0f 94 | } 95 | 96 | override fun copy(): SpaceBetween = SpaceBetween() 97 | 98 | override fun toString(): String = "SpaceBetween($cachedValue)" 99 | } 100 | 101 | /** 102 | * [SpaceEvenly] is a unit used to indicate layouts to space the elements within it evenly. A RuntimeException 103 | * is thrown if this unit is used anywhere but inside a [UILayout]. Because of the lack of information, the layout 104 | * has to calculate the value instead of the unit. 105 | * 106 | * @author sen 107 | * @since 1.0 108 | */ 109 | class SpaceEvenly : UIUnit(0f) { 110 | override fun compute(composable: Composable?, width: Float, height: Float, yaxis: Boolean) { 111 | if (composable !is UILayout) throw RuntimeException("Expected a UILayout for a SpaceEvenly unit. Got: $composable") 112 | super.compute(composable, width, height, yaxis) 113 | } 114 | 115 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float = 0f 116 | 117 | override fun copy(): SpaceEvenly = SpaceEvenly() 118 | 119 | override fun toString(): String = "SpaceEvenly()" 120 | } 121 | 122 | /** 123 | * Sets the width and height of this to [HugLayout]. This can only be applied to Auto Layouts. 124 | */ 125 | fun > T.hug(): T { 126 | hugWidth() 127 | hugHeight() 128 | return this 129 | } 130 | 131 | /** 132 | * Sets the width of this to a [HugLayout]. This can only be applied to Auto Layouts. 133 | */ 134 | fun > T.hugWidth(): T { 135 | width = HugLayout() 136 | return this 137 | } 138 | 139 | /** 140 | * Sets the height of this to a [HugLayout]. This can only be applied to Auto Layouts. 141 | */ 142 | fun > T.hugHeight(): T { 143 | height = HugLayout() 144 | return this 145 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/layout/CustomLayout.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.layout 2 | 3 | import net.prismclient.aether.core.metrics.Size 4 | import net.prismclient.aether.ui.composition.Composable 5 | 6 | /** 7 | * @author sen 8 | * @since 1.0 9 | */ 10 | class CustomLayout @JvmOverloads constructor( 11 | name: String = "CustomLayout", 12 | modifier: LayoutModifier<*> = LayoutModifier(), 13 | layoutStyle: BoxLayoutStyle = BoxLayoutStyle(), 14 | 15 | /** 16 | * Invoked when the potential size of the layout is calculated. 17 | */ 18 | val sizeCalculation: CustomLayout.() -> Size? = { Size(0f, 0f) }, 19 | 20 | /** 21 | * Invoked after the potential size is calculated. This is for any units 22 | * that need to be calculated prior to calculating the layout. 23 | */ 24 | val unitCalculation: CustomLayout.(layoutSize: Size?) -> Unit = {}, 25 | 26 | /** 27 | * Invoked when the layout needs to be calculated. With the given data, the children 28 | * should be laid how they need to be. Expects the final layout size to be returned. 29 | */ 30 | val layout: CustomLayout.(children: ArrayList, layoutSize: Size?) -> Size, 31 | ) : BoxLayout(name, modifier, layoutStyle) { 32 | 33 | override fun updateUnits() = unitCalculation.invoke(this, potentialSize) 34 | 35 | override fun updateLayout(): Size = layout.invoke(this, children, potentialSize) 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/layout/UIListLayout.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.layout 2 | 3 | import net.prismclient.aether.core.metrics.Size 4 | import net.prismclient.aether.core.util.shorthands.dp 5 | import net.prismclient.aether.ui.layout.util.LayoutDirection 6 | import net.prismclient.aether.ui.layout.util.LayoutOrder 7 | import net.prismclient.aether.ui.unit.UIUnit 8 | 9 | open class UIListLayout constructor( 10 | layoutName: String, 11 | var direction: LayoutDirection, 12 | var order: LayoutOrder, 13 | var itemSpacing: UIUnit<*>?, 14 | modifier: LayoutModifier<*> 15 | ) : UILayout(layoutName, modifier, true) { 16 | override fun updateUnits() { 17 | itemSpacing.compute(direction == LayoutDirection.VERTICAL) 18 | } 19 | 20 | override fun updateLayout(): Size { 21 | val spacing = itemSpacing.dp 22 | 23 | var x = x 24 | var y = y 25 | 26 | var w = 0f 27 | var h = 0f 28 | 29 | if (order == LayoutOrder.FIRST) { 30 | for (i in 0 until children.size) { 31 | val child = children[i] 32 | 33 | // Update the position as it would be normally, 34 | // and only override the axis set. Update the size as well. 35 | child.overridden = false 36 | child.compose() 37 | child.overridden = true 38 | 39 | if (direction == LayoutDirection.HORIZONTAL) { 40 | child.x = x + child.modifier.padding?.left.dp 41 | x += child.relWidth + spacing 42 | } else if (direction == LayoutDirection.VERTICAL) { 43 | child.y = y + child.modifier.padding?.top.dp 44 | y += child.relHeight + spacing 45 | } 46 | child.compose() 47 | 48 | w = w.coerceAtLeast(child.x + child.relWidth - this.x) 49 | h = h.coerceAtLeast(child.y + child.relHeight - this.y) 50 | } 51 | } else { 52 | for (i in children.size - 1 downTo 0) { 53 | val child = children[i] 54 | 55 | // Update the position as it would be normally, 56 | // and only override the axis set. Update the size as well. 57 | child.overridden = false 58 | child.compose() 59 | child.overridden = true 60 | 61 | if (direction == LayoutDirection.HORIZONTAL) { 62 | child.x = x + child.modifier.padding?.left.dp 63 | x += child.relWidth + spacing 64 | } else if (direction == LayoutDirection.VERTICAL) { 65 | child.y = y + child.modifier.padding?.top.dp 66 | y += child.relHeight + spacing 67 | } 68 | child.compose() 69 | 70 | w = w.coerceAtLeast(child.x + child.relWidth - this.x) 71 | h = h.coerceAtLeast(child.y + child.relHeight - this.y) 72 | } 73 | } 74 | 75 | // Remove the extra space calculated at the last child 76 | if (direction == LayoutDirection.HORIZONTAL) { 77 | w -= spacing 78 | } else { 79 | h -= spacing 80 | } 81 | 82 | return Size(w, h) 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/layout/util/LayoutDirection.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.layout.util 2 | 3 | /** 4 | * Indicates the axis for which a layout should compose its children. 5 | * 6 | * @author sen 7 | * @since 1.0 8 | */ 9 | enum class LayoutDirection { 10 | /** 11 | * Lays the children on the x-axis. 12 | */ 13 | HORIZONTAL, 14 | 15 | /** 16 | * Lays the children on the y-axis. 17 | */ 18 | VERTICAL 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/layout/util/LayoutOrder.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.layout.util 2 | 3 | /** 4 | * Indicates the order to iterate through the children list of a layout. 5 | * 6 | * @author sen 7 | * @since 1.0 8 | */ 9 | enum class LayoutOrder { 10 | /** 11 | * The first child is laid first, and the last is laid last. 12 | */ 13 | FIRST, 14 | 15 | /** 16 | * The last child is laid first, and the first child is laid last. 17 | */ 18 | LAST 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/layout/util/Overflow.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.layout.util 2 | 3 | import net.prismclient.aether.ui.layout.LayoutModifier 4 | import net.prismclient.aether.ui.layout.UILayout 5 | import net.prismclient.aether.ui.layout.scroll.Scrollbar 6 | 7 | /** 8 | * Indicates how a scrollbar acts within a layout. The behavior nearly is identical to Web/CSS overflow. See the 9 | * respected enums for more information. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | * @see UILayout 14 | * @see Scrollbar 15 | * @see LayoutModifier 16 | */ 17 | enum class Overflow { 18 | /** 19 | * Does nothing; the scrollbar is not introduced, and the content is not clipped 20 | */ 21 | VISIBLE, 22 | 23 | /** 24 | * Clips any content flowing outside the bounds of the layout. 25 | * 26 | * Note: The default renderer, NanoVG does not support multiple scissor operations, so any scissor calls prior 27 | * to this will be ignored. (Such as the scissor calls when Compositions have optimizations disabled). 28 | */ 29 | HIDDEN, 30 | 31 | /** 32 | * Introduces a scrollbar. The [Overflow.HIDDEN] state applies. 33 | */ 34 | SCROLLBAR, 35 | 36 | /** 37 | * Introduces a scrollbar **ONLY** if the content overflows (exceeds) the layout. The [Overflow.HIDDEN] state applies. 38 | */ 39 | AUTO 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/registry/UIRegistry.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.registry 2 | 3 | import net.prismclient.aether.core.util.shorthands.copy 4 | import net.prismclient.aether.ui.modifier.UIModifier 5 | import net.prismclient.aether.ui.registry.UIRegistry.disableApplyingModifiers 6 | import net.prismclient.aether.ui.registry.UIRegistry.enableApplyingModifiers 7 | import net.prismclient.aether.ui.registry.UIRegistry.registerModifier 8 | import net.prismclient.aether.ui.registry.UIRegistry.registerStyle 9 | import net.prismclient.aether.ui.style.Style 10 | 11 | /** 12 | * [UIRegistry] stores global Modifiers and Styles which are automatically applied at creation. To register 13 | * a Modifier or Style pass a style, or modifier to [registerModifier], or [registerStyle] respectively. 14 | * 15 | * Sometimes, you might not want to automatically apply a Modifier or Style, so using [enableApplyingModifiers] 16 | * and [disableApplyingModifiers] will disable it for it's respected type. 17 | * 18 | * @author sen 19 | * @since 1.0 20 | */ 21 | object UIRegistry { 22 | private val modifiers: HashMap> = hashMapOf() 23 | private val styles: HashMap> = hashMapOf() 24 | var applyModifiers: Boolean = true 25 | private set 26 | var applyStyles: Boolean = true 27 | private set 28 | 29 | /** 30 | * Registers a modifier with the given [name] to the modifier list. 31 | * 32 | * @see register 33 | */ 34 | fun registerModifier(name: String, modifier: UIModifier<*>) { 35 | modifiers[name] = modifier 36 | } 37 | 38 | /** 39 | * Registers a style with the given [name] to the style list. 40 | * 41 | * @see register 42 | */ 43 | fun registerStyle(name: String, modifier: Style<*, *>) { 44 | styles[name] = modifier 45 | } 46 | 47 | /** 48 | * Returns a [UIModifier], or null from the given [name]. 49 | * 50 | * @see obtainStyle 51 | * @see enableApplyingModifiers 52 | * @see disableApplyingModifiers 53 | */ 54 | fun obtainModifier(name: String): UIModifier<*>? = if (applyModifiers) modifiers[name] else null 55 | 56 | /** 57 | * Returns a [Style], or null from the given [name]. 58 | * 59 | * @see obtainModifier 60 | * @see enableApplyingStyles 61 | * @see disableApplyingStyles 62 | */ 63 | fun obtainStyle(name: String): Style<*, *>? = if (applyStyles) styles[name] else null 64 | 65 | fun enableApplyingModifiers() { 66 | applyModifiers = true 67 | } 68 | 69 | fun enableApplyingStyles() { 70 | applyStyles = true 71 | } 72 | 73 | fun disableApplyingModifiers() { 74 | applyModifiers = false 75 | } 76 | 77 | fun disableApplyingStyles() { 78 | applyStyles = false 79 | } 80 | } 81 | 82 | /** 83 | * Registers the given modifier with the given [name] to [UIRegistry]. When set to the active modifier, 84 | * all properties of it are applied to the active modifier. 85 | */ 86 | fun register(modifier: UIModifier<*>, name: String = modifier::class.simpleName!!) { 87 | UIRegistry.registerModifier(name, modifier) 88 | } 89 | 90 | /** 91 | * Creates a copy of the [style] and registers it with the given [name] to [UIRegistry]. When set 92 | * to the active modifier, all properties of it are applied to the active modifier. 93 | */ 94 | fun register(style: Style<*, *>, name: String = style::class.simpleName!!) { 95 | UIRegistry.registerStyle(name, style.copy()) 96 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/renderer/UIFramebuffer.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.renderer 2 | 3 | /** 4 | * @author sen 5 | * @since 1.0 6 | */ 7 | class UIFramebuffer( 8 | val imagePattern: Int, 9 | val width: Float, 10 | val height: Float, 11 | val scaledWidth: Float, 12 | val scaledHeight: Float, 13 | val contentScale: Float 14 | ) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/renderer/UIStrokeDirection.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.renderer 2 | 3 | /** 4 | * Instructs the renderer how to render the stroke. When center, the stroke is entered to the 5 | * outline of the given shape. Outside, is the outside of the shape, and inside is the inside of the shape. 6 | * 7 | * @author sen 8 | * @since 1.0 9 | */ 10 | enum class UIStrokeDirection { 11 | CENTER, OUTSIDE, INSIDE 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/resource/Resource.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.resource 2 | 3 | /** 4 | * [Resource] is used to indicate a class that is a resource. 5 | * 6 | * @author sen 7 | * @since 1.0 8 | */ 9 | interface Resource -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/resource/ResourceProvider.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.resource 2 | 3 | import net.prismclient.aether.core.Aether 4 | import net.prismclient.aether.ui.font.UIFontFamily 5 | import net.prismclient.aether.ui.image.ImageProvider 6 | import java.nio.ByteBuffer 7 | 8 | /** 9 | * [ResourceProvider] manages fonts and images for Aether. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | object ResourceProvider { 15 | val images: HashMap = hashMapOf() 16 | 17 | val fonts: HashMap = hashMapOf() 18 | val fontFamilies: HashMap = hashMapOf() 19 | 20 | fun registerFontFamily(family: UIFontFamily) { 21 | fontFamilies[family.familyName] = family 22 | } 23 | 24 | fun registerFont(fontName: String, fontData: ByteBuffer) = UIFontFamily.Font(fontName, fontData).also { 25 | Aether.renderer.createFont(fontName, fontData) 26 | registerFont(it) 27 | } 28 | 29 | fun deleteFont(fontName: String) { 30 | fonts.remove(fontName) 31 | Aether.renderer.deleteFont(fontName) 32 | } 33 | 34 | fun registerFont(font: UIFontFamily.Font) { 35 | fonts[font.name] = font 36 | } 37 | 38 | /** 39 | * Registers the image handle, [imageName] to the [images] HashMap with [image] as the value. 40 | * 41 | * For creating images use [ImageProvider.createImage] or [ImageProvider.createSVG] for creating images. 42 | */ 43 | fun registerImage(imageName: String, image: ByteBuffer) { 44 | images[imageName] = image 45 | } 46 | 47 | /** 48 | * Removes the image data reference from this. 49 | */ 50 | fun deleteImage(imageName: String) { 51 | images.remove(imageName) 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/screen/UIScreen.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.screen 2 | 3 | /** 4 | * @author sen 5 | * @since 1.0 6 | */ 7 | @JvmDefaultWithoutCompatibility 8 | interface UIScreen { 9 | 10 | /** 11 | * Invoked when Aether has prepared the screen. 12 | */ 13 | fun compose() 14 | } 15 | 16 | interface CloseableScreen : UIScreen { 17 | 18 | /** 19 | * Invoked when Aether has closed the screen. 20 | */ 21 | fun closeScreen() 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/shape/ComposableShape.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.shape 2 | 3 | import net.prismclient.aether.core.util.property.Updatable 4 | import net.prismclient.aether.ui.composition.Composable 5 | import net.prismclient.aether.ui.unit.UIUnit 6 | 7 | /** 8 | * [ComposableShape] is a shape which constrains itself based on a composable, or a specific subclass of 9 | * one known as [T]. When composed, the provided composable is expected not to be null. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | abstract class ComposableShape : Shape(), Updatable { 15 | /** 16 | * The x position of the composable. 17 | */ 18 | var initialX: Float = 0f 19 | 20 | /** 21 | * The y position of the composable. 22 | */ 23 | var initialY: Float = 0f 24 | 25 | override fun compose(composable: T?) { 26 | composable!! 27 | x?.compute(composable, false) 28 | y?.compute(composable, true) 29 | width?.compute(composable, false) 30 | height?.compute(composable, true) 31 | initialX = composable.x 32 | initialY = composable.y 33 | } 34 | 35 | protected open fun UIUnit<*>?.compute(composable: Composable, yaxis: Boolean) { 36 | this?.compute(composable, composable.width, composable.height, yaxis) 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/shape/Shape.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.shape 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | import net.prismclient.aether.ui.unit.UIUnit 5 | 6 | /** 7 | * [Shape] is the superclass for all shapes, which are everything not a [Composable] that are rendered on screen. 8 | * 9 | * @author sen 10 | * @since 1.0 11 | */ 12 | abstract class Shape { 13 | open var x: UIUnit<*>? = null 14 | open var y: UIUnit<*>? = null 15 | open var width: UIUnit<*>? = null 16 | open var height: UIUnit<*>? = null 17 | 18 | abstract fun render() 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/style/Style.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.style 2 | 3 | import net.prismclient.aether.core.util.property.UIUniqueProperty 4 | import net.prismclient.aether.ui.composition.Composable 5 | import net.prismclient.aether.ui.registry.UIRegistry 6 | 7 | /** 8 | * Some components require more information than the default properties provided by Modifier. Modifier 9 | * only provides more positioning/plotting based information, so [Style] takes care of the rest. Styles 10 | * target a specific property group, such as a font. Take the label, where the font size, and font color 11 | * need to be changed. 12 | * 13 | * This is the superclass for all Styles. It accepts two generic properties, [S], which is this, and [T] 14 | * which, the expected [Composable] type. 15 | * 16 | * @author sen 17 | * @since 1.0 18 | */ 19 | abstract class Style, T : Composable> : UIUniqueProperty { 20 | init { 21 | applyStyle(this::class.simpleName!!) 22 | } 23 | 24 | open fun preCompose() {} 25 | 26 | /** 27 | * Merges the given style from [UIRegistry] into this, if not null. 28 | */ 29 | @Suppress("unchecked_cast") 30 | open fun applyStyle(name: String): S { 31 | val activeStyle = UIRegistry.obtainStyle(name) 32 | if (activeStyle != null) merge(activeStyle as S) 33 | return this as S 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/Units.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit 2 | 3 | import net.prismclient.aether.core.util.property.Copyable 4 | import net.prismclient.aether.ui.composition.Composable 5 | import kotlin.reflect.KClass 6 | import kotlin.reflect.full.isSubclassOf 7 | 8 | /** 9 | * [UIUnit] is the superclass of all units, which represent an amount of pixels on the screen when 10 | * computed. It is used to describe the position, and size of components and shapes as well as other 11 | * pixel based properties. The provided generic value must be the class which is inheriting this class. 12 | * It is used for the interface [Copyable] so that the unit can be copied to be manipulated with later. 13 | * 14 | * @author sen 15 | * @since 1.0 16 | */ 17 | abstract class UIUnit>(open var value: Float) : Copyable { 18 | open var cachedValue: Float = 0f 19 | 20 | /** 21 | * Updates the [cachedValue] based on the provided values. 22 | * 23 | * @see updateCache 24 | */ 25 | open fun compute(composable: Composable?, width: Float, height: Float, yaxis: Boolean) { 26 | cachedValue = updateCache(composable, width, height, yaxis) 27 | } 28 | 29 | /** 30 | * Updates [cachedValue] based on the given value. The [width] and [height] represent the 31 | * width and height of the composable or other object, such as a shape. [yaxis] indicates 32 | * that the expected axis should be the y-axis. 33 | */ 34 | abstract fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float 35 | 36 | /** 37 | * [identifiesAs] is intended to replace the `instanceof` or `is` keyword as certain [UIUnit]s might 38 | * not actually be an instance of the expected type, but represent one, such as a [DynamicUnit]. 39 | */ 40 | open fun > identifiesAs(clazz: KClass): Boolean = this::class.isSubclassOf(clazz) 41 | } 42 | 43 | /** 44 | * An internal class used to represent a [Composable] which resizes based on the content within it. 45 | * 46 | * @author sen 47 | * @since 1.0 48 | */ 49 | internal class ResizeUnit : UIUnit(0f) { 50 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float = 0f 51 | 52 | override fun copy(): ResizeUnit = ResizeUnit() 53 | } 54 | 55 | /** 56 | * Computes and updates the unit with the provided [composable] as the width and height. 57 | */ 58 | internal fun UIUnit<*>?.compute(composable: Composable?, yaxis: Boolean) { 59 | this?.compute(composable, composable?.width ?: 0f, composable?.height ?: 0f, yaxis) 60 | } 61 | 62 | /** 63 | * An extension function used to support the [UIUnit.identifiesAs] function. Returns true if this 64 | * is a subclass or equal to [other], like an instanceof type check. 65 | */ 66 | internal fun > UIUnit<*>?.typeOf(other: KClass): Boolean = 67 | this != null && (this.identifiesAs(other)) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/other/AnchorPoint.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.other 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | import net.prismclient.aether.core.util.property.Animatable 5 | import net.prismclient.aether.core.util.property.Copyable 6 | import net.prismclient.aether.core.util.property.Mergable 7 | import net.prismclient.aether.core.util.shorthands.* 8 | import net.prismclient.aether.ui.alignment.Alignment 9 | import net.prismclient.aether.ui.alignment.Alignment.* 10 | import net.prismclient.aether.ui.composition.Composable 11 | import net.prismclient.aether.ui.unit.UIUnit 12 | 13 | /** 14 | * Expects a width and height which is used to scale the given properties 15 | * 16 | * @author sen 17 | * @since 1.0 18 | */ 19 | open class AnchorPoint : Animatable, Copyable, Mergable { 20 | open var x: UIUnit<*>? = null 21 | open var y: UIUnit<*>? = null 22 | 23 | open fun compose(composable: Composable?, width: Float, height: Float) { 24 | x?.compute(composable, width, height, false) 25 | y?.compute(composable, width, height, true) 26 | } 27 | 28 | /** 29 | * Sets the x and y values to relative values based on the [alignment] 30 | */ 31 | fun align(alignment: Alignment) { 32 | x = when (alignment) { 33 | TOPCENTER, CENTER, BOTTOMCENTER -> 0.5f 34 | TOPRIGHT, MIDDLERIGHT, BOTTOMRIGHT -> 1f 35 | else -> 0f 36 | }.crel 37 | y = when (alignment) { 38 | MIDDLELEFT, CENTER, MIDDLERIGHT -> 0.5f 39 | BOTTOMLEFT, BOTTOMCENTER, BOTTOMRIGHT -> 1f 40 | else -> 0f 41 | }.crel 42 | } 43 | 44 | override fun animate( 45 | context: AnimationContext<*>, 46 | initial: AnchorPoint?, 47 | start: AnchorPoint?, 48 | end: AnchorPoint?, 49 | progress: Float, 50 | completed: Boolean 51 | ) { 52 | TODO("Not yet implemented") 53 | } 54 | 55 | override fun copy(): AnchorPoint = AnchorPoint().also { 56 | it.x = x?.copy() 57 | it.y = y?.copy() 58 | } 59 | 60 | override fun merge(other: AnchorPoint?) { 61 | if (other != null) { 62 | x = other.x or x 63 | y = other.y or y 64 | } 65 | } 66 | override fun toString(): String = "AnchorPoint($x, $y)" 67 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/other/Margin.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.other 2 | 3 | import net.prismclient.aether.ui.unit.UIUnit 4 | 5 | /** 6 | * The margin is the space between each component within a layout. This will be offset by [top] and 7 | * [right], and the next component will be offset by the [right] and [bottom]. If the component is 8 | * not within a restrictive layout, the [top] and [right] will offset by the given position, and [bottom] 9 | * and [left] will be ignored. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | open class Margin(top: UIUnit<*>?, right: UIUnit<*>?, bottom: UIUnit<*>?, left: UIUnit<*>?) : 15 | Padding(top, right, bottom, left) { 16 | override fun copy(): Margin = Margin(top?.copy(), right?.copy(), bottom?.copy(), left?.copy()) 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/other/Padding.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.other 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | import net.prismclient.aether.core.util.property.UIProperty 5 | import net.prismclient.aether.core.util.shorthands.or 6 | import net.prismclient.aether.ui.composition.Composable 7 | import net.prismclient.aether.ui.modifier.UIModifier 8 | import net.prismclient.aether.ui.unit.UIUnit 9 | import net.prismclient.aether.ui.unit.compute 10 | 11 | /** 12 | * Padding is a property of [UIModifier] which represents the bounds of a component. When padding is added, the content size 13 | * does not change but the bounds of the actual component will change. This will affect properties such as the background 14 | * that rely on the "relative" bounds instead of the inner bounds. 15 | * 16 | * @author sen 17 | * @since 1.0 18 | */ 19 | open class Padding( 20 | var top: UIUnit<*>?, 21 | var right: UIUnit<*>?, 22 | var bottom: UIUnit<*>?, 23 | var left: UIUnit<*>? 24 | ) : UIProperty { 25 | override fun compose(composable: Composable?) { 26 | composable!! 27 | top?.compute(composable, true) 28 | right?.compute(composable, false) 29 | bottom?.compute(composable, true) 30 | left?.compute(composable, false) 31 | } 32 | 33 | override fun animate( 34 | context: AnimationContext<*>, 35 | initial: Padding?, 36 | start: Padding?, 37 | end: Padding?, 38 | progress: Float, 39 | completed: Boolean 40 | ) { 41 | TODO("Not yet implemented") 42 | } 43 | 44 | override fun copy(): Padding = Padding(top?.copy(), right?.copy(), bottom?.copy(), left?.copy()) 45 | 46 | override fun merge(other: Padding?) { 47 | if (other != null) { 48 | top = other.top or top 49 | right = other.right or right 50 | bottom = other.bottom or bottom 51 | left = other.left or left 52 | } 53 | } 54 | 55 | operator fun component1() = top 56 | 57 | operator fun component2() = right 58 | 59 | operator fun component3() = bottom 60 | 61 | operator fun component4() = left 62 | 63 | override fun toString(): String = "Padding(top=$top, right=$right, bottom=$bottom, left=$left)" 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/other/UIRadius.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.other 2 | 3 | import net.prismclient.aether.core.animation.AnimationContext 4 | import net.prismclient.aether.core.util.property.UIProperty 5 | import net.prismclient.aether.core.util.shorthands.or 6 | import net.prismclient.aether.ui.composition.Composable 7 | import net.prismclient.aether.ui.unit.UIUnit 8 | import net.prismclient.aether.ui.unit.compute 9 | 10 | /** 11 | * [UIRadius] represents a 4 corner shape, such as rectangle which can be used to create 12 | * rounded corners. 13 | * 14 | * @author sen 15 | * @since 1.0 16 | */ 17 | open class UIRadius( 18 | var topLeft: UIUnit<*>? = null, 19 | var topRight: UIUnit<*>? = null, 20 | var bottomRight: UIUnit<*>? = null, 21 | var bottomLeft: UIUnit<*>? = null 22 | ) : UIProperty { 23 | override fun compose(composable: Composable?) { 24 | composable!! 25 | topLeft?.compute(composable, true) 26 | topRight?.compute(composable, false) 27 | bottomRight?.compute(composable, true) 28 | bottomLeft?.compute(composable, false) 29 | } 30 | 31 | override fun animate( 32 | context: AnimationContext<*>, 33 | initial: UIRadius?, 34 | start: UIRadius?, 35 | end: UIRadius?, 36 | progress: Float, 37 | completed: Boolean 38 | ) { 39 | TODO("Not yet implemented") 40 | } 41 | 42 | override fun copy(): UIRadius = UIRadius(topLeft?.copy(), topRight?.copy(), bottomRight?.copy(), bottomLeft?.copy()) 43 | 44 | override fun merge(other: UIRadius?) { 45 | if (other != null) { 46 | topLeft = other.topLeft or topLeft 47 | topRight = other.topRight or topRight 48 | bottomRight = other.bottomRight or bottomRight 49 | bottomLeft = other.bottomLeft or bottomLeft 50 | } 51 | } 52 | 53 | override fun toString(): String = 54 | "UIRadius(topLeft=$topLeft, topRight=$topRight, bottomRight=$bottomRight, bottomLeft=$bottomLeft)" 55 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/type/DependableUnit.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.type 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | import net.prismclient.aether.ui.unit.UIUnit 5 | import java.util.function.Supplier 6 | 7 | /** 8 | * [DependableUnit] expects a [Supplier] which is executed when the unit is calculated. The returned 9 | * value is the cached value. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | class DependableUnit(val supplier: Supplier) : UIUnit(0f) { 15 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float = supplier.get() 16 | 17 | override fun copy(): DependableUnit = DependableUnit(supplier) 18 | } 19 | 20 | fun Dependent(supplier: Supplier): DependableUnit = DependableUnit(supplier) -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/type/RelativeUnit.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.type 2 | 3 | import net.prismclient.aether.core.Aether 4 | import net.prismclient.aether.ui.composition.Composable 5 | import net.prismclient.aether.ui.unit.UIUnit 6 | 7 | /** 8 | * A [UIUnit] which scales based on the width or height of the composable's parent. If 9 | * there is no parent, the screen's width and height are which this scales on. Because 10 | * the parent size is known immediately, this is not a relative unit. 11 | * 12 | * @author sen 13 | * @since 1.0 14 | */ 15 | open class RelativeUnit(value: Float) : UIUnit(value) { 16 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float { 17 | return if (!yaxis) { 18 | composable?.parentWidth() ?: Aether.instance.displayWidth 19 | } else { 20 | composable?.parentHeight() ?: Aether.instance.displayHeight 21 | } * value 22 | } 23 | 24 | override fun copy(): RelativeUnit = RelativeUnit(value) 25 | 26 | override fun toString(): String = "Relative($value, $cachedValue)" 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/type/SizeUnit.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.type 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | import net.prismclient.aether.ui.unit.UIUnit 5 | 6 | /** 7 | * Acts nearly identical to [RelativeUnit] however, unlike [RelativeUnit] the value scales 8 | * based on the width/height of the provided width and height instead of the parent of the 9 | * composable. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | class SizeUnit(value: Float) : UIUnit(value) { 15 | override fun updateCache( 16 | composable: Composable?, 17 | width: Float, 18 | height: Float, 19 | yaxis: Boolean 20 | ): Float = if (!yaxis) { 21 | width 22 | } else { 23 | height 24 | } * value 25 | 26 | override fun copy(): SizeUnit = SizeUnit(value) 27 | 28 | override fun toString(): String = "SizeUnit($value, $cachedValue)" 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/type/other/OperationUnit.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.type.other 2 | 3 | import net.prismclient.aether.core.util.shorthands.copy 4 | import net.prismclient.aether.core.util.shorthands.dp 5 | import net.prismclient.aether.ui.composition.Composable 6 | import net.prismclient.aether.ui.unit.UIUnit 7 | import kotlin.reflect.KClass 8 | 9 | /** 10 | * [OperationUnit] itself, is not a value, however, it accepts two units which it computes 11 | * a value based on the value of the given units. Because [OperationUnit] accepts units as 12 | * values to operate on, it can be considered a dynamic or static unit depending on the 13 | * provided units. If at least one unit is dynamic this is dynamic. 14 | * 15 | * @author sen 16 | * @since 1.0 17 | */ 18 | @Suppress("MemberVisibilityCanBePrivate") 19 | open class OperationUnit(val unit1: UIUnit<*>?, val unit2: UIUnit<*>?, val operation: Operation) : 20 | UIUnit(0f) { 21 | 22 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float { 23 | // The units automatically make the composable dynamic if necessary. 24 | unit1?.compute(composable, width, height, yaxis) 25 | unit2?.compute(composable, width, height, yaxis) 26 | return when (operation) { 27 | Operation.ADD -> unit1.dp + unit2.dp 28 | Operation.SUBTRACT -> unit1.dp - unit2.dp 29 | Operation.MULTIPLY -> unit1.dp * unit2.dp 30 | Operation.DIVIDE -> unit1.dp / unit2.dp 31 | } 32 | } 33 | 34 | override fun > identifiesAs(clazz: KClass): Boolean = 35 | super.identifiesAs(clazz) || (unit1?.identifiesAs(clazz) ?: false) || (unit2?.identifiesAs(clazz) ?: false) 36 | 37 | override fun copy(): OperationUnit = OperationUnit(unit1.copy, unit2.copy, operation) 38 | 39 | override fun toString(): String = "Operation($unit1, $unit2, $operation)" 40 | 41 | /** 42 | * Represents an operation that [OperationUnit] does when the value needs to be computed. 43 | * 44 | * @author sen 45 | * @since 1.0 46 | */ 47 | enum class Operation { 48 | ADD, SUBTRACT, MULTIPLY, DIVIDE 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/type/range/RangeUnit.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.type.range 2 | 3 | import net.prismclient.aether.core.util.shorthands.copy 4 | import net.prismclient.aether.core.util.shorthands.dp 5 | import net.prismclient.aether.ui.composition.Composable 6 | import net.prismclient.aether.ui.unit.UIUnit 7 | import kotlin.reflect.KClass 8 | 9 | /** 10 | * Acts like [unit] however, the cached value will not exceed the [min] and [max] bounds. If either bounds 11 | * are null, it is ignored. [UIUnit.identifiesAs] is overwritten and returns [unit]s output. 12 | * 13 | * @author sen 14 | * @since 1.0 15 | */ 16 | class RangeUnit(var unit: UIUnit<*>, var min: UIUnit<*>?, var max: UIUnit<*>?) : UIUnit(0f) { 17 | override var cachedValue: Float = super.cachedValue 18 | set(value) { 19 | field = value 20 | .coerceAtLeast(min?.cachedValue ?: value) 21 | .coerceAtMost(max?.cachedValue ?: value) 22 | } 23 | 24 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float { 25 | unit.compute(composable, width, height, yaxis) 26 | min?.compute(composable, width, height, yaxis) 27 | max?.compute(composable, width, height, yaxis) 28 | return unit.dp 29 | } 30 | 31 | override fun copy(): RangeUnit = RangeUnit(unit.copy(), min.copy, max.copy) 32 | 33 | override fun toString(): String = "RangeUnit($unit, $min, $max)" 34 | 35 | override fun > identifiesAs(clazz: KClass): Boolean = unit.identifiesAs(clazz) 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/net/prismclient/aether/ui/unit/type/solid/PixelUnit.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.ui.unit.type.solid 2 | 3 | import net.prismclient.aether.ui.composition.Composable 4 | import net.prismclient.aether.ui.unit.UIUnit 5 | 6 | /** 7 | * The simplest of all [UIUnit]s which represents the given [value] in pixels. It is a static unit, 8 | * and does not require the parent of the composition to re-update if necessary. 9 | * 10 | * @author sen 11 | * @since 1.0 12 | */ 13 | open class PixelUnit(value: Float) : UIUnit(value) { 14 | // Since this is a static unit, and the value of it is 15 | // known at the creation, return the value directly. 16 | @Suppress("SuspiciousVarProperty") 17 | override var cachedValue: Float = value 18 | get() = value 19 | 20 | override fun updateCache(composable: Composable?, width: Float, height: Float, yaxis: Boolean): Float = value 21 | 22 | override fun copy(): PixelUnit = PixelUnit(value) 23 | 24 | override fun toString(): String = "Pixel($value, $cachedValue)" 25 | } -------------------------------------------------------------------------------- /src/test/java/test.java: -------------------------------------------------------------------------------- 1 | import net.prismclient.aether.core.event.MouseMove; 2 | import net.prismclient.aether.core.event.UIEventBus; 3 | 4 | public class test { 5 | public void hi() { 6 | UIEventBus.INSTANCE.register("HEllo!", MouseMove.class, (event) -> { 7 | System.out.println("Hello!"); 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/kotlin/net/prismclient/aether/example/Game.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.example 2 | 3 | import net.prismclient.aether.core.Aether 4 | import net.prismclient.aether.example.util.Window 5 | import net.prismclient.aether.ui.alignment.UITextAlignment 6 | import net.prismclient.aether.ui.dsl.Renderer 7 | import org.lwjgl.glfw.Callbacks 8 | import org.lwjgl.glfw.GLFW.* 9 | import org.lwjgl.glfw.GLFWErrorCallback 10 | import org.lwjgl.opengl.GL 11 | import org.lwjgl.opengl.GL.createCapabilities 12 | import org.lwjgl.opengl.GL11.* 13 | import org.lwjgl.system.MemoryStack 14 | import org.lwjgl.system.MemoryUtil 15 | import org.lwjgl.system.Platform 16 | import kotlin.math.max 17 | 18 | /** 19 | * An example game using LWJGL 3 and GLFW to run Aether. 20 | */ 21 | abstract class Game(val windowTitle: String) { 22 | open lateinit var aether: Aether 23 | open lateinit var window: Window 24 | 25 | open var mouseX: Float = 0f 26 | open var mouseY: Float = 0f 27 | 28 | open var fps = 0 29 | 30 | open fun initialize() { 31 | createWindow() 32 | 33 | // Create the callbacks to handle Aether input and other things 34 | createCallbacks() 35 | } 36 | 37 | open fun createAether() { 38 | // Aether is an open (inheritable) class. It can be instantiated on its own, 39 | // however if a specific feature is needed, it can be extended to implement it. 40 | // Renderer is an object class provided within the example package which implements 41 | // UIRenderer and uses NanoVG to handle the functions. 42 | aether = Aether(Renderer) 43 | } 44 | 45 | open fun createWindow() { 46 | GLFWErrorCallback.createPrint().set() 47 | 48 | // Configuration.GLFW_CHECK_THREAD0.set(false) 49 | 50 | if (!glfwInit()) throw RuntimeException("Failed to init GLFW") 51 | if (Platform.get() === Platform.MACOSX) { 52 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3) 53 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2) 54 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE) 55 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) 56 | } 57 | 58 | glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE) 59 | 60 | window = Window(glfwCreateWindow(1000, 600, windowTitle, MemoryUtil.NULL, MemoryUtil.NULL)) 61 | 62 | if (window.handle == MemoryUtil.NULL) { 63 | glfwTerminate() 64 | throw RuntimeException("Failed to create the window.") 65 | } 66 | 67 | 68 | // Add some callbacks for window resizing and content scale 69 | glfwSetFramebufferSizeCallback(window.handle) { _, width, height -> 70 | window.width = width 71 | window.height = height 72 | 73 | // Update Aether as the window is resized 74 | aether.update( 75 | width / window.contentScaleX, 76 | height / window.contentScaleY, 77 | max(window.contentScaleX, window.contentScaleY) 78 | ) 79 | } 80 | 81 | glfwSetWindowContentScaleCallback(window.handle) { _, x, y -> 82 | window.contentScaleX = x 83 | window.contentScaleY = y 84 | } 85 | 86 | glfwMakeContextCurrent(window.handle) 87 | createCapabilities() 88 | glfwSetTime(0.0) 89 | glfwSwapInterval(0) 90 | 91 | // Create Aether after we've initialized OpenGL. 92 | createAether() 93 | 94 | // Update the state of the window 95 | MemoryStack.stackPush().use { 96 | val width = it.mallocInt(1) 97 | val height = it.mallocInt(1) 98 | val contentScaleX = it.mallocFloat(1) 99 | val contentScaleY = it.mallocFloat(1) 100 | glfwGetFramebufferSize(window.handle, width, height) 101 | glfwGetWindowContentScale(window.handle, contentScaleX, contentScaleY) 102 | 103 | window.width = width[0] 104 | window.height = height[0] 105 | window.contentScaleX = contentScaleX[0] 106 | window.contentScaleY = contentScaleY[0] 107 | 108 | // Update Aether and pass the properties of the window. For 109 | // content scale, return the larger value of the two axes. 110 | aether.update( 111 | window.width / window.contentScaleX, 112 | window.height / window.contentScaleY, 113 | max(window.contentScaleX, window.contentScaleY) 114 | ) 115 | } 116 | } 117 | 118 | /** 119 | * Creates the callbacks for input events. The window resize callback is set within [createWindow]. 120 | */ 121 | abstract fun createCallbacks() 122 | 123 | open fun run() { 124 | var frames = 0 125 | var lastSecond = System.currentTimeMillis() 126 | 127 | while (!glfwWindowShouldClose(window.handle)) { 128 | aether.renderFrames() 129 | 130 | glViewport(0, 0, window.width, window.height) 131 | glClearColor(0f, 0f, 0f, 0f) 132 | glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT) 133 | 134 | // Render Aether :) 135 | aether.render() 136 | 137 | Renderer { 138 | beginFrame(Aether.instance.displayWidth, Aether.instance.displayHeight, Aether.instance.devicePixelRatio) 139 | color(-1) 140 | font("Poppins-Regular", 16f, UITextAlignment.LEFT, UITextAlignment.TOP, 0f) 141 | "FPS: $fps".render(0f, 0f) 142 | endFrame() 143 | } 144 | 145 | glfwSwapBuffers(window.handle) 146 | glfwPollEvents() 147 | 148 | if (lastSecond + 1000 < System.currentTimeMillis()) { 149 | lastSecond = System.currentTimeMillis() 150 | fps = frames 151 | frames = 0 152 | } else frames++ 153 | } 154 | 155 | GL.setCapabilities(null) 156 | Callbacks.glfwFreeCallbacks(window.handle) 157 | glfwTerminate() 158 | glfwSetErrorCallback(null)!!.free() 159 | } 160 | } -------------------------------------------------------------------------------- /src/test/kotlin/net/prismclient/aether/example/Runner.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.example 2 | 3 | import net.prismclient.aether.core.Aether 4 | import net.prismclient.aether.core.input.MouseButtonType 5 | import net.prismclient.aether.ui.screen.UIScreen 6 | import org.lwjgl.glfw.GLFW.* 7 | 8 | /** 9 | * Creates a new game with the provided [screen]. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | class Runner(private val screen: UIScreen) : Game("Runner") { 15 | init { 16 | initialize() 17 | Aether.displayScreen(screen) 18 | run() 19 | } 20 | 21 | override fun createCallbacks() { 22 | // Mouse move input 23 | glfwSetCursorPosCallback(window.handle) { _, x, y -> 24 | mouseX = x.toFloat() 25 | mouseY = y.toFloat() 26 | // Set the MouseButtonType to None to inform Aether that it is 27 | // a mouse move event instead of a mouse click or release event 28 | aether.mouseChanged(mouseX, mouseY, MouseButtonType.None, false) 29 | } 30 | 31 | // Mouse press / release input 32 | glfwSetMouseButtonCallback(window.handle) { _, button, action, _ -> 33 | val mouseButton = when (button) { 34 | GLFW_MOUSE_BUTTON_LEFT -> MouseButtonType.Primary 35 | GLFW_MOUSE_BUTTON_RIGHT -> MouseButtonType.Secondary 36 | GLFW_MOUSE_BUTTON_MIDDLE -> MouseButtonType.Middle 37 | else -> return@glfwSetMouseButtonCallback 38 | } 39 | 40 | // Inform Aether that the mouse has changed with the provided 41 | // mouse position, mouse button, and if the mouse is released. 42 | aether.mouseChanged(mouseX, mouseY, mouseButton, action == GLFW_RELEASE) 43 | } 44 | 45 | // Mouse scroll input 46 | glfwSetScrollCallback(window.handle) { _, xDst, yDst -> 47 | aether.mouseScrolled(xDst.toFloat() * 10f, yDst.toFloat() * 10f) 48 | } 49 | 50 | // Text / keyboard input 51 | glfwSetKeyCallback(window.handle) { _, keyCode, scanCode, action, _ -> 52 | // Reset the screen when esc is pressed. 53 | if (glfwGetKeyName(keyCode, scanCode) == null) { 54 | if (action == GLFW_PRESS && keyCode == GLFW_KEY_ESCAPE) { 55 | Aether.displayScreen(screen) 56 | } 57 | } 58 | 59 | // TODO: Aether keyboard input 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/kotlin/net/prismclient/aether/example/components/Logo.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.example.components 2 | 3 | import net.prismclient.aether.core.util.shorthands.copy 4 | import net.prismclient.aether.ui.component.type.Construct 5 | import net.prismclient.aether.ui.modifier.UIModifier 6 | 7 | /** 8 | * A component to represent the component logo. This extends a construct, as it is rendering 9 | * a fixed set of shapes which is not intended to be changed. 10 | * 11 | * @author sen 12 | * @since 1.0 13 | */ 14 | class Logo(modifier: UIModifier<*>) : Construct(modifier) { 15 | init { 16 | modifier.size(145, 37) 17 | action = logoAction 18 | } 19 | 20 | override fun copy(): Logo = Logo(modifier.copy()) 21 | 22 | companion object { 23 | @JvmStatic 24 | val logoAction: Runnable = Runnable { 25 | 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/kotlin/net/prismclient/aether/example/screens/Animations.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.example.screens 2 | 3 | import net.prismclient.aether.core.animation.type.ComposableAnimation 4 | import net.prismclient.aether.core.ease.impl.UIQuart 5 | import net.prismclient.aether.core.util.shorthands.ColorOf 6 | import net.prismclient.aether.core.util.shorthands.px 7 | import net.prismclient.aether.core.util.shorthands.rel 8 | import net.prismclient.aether.core.util.shorthands.rgb 9 | import net.prismclient.aether.example.Runner 10 | import net.prismclient.aether.ui.alignment.Alignment 11 | import net.prismclient.aether.ui.component.Button 12 | import net.prismclient.aether.ui.component.Label 13 | import net.prismclient.aether.ui.dsl.Compose 14 | import net.prismclient.aether.ui.dsl.Resource 15 | import net.prismclient.aether.ui.font.* 16 | import net.prismclient.aether.ui.modifier.DefaultModifier 17 | import net.prismclient.aether.ui.modifier.Modifier 18 | import net.prismclient.aether.ui.screen.UIScreen 19 | 20 | object Animations : UIScreen { 21 | var initialized = false 22 | 23 | @JvmStatic 24 | fun main(args: Array) { 25 | Runner(Animations) 26 | } 27 | 28 | override fun compose() = Compose { 29 | if (!initialized) { 30 | Resource { 31 | fontCollection(localResource("/fonts/Poppins")) 32 | fontCollection(localResource("/fonts/Montserrat")) 33 | 34 | pngCollection(localResource("/images/")) 35 | svgCollection(localResource("/icons/")) 36 | } 37 | initialized = true 38 | } 39 | 40 | val propertyAnimation = ComposableAnimation() 41 | 42 | //Aether.instance.defaultComposition?.modifier?.disableOptimizations() 43 | 44 | val title = Button( 45 | text = "Some text!", 46 | modifier = Modifier() 47 | .control(Alignment.CENTER) 48 | .background(ColorOf(1f, 0f, 0f)) 49 | .padding(5.px, 14.px, 5.px, 14f.px), 50 | fontStyle = FontStyle() 51 | .fontName("Poppins-Medium") 52 | .fontColor(0xFFFFFF.rgb) 53 | .fontSize(24.px) 54 | .fontType(FontType.AutoWidth), 55 | onClick = { 56 | propertyAnimation.start(this, modifier as DefaultModifier) 57 | } 58 | ) 59 | 60 | // propertyAnimation.completionAction = Runnable { 61 | // propertyAnimation.start(title, title.modifier as DefaultModifier) 62 | // } 63 | 64 | propertyAnimation.keyframes.add(propertyAnimation.createKeyframe(UIQuart(1250L), DefaultModifier().x(0.px))) 65 | 66 | propertyAnimation.keyframes.add(propertyAnimation.createKeyframe(UIQuart(1000L), DefaultModifier().x(0.5.rel))) 67 | 68 | title.animations = HashMap() 69 | title.animations!!["Hello"] = propertyAnimation 70 | } 71 | } -------------------------------------------------------------------------------- /src/test/kotlin/net/prismclient/aether/example/util/Window.kt: -------------------------------------------------------------------------------- 1 | package net.prismclient.aether.example.util 2 | 3 | /** 4 | * Represents the open gl window for the example 5 | * 6 | * @author sen 7 | */ 8 | class Window( 9 | val handle: Long = 0, 10 | var width: Int = 0, 11 | var height: Int = 0, 12 | var contentScaleX: Float = 0f, 13 | var contentScaleY: Float = 0f 14 | ) -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-Black.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-BlackItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-BoldItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-ExtraBold.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-ExtraLight.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-Italic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-Light.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-LightItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-Medium.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-MediumItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-SemiBold.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-Thin.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Montserrat/Montserrat-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Montserrat/Montserrat-ThinItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-Black.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-Bold.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-Italic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-Light.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-Medium.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-Regular.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-Thin.ttf -------------------------------------------------------------------------------- /src/test/resources/fonts/Poppins/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/fonts/Poppins/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/bag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/medal-star-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/messages.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/people.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 16 | 18 | 20 | 22 | 23 | -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 15 | 16 | -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/icons/gradient/video.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 15 | 16 | -------------------------------------------------------------------------------- /src/test/resources/icons/mods/mouse.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/icons/outline/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/icons/outline/frame.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/icons/outline/more-option.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/icons/outline/sort.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/icons/outline/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/bag.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/medal-star.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/messages.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/people.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/premium-star.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/icons/solid/video.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/images/Character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/images/Character.png -------------------------------------------------------------------------------- /src/test/resources/images/Prism-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prism-Client/Aether/3b98cf565f938faacfe61b4dec383110c1a56908/src/test/resources/images/Prism-Logo.png -------------------------------------------------------------------------------- /src/test/resources/licenses/OFL: -------------------------------------------------------------------------------- 1 | Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /src/test/resources/licenses/Vuesax: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ldrovira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. --------------------------------------------------------------------------------