├── .gitignore ├── LICENSE ├── README.md ├── assets └── demo.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── io │ └── github │ └── itzispyder │ └── improperui │ ├── ImproperUI.java │ ├── ImproperUIAPI.java │ ├── InitContext.java │ ├── client │ └── ImproperUIClient.java │ ├── config │ ├── ConfigKey.java │ ├── ConfigKeyHolder.java │ ├── ConfigReader.java │ ├── Paths.java │ ├── Properties.java │ └── PropertyCache.java │ ├── interfaces │ └── FontManagerAccessor.java │ ├── mixin │ ├── MixinFontManager.java │ └── MixinMinecraftClient.java │ ├── render │ ├── Element.java │ ├── ImproperUIPanel.java │ ├── KeyHolderElement.java │ ├── constants │ │ ├── Alignment.java │ │ ├── Axis.java │ │ ├── BackgroundClip.java │ │ ├── ChildrenAlignment.java │ │ ├── InputType.java │ │ ├── Position.java │ │ └── Visibility.java │ ├── elements │ │ ├── Button.java │ │ ├── CheckBox.java │ │ ├── Header.java │ │ ├── HyperLink.java │ │ ├── Label.java │ │ ├── Positionable.java │ │ ├── Radio.java │ │ ├── Slider.java │ │ ├── TextBox.java │ │ └── TextField.java │ └── math │ │ ├── Color.java │ │ ├── Dimensions.java │ │ └── animation │ │ ├── Animator.java │ │ └── PollingAnimator.java │ ├── script │ ├── CallbackHandler.java │ ├── CallbackListener.java │ ├── Event.java │ ├── ScriptArgs.java │ ├── ScriptParser.java │ ├── ScriptReader.java │ ├── callbacks │ │ └── BuiltInCallbacks.java │ └── events │ │ ├── KeyEvent.java │ │ └── MouseEvent.java │ └── util │ ├── ChatUtils.java │ ├── FileValidationUtils.java │ ├── MathUtils.java │ ├── RenderUtils.java │ ├── StringUtils.java │ └── misc │ ├── ManualMap.java │ ├── Pair.java │ └── Voidable.java └── resources ├── assets └── improperui │ ├── icon.png │ ├── improperui │ ├── example.ui │ └── homescreen.ui │ └── lang │ └── en_us.json ├── fabric.mod.json └── improperui.mixins.json /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 2 | All rights reserved. 3 | -------------------------------------------------------------------------------- /assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItziSpyder/ImproperUI/930da42889d10c073bf1e904fbb39997c2d13811/assets/demo.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.7.0-alpha.6' 3 | id 'maven-publish' 4 | } 5 | 6 | version = project.mod_version 7 | group = project.maven_group 8 | 9 | base { 10 | archivesName = project.archives_base_name 11 | } 12 | 13 | repositories { 14 | // Add repositories to retrieve artifacts from in here. 15 | // You should only use this when depending on other mods because 16 | // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. 17 | // See https://docs.gradle.org/current/userguide/declaring_repositories.html 18 | // for more information about repositories. 19 | } 20 | 21 | dependencies { 22 | // To change the versions see the gradle.properties file 23 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 24 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" 25 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 26 | 27 | // Fabric API. This is technically optional, but you probably want it anyway. 28 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 29 | } 30 | 31 | processResources { 32 | inputs.property "version", project.version 33 | inputs.property "minecraft_version", project.minecraft_version 34 | inputs.property "loader_version", project.loader_version 35 | filteringCharset "UTF-8" 36 | 37 | filesMatching("fabric.mod.json") { 38 | expand "version": project.version, 39 | "minecraft_version": project.minecraft_version, 40 | "loader_version": project.loader_version 41 | } 42 | } 43 | 44 | def targetJavaVersion = 17 45 | tasks.withType(JavaCompile).configureEach { 46 | // ensure that the encoding is set to UTF-8, no matter what the system default is 47 | // this fixes some edge cases with special characters not displaying correctly 48 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 49 | // If Javadoc is generated, this must be specified in that task too. 50 | it.options.encoding = "UTF-8" 51 | if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { 52 | it.options.release.set(targetJavaVersion) 53 | } 54 | } 55 | 56 | java { 57 | def javaVersion = JavaVersion.toVersion(targetJavaVersion) 58 | if (JavaVersion.current() < javaVersion) { 59 | toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) 60 | } 61 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 62 | // if it is present. 63 | // If you remove this line, sources will not be generated. 64 | withSourcesJar() 65 | } 66 | 67 | jar { 68 | from("LICENSE") { 69 | rename { "${it}_${project.archivesBaseName}"} 70 | } 71 | } 72 | 73 | // configure the maven publication 74 | publishing { 75 | publications { 76 | create("mavenJava", MavenPublication) { 77 | artifactId = project.archives_base_name 78 | from components.java 79 | } 80 | } 81 | 82 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 83 | repositories { 84 | // Add repositories to publish to here. 85 | // Notice: This block does NOT have the same function as the block in the top level. 86 | // The repositories here will be used for publishing your artifact, not for 87 | // retrieving dependencies. 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | org.gradle.java.home=/Program Files/Java/jdk-21 4 | 5 | # Fabric Properties 6 | # check these on https://modmuss50.me/fabric.html 7 | minecraft_version=1.21 8 | yarn_mappings=1.21+build.1 9 | loader_version=0.15.11 10 | 11 | # Mod Properties 12 | mod_version = 1.21-0.0.6-BETA 13 | maven_group = io.github.itzispyder 14 | archives_base_name = ImproperUI 15 | 16 | # Dependencies 17 | # check this on https://modmuss50.me/fabric.html 18 | fabric_version=0.100.1+1.21 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItziSpyder/ImproperUI/930da42889d10c073bf1e904fbb39997c2d13811/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-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/ImproperUI.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui; 2 | 3 | import io.github.itzispyder.improperui.script.callbacks.BuiltInCallbacks; 4 | import net.fabricmc.api.ModInitializer; 5 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; 6 | import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; 7 | import net.minecraft.client.option.KeyBinding; 8 | import net.minecraft.client.util.InputUtil; 9 | import org.lwjgl.glfw.GLFW; 10 | 11 | public class ImproperUI implements ModInitializer { 12 | 13 | public static final KeyBinding BIND = KeyBindingHelper.registerKeyBinding(new KeyBinding( 14 | "binds.improperui.menu", 15 | InputUtil.Type.KEYSYM, 16 | GLFW.GLFW_KEY_RIGHT_SHIFT, 17 | "binds.improperui" 18 | )); 19 | 20 | @Override 21 | public void onInitialize() { 22 | ImproperUIAPI.init("improperui", ImproperUI.class, 23 | "assets/improperui/improperui/homescreen.ui", 24 | "assets/improperui/improperui/example.ui" 25 | ); 26 | 27 | ClientTickEvents.END_CLIENT_TICK.register(client -> { 28 | while (BIND.wasPressed()) { 29 | ImproperUIAPI.parseAndRunFile("improperui", "homescreen.ui", new BuiltInCallbacks()); 30 | } 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/ImproperUIAPI.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui; 2 | 3 | import io.github.itzispyder.improperui.config.ConfigReader; 4 | import io.github.itzispyder.improperui.config.Paths; 5 | import io.github.itzispyder.improperui.render.Element; 6 | import io.github.itzispyder.improperui.render.ImproperUIPanel; 7 | import io.github.itzispyder.improperui.script.CallbackListener; 8 | import io.github.itzispyder.improperui.script.ScriptParser; 9 | import net.fabricmc.api.ModInitializer; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.File; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | public class ImproperUIAPI { 20 | 21 | public static final Logger LOGGER = LoggerFactory.getLogger("ImproperUIAPI"); 22 | private static final Map CONTEXTS = new HashMap<>(); 23 | 24 | /** 25 | * Example: ImproperUI.init("improperui", ImproperUI.class, "scripts/example.ui"); 26 | * @param modId YOUR mod's mod ID 27 | * @param initializer YOUR mod's main initializer, NOT CLIENT INITIALIZER 28 | * @param scriptPaths Target script files 29 | */ 30 | public static void init(String modId, Class initializer, String... scriptPaths) { 31 | InitContext context = CONTEXTS.get(modId); 32 | 33 | if (context == null) { 34 | context = new InitContext(modId, initializer, scriptPaths); 35 | CONTEXTS.put(modId, context); 36 | } 37 | context.init(); 38 | } 39 | 40 | public static void reload() { 41 | CONTEXTS.values().forEach(InitContext::reload); 42 | } 43 | 44 | public static List collectContext() { 45 | return new ArrayList<>(CONTEXTS.values()); 46 | } 47 | 48 | public static InitContext getContext(String modId) { 49 | return CONTEXTS.get(modId); 50 | } 51 | 52 | public static ConfigReader getConfigReader(String modId, String configFile) { 53 | return new ConfigReader(modId, configFile); 54 | } 55 | 56 | 57 | 58 | // parse helper methods 59 | 60 | public static List parse(String script) { 61 | return ScriptParser.parse(script); 62 | } 63 | 64 | public static List parse(File file) { 65 | return ScriptParser.parseFile(file); 66 | } 67 | 68 | public static void parseAndRunScript(String script, CallbackListener... callbackListeners) { 69 | new ImproperUIPanel(script, callbackListeners).open(); 70 | } 71 | 72 | /** 73 | * Parses and runs the script from the path provided 74 | * @param modId Multiple mods may use ImproperUI at the same time with their own respective scripts, specify the mod ID! 75 | * @param fileName The file NAME, NOT THE FILE PATH 76 | * @param callbackListeners A list of callbacks that you want to add to the panel screen 77 | */ 78 | public static void parseAndRunFile(String modId, String fileName, CallbackListener... callbackListeners) { 79 | File script = new File(Paths.getScripts(modId) + fileName); 80 | new ImproperUIPanel(script, callbackListeners).open(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/InitContext.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui; 2 | 3 | import io.github.itzispyder.improperui.config.Paths; 4 | import io.github.itzispyder.improperui.util.FileValidationUtils; 5 | import net.fabricmc.api.ModInitializer; 6 | 7 | import java.io.*; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | public class InitContext { 12 | 13 | private final String modId; 14 | private final Class initializer; 15 | private String[] scriptPaths; 16 | 17 | private boolean initialized = false; 18 | private final Set paths = new HashSet<>(); 19 | 20 | public InitContext(String modId, Class initializer, String... scriptPaths) { 21 | this.modId = modId; 22 | this.initializer = initializer; 23 | this.scriptPaths = scriptPaths; 24 | } 25 | 26 | public void init() { 27 | if (initialized) 28 | return; 29 | initialized = true; 30 | paths.clear(); 31 | Paths.init(); 32 | ImproperUIAPI.LOGGER.info("Initializing mod '{}' in '{}.class' with {} scripts:", modId, getName(), scriptPaths.length); 33 | 34 | var loader = initializer.getClassLoader(); 35 | for (String path : scriptPaths) 36 | while (copyResource(loader, path) == -1); 37 | } 38 | 39 | public void reload() { 40 | reInit(scriptPaths); 41 | } 42 | 43 | public void reInit(String... scriptPaths) { 44 | this.initialized = false; 45 | this.scriptPaths = scriptPaths; 46 | this.paths.clear(); 47 | init(); 48 | } 49 | 50 | private int copyResource(ClassLoader loader, String path) { 51 | try { 52 | String name = path.trim().replaceAll(".*/", ""); 53 | if (paths.contains(name)) 54 | throw new IllegalArgumentException("path '%s' already exists".formatted(path)); 55 | 56 | InputStream is = loader.getResourceAsStream(path); 57 | 58 | if (is == null) 59 | throw new IllegalArgumentException("resource not found!"); 60 | 61 | InputStreamReader isr = new InputStreamReader(is); 62 | BufferedReader br = new BufferedReader(isr); 63 | String read = String.join("\n", br.lines().toList()); 64 | br.close(); 65 | isr.close(); 66 | is.close(); 67 | 68 | File file = new File(Paths.getScripts(modId) + name); 69 | FileValidationUtils.validate(file); 70 | 71 | FileWriter fw = new FileWriter(file); 72 | BufferedWriter bw = new BufferedWriter(fw); 73 | bw.write(read); 74 | bw.close(); 75 | fw.close(); 76 | 77 | boolean success = file.exists(); 78 | String filePath = file.getPath(); 79 | 80 | if (success) { 81 | paths.add(name); 82 | ImproperUIAPI.LOGGER.info("-> Successfully cloned path '{}' to '{}'", path, filePath); 83 | } 84 | else { 85 | ImproperUIAPI.LOGGER.error("<- Path '{}' read, but was unable to be copied to '{}'", path, filePath); 86 | } 87 | return success ? 1 : -1; 88 | } 89 | catch (Exception ex) { 90 | ImproperUIAPI.LOGGER.error("<- Error copying resource '{}': {}\n", path, ex.getMessage()); 91 | return 0; 92 | } 93 | } 94 | 95 | public String getId() { 96 | return modId; 97 | } 98 | 99 | public Class getInitializer() { 100 | return initializer; 101 | } 102 | 103 | public String getName() { 104 | return initializer.getSimpleName(); 105 | } 106 | 107 | public String[] getPaths() { 108 | return scriptPaths; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/client/ImproperUIClient.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.client; 2 | 3 | import net.fabricmc.api.ClientModInitializer; 4 | import net.minecraft.client.font.TextRenderer; 5 | 6 | public class ImproperUIClient implements ClientModInitializer { 7 | 8 | private static final ImproperUIClient system = new ImproperUIClient(); 9 | public static ImproperUIClient getInstance() { 10 | return system; 11 | } 12 | 13 | public TextRenderer codeRenderer; 14 | 15 | public ImproperUIClient() { 16 | 17 | } 18 | 19 | @Override 20 | public void onInitializeClient() { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/config/ConfigKey.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.config; 2 | 3 | public class ConfigKey { 4 | 5 | public final String modId, path, key; 6 | 7 | public ConfigKey(String entry) { 8 | entry = entry.trim() 9 | .replaceAll("\\s+|_", "-") 10 | .replaceAll("[^a-zA-Z0-9:.-]", ""); 11 | 12 | String[] split = entry.split("\\s*:\\s*"); 13 | 14 | 15 | switch (split.length) { 16 | case 2 -> { 17 | this.modId = "improperui"; 18 | this.path = split[0]; 19 | this.key = split[1]; 20 | } 21 | case 3 -> { 22 | this.modId = split[0]; 23 | this.path = split[1]; 24 | this.key = split[2]; 25 | } 26 | default -> throw new IllegalArgumentException("malformed config key: \"%s\"".formatted(entry)); 27 | } 28 | } 29 | 30 | public ConfigKey(String modId, String path, String key) { 31 | this.modId = modId; 32 | this.path = path; 33 | this.key = key; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object obj) { 38 | if (!(obj instanceof ConfigKey configKey)) 39 | return false; 40 | return configKey.path.equals(this.path) && configKey.key.equals(this.key); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/config/ConfigKeyHolder.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.config; 2 | 3 | import io.github.itzispyder.improperui.render.Element; 4 | 5 | import java.util.function.Function; 6 | 7 | public interface ConfigKeyHolder { 8 | 9 | Function ELEMENT_KEY_HOLDER = element -> { 10 | String regex = "([a-zA-Z0-9_.-]+:)?[a-zA-Z0-9_.-]+\\.[a-zA-Z0-9_.-]+:[a-zA-Z0-9_.-]+"; 11 | for (var s : element.classList) 12 | if (s.matches(regex)) 13 | return new ConfigKey(s); 14 | return null; 15 | }; 16 | 17 | ConfigKey getConfigKey(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/config/ConfigReader.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.config; 2 | 3 | import io.github.itzispyder.improperui.script.ScriptParser; 4 | 5 | public record ConfigReader(String modId, String configFile){ 6 | 7 | public ConfigKey getKey(String property) { 8 | return new ConfigKey(modId, configFile, property); 9 | } 10 | 11 | public PropertyCache getPropertyCache() { 12 | return ScriptParser.getCache(modId); 13 | } 14 | 15 | public Properties.Value read(String property) { 16 | return getPropertyCache().getProperty(getKey(property)); 17 | } 18 | 19 | public void write(String property, Object value) { 20 | ScriptParser.getCache(modId).setProperty(getKey(property), value, true); 21 | } 22 | 23 | public boolean readBool(String property, boolean def) { 24 | var o = read(property); 25 | if (o == null) 26 | write(property, def); 27 | return o != null ? o.first().toBool() : def; 28 | } 29 | 30 | public int readInt(String property, int def) { 31 | return (int)readDouble(property, def); 32 | } 33 | 34 | public double readFloat(String property, float def) { 35 | return (float)readDouble(property, def); 36 | } 37 | 38 | public double readDouble(String property, double def) { 39 | var o = read(property); 40 | if (o == null) 41 | write(property, def); 42 | return o != null ? o.first().toDouble() : def; 43 | } 44 | 45 | public String readStr(String property, String def) { 46 | var o = read(property); 47 | if (o == null) 48 | write(property, def); 49 | return o != null ? o.first().toString() : def; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/config/Paths.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.config; 2 | 3 | import java.io.File; 4 | 5 | public class Paths { 6 | 7 | public static final String FOLDER = ".improper-ui/"; 8 | public static final String SCRIPTS = FOLDER + "scripts/"; 9 | public static final String CONFIGS = FOLDER + "configs/"; 10 | public static final String ASSETS = FOLDER + "assets/"; 11 | 12 | public static void init() { 13 | makeDirIfAbsent(FOLDER); 14 | makeDirIfAbsent(SCRIPTS); 15 | makeDirIfAbsent(CONFIGS); 16 | makeDirIfAbsent(ASSETS); 17 | } 18 | 19 | private static void makeDirIfAbsent(String path) { 20 | File file = new File(path); 21 | if (!file.exists()) 22 | file.mkdirs(); 23 | } 24 | 25 | public static String getScripts(String modId) { 26 | return SCRIPTS + modId + "/"; 27 | } 28 | 29 | public static String getConfigs(String modId) { 30 | return CONFIGS + modId + "/"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/config/Properties.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.config; 2 | 3 | import io.github.itzispyder.improperui.script.ScriptArgs; 4 | import io.github.itzispyder.improperui.util.FileValidationUtils; 5 | import io.github.itzispyder.improperui.util.misc.Pair; 6 | 7 | import java.io.*; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class Properties { 12 | 13 | private final Map properties; 14 | 15 | public Properties() { 16 | this.properties = new HashMap<>(); 17 | } 18 | 19 | public Value getProperty(Key key) { 20 | return properties.get(key.name()); 21 | } 22 | 23 | public Value getProperty(String key) { 24 | return getProperty(new Key(key)); 25 | } 26 | 27 | public void setProperty(Key key, Value value) { 28 | if (key != null && value != null) 29 | properties.put(key.name(), value); 30 | } 31 | 32 | public void setProperty(String key, String value) { 33 | setProperty(new Key(key), new Value(value)); 34 | } 35 | 36 | public boolean hasProperty(Key key) { 37 | return properties.containsKey(key); 38 | } 39 | 40 | public boolean hasProperty(String key) { 41 | return hasProperty(new Key(key)); 42 | } 43 | 44 | public void read(InputStream in) { 45 | try (in; var isr = new InputStreamReader(in); var br = new BufferedReader(isr)) { 46 | properties.clear(); 47 | 48 | String line; 49 | while ((line = br.readLine()) != null) { 50 | var pair = callProperty(line); 51 | if (pair != null) 52 | setProperty(pair.left, pair.right); 53 | } 54 | } 55 | catch (Exception ex) { 56 | ex.printStackTrace(); 57 | } 58 | } 59 | 60 | public void write(OutputStream out) { 61 | try (out; var osw = new OutputStreamWriter(out); var bw = new BufferedWriter(osw)) { 62 | StringBuilder sb = new StringBuilder(); 63 | for (Map.Entry entry : properties.entrySet()) { 64 | String line = "%s = %s".formatted(entry.getKey(), entry.getValue().getName()); 65 | sb.append(line).append('\n'); 66 | } 67 | bw.write(sb.toString()); 68 | bw.flush(); 69 | } 70 | catch (Exception ex) { 71 | ex.printStackTrace(); 72 | } 73 | } 74 | 75 | public void read(String modId, String path) { 76 | path = Paths.getConfigs(modId) + path; 77 | FileValidationUtils.validate(new File(path)); 78 | 79 | try (FileInputStream fis = new FileInputStream(path)) { 80 | read(fis); 81 | } 82 | catch (Exception ex) { 83 | ex.printStackTrace(); 84 | } 85 | } 86 | 87 | public void write(String modId, String path) { 88 | path = Paths.getConfigs(modId) + path; 89 | File file = new File(path); 90 | FileValidationUtils.validate(file); 91 | 92 | try (FileOutputStream fos = new FileOutputStream(path)) { 93 | write(fos); 94 | } 95 | catch (Exception ex) { 96 | ex.printStackTrace(); 97 | } 98 | } 99 | 100 | private Pair callProperty(String line) { 101 | String regex = "\\s*((=>)|(->)|:|=)\\s*"; 102 | String[] split = line.trim().split(regex); 103 | 104 | if (split.length < 2) 105 | return null; 106 | 107 | Key key = new Key(split[0]); 108 | Value val = new Value(line.substring(split[0].length()).replaceFirst(regex, "")); 109 | return Pair.of(key, val); 110 | } 111 | 112 | public record Key(String name) { 113 | public Key(String name) { 114 | this.name = name.trim() 115 | .replaceAll("\\s+|_", "-") 116 | .replaceAll("[^a-zA-Z0-9.-]", ""); 117 | } 118 | 119 | @Override 120 | public boolean equals(Object obj) { 121 | if (!(obj instanceof Key key)) 122 | return false; 123 | return key.name().equals(this.name()); 124 | } 125 | } 126 | 127 | public static class Value extends ScriptArgs { 128 | public Value(String name) { 129 | super(name.trim().split("\\s+")); 130 | } 131 | 132 | public String getName() { 133 | return getAll().toString(); 134 | } 135 | 136 | @Override 137 | public String getQuote(int beginIndex) { 138 | String q = super.getQuote(beginIndex); 139 | return q.matches("\\\"+") ? "" : q; 140 | } 141 | 142 | @Override 143 | public String getQuoteAndRemove(int beginIndex) { 144 | String q = super.getQuoteAndRemove(beginIndex); 145 | return q.matches("\\\"+") ? "" : q; 146 | } 147 | 148 | @Override 149 | public boolean equals(Object obj) { 150 | if (!(obj instanceof Value val)) 151 | return false; 152 | return val.getName().equals(this.getName()); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/config/PropertyCache.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.config; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class PropertyCache { 7 | 8 | private final Map cache; 9 | private final String modId; 10 | 11 | public PropertyCache(String modId) { 12 | this.cache = new HashMap<>(); 13 | this.modId = modId; 14 | } 15 | 16 | public void upload(String path, Properties properties) { 17 | if (properties == null) 18 | return; 19 | cache.put(path, properties); 20 | properties.read(modId, path); 21 | } 22 | 23 | public Properties get(String path) { 24 | if (!cache.containsKey(path)) { 25 | upload(path, new Properties()); 26 | } 27 | return cache.get(path); 28 | } 29 | 30 | public Properties.Value getProperty(ConfigKey key) { 31 | if (key == null) 32 | return null; 33 | return get(key.path).getProperty(key.key); 34 | } 35 | 36 | public void setProperty(ConfigKey key, Object value) { 37 | setProperty(key, value, false); 38 | } 39 | 40 | public void setProperty(ConfigKey key, Object value, boolean save) { 41 | if (key == null) 42 | return; 43 | get(key.path).setProperty(key.key, value.toString()); 44 | if (save) 45 | save(key); 46 | } 47 | 48 | public void save(ConfigKey key) { 49 | if (key != null) 50 | get(key.path).write(modId, key.path); 51 | } 52 | 53 | public void clear() { 54 | for (Map.Entry entry : cache.entrySet()) { 55 | entry.getValue().write(modId, entry.getKey()); 56 | } 57 | cache.clear(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/interfaces/FontManagerAccessor.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.interfaces; 2 | 3 | import net.minecraft.client.font.TextRenderer; 4 | import net.minecraft.util.Identifier; 5 | 6 | public interface FontManagerAccessor { 7 | 8 | TextRenderer createRenderer(Identifier fontId); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/mixin/MixinFontManager.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.mixin; 2 | 3 | import io.github.itzispyder.improperui.interfaces.FontManagerAccessor; 4 | import net.minecraft.client.font.FontManager; 5 | import net.minecraft.client.font.FontStorage; 6 | import net.minecraft.client.font.TextRenderer; 7 | import net.minecraft.util.Identifier; 8 | import org.spongepowered.asm.mixin.Final; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | 12 | import java.util.Map; 13 | 14 | @Mixin(FontManager.class) 15 | public abstract class MixinFontManager implements FontManagerAccessor { 16 | 17 | @Shadow @Final private Map fontStorages; 18 | @Shadow @Final private FontStorage missingStorage; 19 | 20 | @Override 21 | public TextRenderer createRenderer(Identifier fontId) { 22 | return new TextRenderer(id -> this.fontStorages.getOrDefault(fontId, this.missingStorage), false); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/mixin/MixinMinecraftClient.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.mixin; 2 | 3 | import io.github.itzispyder.improperui.client.ImproperUIClient; 4 | import io.github.itzispyder.improperui.interfaces.FontManagerAccessor; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.font.FontManager; 7 | import net.minecraft.util.Identifier; 8 | import org.spongepowered.asm.mixin.Final; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(MinecraftClient.class) 16 | public abstract class MixinMinecraftClient { 17 | 18 | @Shadow @Final private FontManager fontManager; 19 | @Shadow @Final public static Identifier UNICODE_FONT_ID; 20 | 21 | @Inject(method = "onFontOptionsChanged", at = @At("TAIL")) 22 | public void initFont(CallbackInfo ci) { 23 | var fonts = ((FontManagerAccessor)this.fontManager); 24 | ImproperUIClient.getInstance().codeRenderer = fonts.createRenderer(UNICODE_FONT_ID); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/ImproperUIPanel.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render; 2 | 3 | import io.github.itzispyder.improperui.render.math.Color; 4 | import io.github.itzispyder.improperui.script.CallbackListener; 5 | import io.github.itzispyder.improperui.script.Event; 6 | import io.github.itzispyder.improperui.script.ScriptParser; 7 | import io.github.itzispyder.improperui.util.ChatUtils; 8 | import io.github.itzispyder.improperui.util.RenderUtils; 9 | import io.github.itzispyder.improperui.util.StringUtils; 10 | import net.minecraft.client.MinecraftClient; 11 | import net.minecraft.client.gui.DrawContext; 12 | import net.minecraft.client.gui.screen.Screen; 13 | import net.minecraft.text.Text; 14 | import org.lwjgl.glfw.GLFW; 15 | 16 | import java.io.File; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | public class ImproperUIPanel extends Screen { 21 | 22 | public Element selected, focused, hovered; 23 | public boolean shiftKeyPressed, altKeyPressed, ctrlKeyPressed; 24 | public final int[] cursor; 25 | private final List children; 26 | private final List callbackListeners; 27 | 28 | private File uiScriptAsFile; 29 | private String uiScriptAsString; 30 | 31 | public ImproperUIPanel() { 32 | super(Text.of("Custom Scripted Panel Screen")); 33 | children = new ArrayList<>(); 34 | callbackListeners = new ArrayList<>(); 35 | cursor = new int[2]; 36 | } 37 | 38 | public ImproperUIPanel(List widgets, CallbackListener... callbackListeners) { 39 | this(); 40 | for (var callback : callbackListeners) 41 | this.registerCallback(callback); 42 | for (var child : widgets) 43 | this.addChild(child); 44 | } 45 | 46 | public ImproperUIPanel(String uiScript, CallbackListener... callbackListeners) { 47 | this(ScriptParser.parse(uiScript), callbackListeners); 48 | uiScriptAsString = uiScript; 49 | } 50 | 51 | public ImproperUIPanel(File uiScript, CallbackListener... callbackListeners) { 52 | this(ScriptParser.parseFile(uiScript), callbackListeners); 53 | uiScriptAsFile = uiScript; 54 | } 55 | 56 | @Override 57 | public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) { 58 | 59 | } 60 | 61 | @Override 62 | public void render(DrawContext context, int mx, int my, float delta) { 63 | if (selected != null && selected.draggable) { 64 | int dx = mx - cursor[0]; 65 | int dy = my - cursor[1]; 66 | selected.move(dx, dy); 67 | selected.boundInConstraints(); 68 | cursor[0] = mx; 69 | cursor[1] = my; 70 | } 71 | 72 | getChildren().forEach(child -> child.onRender(context, mx, my, delta)); 73 | 74 | boolean foundTarget = false; 75 | for (var child : collectOrdered()) { 76 | if (!foundTarget && child.getHitboxDimensions().contains(mx, my)) { 77 | hovered = child; 78 | foundTarget = true; 79 | } 80 | if (altKeyPressed) { 81 | var hit = child.getHitboxDimensions(); 82 | RenderUtils.drawBox(context, hit.x, hit.y, hit.width, hit.height, Color.RED.getHex()); 83 | 84 | if (ctrlKeyPressed) 85 | RenderUtils.drawLine(context, RenderUtils.width() / 2, RenderUtils.height() / 2, hit.x, hit.y, Color.BLUE.getHex()); 86 | if (selected == child) 87 | RenderUtils.drawRect(context, hit.x, hit.y, hit.width, hit.height, Color.RED.getHex()); 88 | else if (focused == child) 89 | RenderUtils.drawRect(context, hit.x, hit.y, hit.width, hit.height, Color.BLUE.getHex()); 90 | else if (hovered == child) 91 | RenderUtils.drawRect(context, hit.x, hit.y, hit.width, hit.height, Color.ORANGE.getHex()); 92 | } 93 | } 94 | } 95 | 96 | @Override 97 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 98 | int mx = (int) mouseX; 99 | int my = (int) mouseY; 100 | 101 | for (var child : collectOrdered()) { 102 | if (child.pollClickable(button, mx, my, false)) { 103 | switch (button) { 104 | case 0 -> child.onLeftClick(mx, my, false); 105 | case 1 -> child.onRightClick(mx, my, false); 106 | case 2 -> child.onMiddleClick(mx, my, false); 107 | } 108 | break; 109 | } 110 | } 111 | return true; 112 | } 113 | 114 | @Override 115 | public boolean mouseReleased(double mouseX, double mouseY, int button) { 116 | int mx = (int) mouseX; 117 | int my = (int) mouseY; 118 | 119 | for (var child : collectOrdered()) { 120 | if (child.pollClickable(button, mx, my, true)) { 121 | switch (button) { 122 | case 0 -> child.onLeftClick(mx, my, true); 123 | case 1 -> child.onRightClick(mx, my, true); 124 | case 2 -> child.onMiddleClick(mx, my, true); 125 | } 126 | break; 127 | } 128 | } 129 | selected = null; 130 | return true; 131 | } 132 | 133 | @Override 134 | public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { 135 | if (verticalAmount == 0) 136 | return false; 137 | 138 | int mx = (int) mouseX; 139 | int my = (int) mouseY; 140 | 141 | for (var child : collectOrdered()) { 142 | if (child.pollScrollable(mx, my, verticalAmount > 0)) { 143 | child.onScroll(mx, my, true); 144 | break; 145 | } 146 | } 147 | return true; 148 | } 149 | 150 | @Override 151 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 152 | if (keyCode == GLFW.GLFW_KEY_LEFT_SHIFT || keyCode == GLFW.GLFW_KEY_RIGHT_SHIFT) 153 | this.shiftKeyPressed = true; 154 | else if (keyCode == GLFW.GLFW_KEY_LEFT_ALT || keyCode == GLFW.GLFW_KEY_RIGHT_ALT) 155 | this.altKeyPressed = true; 156 | else if (keyCode == GLFW.GLFW_KEY_LEFT_CONTROL || keyCode == GLFW.GLFW_KEY_RIGHT_CONTROL) 157 | this.ctrlKeyPressed = true; 158 | 159 | super.keyPressed(keyCode, scanCode, modifiers); 160 | 161 | if (focused != null) 162 | focused.onKey(keyCode, scanCode, false); 163 | return true; 164 | } 165 | 166 | @Override 167 | public boolean keyReleased(int keyCode, int scanCode, int modifiers) { 168 | if (keyCode == GLFW.GLFW_KEY_LEFT_SHIFT || keyCode == GLFW.GLFW_KEY_RIGHT_SHIFT) 169 | this.shiftKeyPressed = false; 170 | else if (keyCode == GLFW.GLFW_KEY_LEFT_ALT || keyCode == GLFW.GLFW_KEY_RIGHT_ALT) 171 | this.altKeyPressed = false; 172 | else if (keyCode == GLFW.GLFW_KEY_LEFT_CONTROL || keyCode == GLFW.GLFW_KEY_RIGHT_CONTROL) 173 | this.ctrlKeyPressed = false; 174 | 175 | super.keyReleased(keyCode, scanCode, modifiers); 176 | 177 | if (focused != null) 178 | focused.onKey(keyCode, scanCode, true); 179 | return true; 180 | } 181 | 182 | @Override 183 | public void tick() { 184 | children.forEach(Element::onTick); 185 | } 186 | 187 | @Override 188 | public void resize(MinecraftClient client, int width, int height) { 189 | if (uiScriptAsFile != null) 190 | new ImproperUIPanel(uiScriptAsFile, callbackListeners.toArray(CallbackListener[]::new)).open(); 191 | else if (uiScriptAsString != null) 192 | new ImproperUIPanel(uiScriptAsString, callbackListeners.toArray(CallbackListener[]::new)).open(); 193 | } 194 | 195 | public void addChild(Element child) { 196 | if (child == null || child.parentPanel != null || child.parent != null || children.contains(child)) 197 | return; 198 | children.add(child); 199 | child.setParentPanel(this); 200 | } 201 | 202 | public void removeChild(Element child) { 203 | if (child == null) 204 | return; 205 | child.setParentPanel(null); 206 | children.remove(child); 207 | } 208 | 209 | public List getChildren() { 210 | return new ArrayList<>(children); 211 | } 212 | 213 | public List getChildrenOrdered() { 214 | return new ArrayList<>(getChildren().stream() 215 | .sorted(Element.ORDER) 216 | .toList()); 217 | } 218 | 219 | public void clearChildren() { 220 | var children = getChildren(); 221 | children.forEach(this::removeChild); 222 | } 223 | 224 | public Element getHoveredElement(int mx, int my) { 225 | for (Element child : collectOrdered()) 226 | if (child.getHitboxDimensions().contains(mx, my)) 227 | return child; 228 | return null; 229 | } 230 | 231 | public void registerCallback(CallbackListener listener) { 232 | if (listener != null) 233 | callbackListeners.add(listener); 234 | } 235 | 236 | public void runCallbacks(String methodName, Event event) { 237 | try { 238 | var callbacks = new ArrayList<>(callbackListeners); 239 | callbacks.forEach(callback -> callback.runCallbacks(methodName, event)); 240 | } 241 | catch (Exception ex) { 242 | ChatUtils.sendMessage(StringUtils.color("&c" + ex.getMessage())); 243 | } 244 | } 245 | 246 | public void printAll() { 247 | children.forEach(Element::printAll); 248 | } 249 | 250 | public List collect() { 251 | List list = new ArrayList<>(); 252 | for (Element child : children) { 253 | list.add(child); 254 | list.addAll(child.collect()); 255 | } 256 | return list; 257 | } 258 | 259 | public List collectOrdered() { 260 | return new ArrayList<>(collect().stream().sorted(Element.ORDER).toList()); 261 | } 262 | 263 | public List collectById(String id) { 264 | List list = new ArrayList<>(); 265 | for (Element child : collect()) 266 | if (child.getId().equals(id)) 267 | list.add(child); 268 | return list; 269 | } 270 | 271 | public Element collectFirstById(String id) { 272 | for (Element child : collect()) 273 | if (child.getId().equals(id)) 274 | return child; 275 | return null; 276 | } 277 | 278 | public List collectByClassAttribute(String classAttribute) { 279 | List list = new ArrayList<>(); 280 | for (Element child : collect()) 281 | if (child.classList.contains(classAttribute)) 282 | list.add(child); 283 | return list; 284 | } 285 | 286 | public Element collectFirstByClassAttribute(String classAttribute) { 287 | for (Element child : collect()) 288 | if (child.classList.contains(classAttribute)) 289 | return child; 290 | return null; 291 | } 292 | 293 | public void open() { 294 | var mc = MinecraftClient.getInstance(); 295 | if (mc.currentScreen != this) 296 | mc.execute(() -> mc.setScreen(this)); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/KeyHolderElement.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render; 2 | 3 | import io.github.itzispyder.improperui.config.ConfigKey; 4 | import io.github.itzispyder.improperui.config.ConfigKeyHolder; 5 | import io.github.itzispyder.improperui.config.PropertyCache; 6 | import io.github.itzispyder.improperui.script.ScriptParser; 7 | 8 | public abstract class KeyHolderElement extends Element implements ConfigKeyHolder { 9 | 10 | public abstract void onLoadKey(PropertyCache cache, ConfigKey key); 11 | public abstract void onSaveKey(PropertyCache cache, ConfigKey key); 12 | 13 | protected KeyHolderElement(int x, int y, int w, int h) { 14 | super(x, y, w, h); 15 | } 16 | 17 | protected KeyHolderElement() { 18 | this(0, 0, 0, 0); 19 | } 20 | 21 | @Override 22 | public void style() { 23 | super.style(); 24 | 25 | var key = getConfigKey(); 26 | if (key != null) 27 | onLoadKey(ScriptParser.getCache(key.modId), key); 28 | } 29 | 30 | @Override 31 | public void onLeftClick(int mx, int my, boolean release) { 32 | super.onLeftClick(mx, my, release); 33 | 34 | var key = getConfigKey(); 35 | if (key != null && release) 36 | onSaveKey(ScriptParser.getCache(key.modId), key); 37 | } 38 | 39 | @Override 40 | public void onKey(int key, int scan, boolean release) { 41 | super.onKey(key, scan, release); 42 | 43 | var configKey = getConfigKey(); 44 | if (configKey != null && release) 45 | onSaveKey(ScriptParser.getCache(configKey.modId), configKey); 46 | } 47 | 48 | @Override 49 | public ConfigKey getConfigKey() { 50 | return ELEMENT_KEY_HOLDER.apply(this); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/constants/Alignment.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.constants; 2 | 3 | public enum Alignment { 4 | 5 | LEFT, 6 | RIGHT, 7 | CENTER 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/constants/Axis.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.constants; 2 | 3 | public enum Axis { 4 | 5 | VERTICAL, 6 | HORIZONTAL, 7 | BOTH 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/constants/BackgroundClip.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.constants; 2 | 3 | public enum BackgroundClip { 4 | 5 | PADDING, 6 | MARGIN, 7 | BORDER, 8 | SELF, 9 | NONE 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/constants/ChildrenAlignment.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.constants; 2 | 3 | public enum ChildrenAlignment { 4 | 5 | GRID, 6 | NONE 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/constants/InputType.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.constants; 2 | 3 | public enum InputType { 4 | 5 | RELEASE, 6 | CLICK, 7 | HOLD, 8 | SCROLL, 9 | UNKNOWN; 10 | 11 | public static InputType of(int action) { 12 | InputType r; 13 | switch (action) { 14 | case 0 -> r = RELEASE; 15 | case 1 -> r = CLICK; 16 | case 2 -> r = HOLD; 17 | default -> r = UNKNOWN; 18 | } 19 | return r; 20 | } 21 | 22 | public boolean isRelease() { 23 | return this == RELEASE; 24 | } 25 | 26 | public boolean isClick() { 27 | return this == CLICK; 28 | } 29 | 30 | public boolean isHold() { 31 | return this == HOLD; 32 | } 33 | 34 | public boolean isUnknown() { 35 | return this == UNKNOWN; 36 | } 37 | 38 | public boolean isUp() { 39 | return this == RELEASE || this == UNKNOWN; 40 | } 41 | 42 | public boolean isDown() { 43 | return this == CLICK || this == HOLD; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/constants/Position.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.constants; 2 | 3 | public enum Position { 4 | 5 | INHERIT, // x + parent.x 6 | ABSOLUTE // x 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/constants/Visibility.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.constants; 2 | 3 | public enum Visibility { 4 | 5 | VISIBLE, 6 | INVISIBLE, 7 | ONLY_SELF, 8 | ONLY_CHILDREN 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/Button.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import io.github.itzispyder.improperui.render.Element; 4 | 5 | public class Button extends Element { 6 | 7 | public Button() { 8 | super(); 9 | queueProperty("size: 69 7"); 10 | queueProperty("inner-text: \"Button\""); 11 | queueProperty("background-color: white"); 12 | queueProperty("border-radius: 2"); 13 | queueProperty("padding: 2"); 14 | queueProperty("margin: 2"); 15 | queueProperty("text-align: center"); 16 | queueProperty("text-color: dark_gray"); 17 | queueProperty("hovered => { border-thickness: 1; border-color: dark_gray; }"); 18 | queueProperty("selected => { border-thickness: 0; background-color: gray; text-color: light_gray; }"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/CheckBox.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import io.github.itzispyder.improperui.config.ConfigKey; 4 | import io.github.itzispyder.improperui.config.PropertyCache; 5 | import io.github.itzispyder.improperui.render.KeyHolderElement; 6 | 7 | public class CheckBox extends KeyHolderElement { 8 | 9 | public CheckBox() { 10 | super(); 11 | queueProperty("text-align: center"); 12 | queueProperty("size: 10"); 13 | queueProperty("border: 1 0 white"); 14 | queueProperty("background-color: black"); 15 | } 16 | 17 | @Override 18 | public void init() { 19 | super.init(); 20 | registerProperty("active", args -> setActive(args.get(0).toBool())); 21 | } 22 | 23 | public boolean isActive() { 24 | return classList.contains("active"); 25 | } 26 | 27 | public void setActive(boolean active) { 28 | if (active) { 29 | classList.add("active"); 30 | innerText = "✔"; 31 | } 32 | else { 33 | classList.remove("active"); 34 | innerText = ""; 35 | } 36 | } 37 | 38 | @Override 39 | public void onLeftClick(int mx, int my, boolean release) { 40 | super.onLeftClick(mx, my, release); 41 | if (!release) 42 | setActive(!isActive()); 43 | } 44 | 45 | @Override 46 | public void onLoadKey(PropertyCache cache, ConfigKey key) { 47 | var property = cache.getProperty(key); 48 | if (property != null) 49 | setActive(property.get(0).toBool()); 50 | } 51 | 52 | @Override 53 | public void onSaveKey(PropertyCache cache, ConfigKey key) { 54 | cache.setProperty(key, isActive(), true); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/Header.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | public class Header extends Label { 4 | 5 | public Header(float textScale) { 6 | super(); 7 | queueProperty("inner-text-prefix: \"&l\""); 8 | queueProperty("text-scale: " + textScale); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/HyperLink.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import io.github.itzispyder.improperui.render.Element; 4 | import net.minecraft.util.Util; 5 | 6 | public class HyperLink extends Element { 7 | 8 | public String link; 9 | 10 | public HyperLink() { 11 | super(); 12 | queueProperty("text-color: aqua"); 13 | queueProperty("height: 7"); 14 | queueProperty("background-color: #00000000"); 15 | queueProperty("hovered => { inner-text-prefix: \"&b&n\" }"); 16 | queueProperty("selected => { inner-text-prefix: \"&3&n\" }"); 17 | queueProperty("focused => { inner-text-prefix: \"&5&n\" }"); 18 | } 19 | 20 | @Override 21 | public void init() { 22 | super.init(); 23 | registerProperty("link", args -> link = args.get(0).toString()); 24 | registerProperty("url", args -> link = args.get(0).toString()); 25 | registerProperty("href", args -> link = args.get(0).toString()); 26 | } 27 | 28 | @Override 29 | public void style() { 30 | super.style(); 31 | innerText = innerText == null ? link : innerText; 32 | width = mc.textRenderer.getWidth(innerText == null ? "" : innerText); 33 | 34 | if (selectStyle != null) 35 | selectStyle.innerText = innerText; 36 | if (hoverStyle != null) 37 | hoverStyle.innerText = innerText; 38 | if (focusStyle != null) 39 | focusStyle.innerText = innerText; 40 | } 41 | 42 | @Override 43 | public void onLeftClick(int mx, int my, boolean release) { 44 | super.onLeftClick(mx, my, release); 45 | if (link != null && !link.trim().isEmpty() && !release) { 46 | Util.getOperatingSystem().open(link); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/Label.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import io.github.itzispyder.improperui.render.Element; 4 | import io.github.itzispyder.improperui.util.StringUtils; 5 | import net.minecraft.client.gui.DrawContext; 6 | 7 | public class Label extends Element { 8 | 9 | public Label() { 10 | super(); 11 | queueProperty("inner-text: \"Empty Label\""); 12 | queueProperty("padding: 2"); 13 | queueProperty("background-color: none"); 14 | } 15 | 16 | @Override 17 | public void init() { 18 | super.init(); 19 | 20 | registerProperty("inner-text", args -> { 21 | innerText = StringUtils.color(args.getQuoteAndRemove()); 22 | updateDimensions(); 23 | }); 24 | registerProperty("inner-text-prefix", args -> { 25 | innerTextPrefix = StringUtils.color(args.getQuoteAndRemove()); 26 | updateDimensions(); 27 | }); 28 | registerProperty("inner-text-suffix", args -> { 29 | innerTextSuffix = StringUtils.color(args.getQuoteAndRemove()); 30 | updateDimensions(); 31 | }); 32 | } 33 | 34 | @Override 35 | public void onRender(DrawContext context, int mx, int my, float delta) { 36 | super.onRender(context, mx, my, delta); 37 | updateDimensions(); 38 | } 39 | 40 | private void updateDimensions() { 41 | var text = getText(); 42 | if (mc != null && mc.textRenderer != null && text != null) { 43 | width = (int)(mc.textRenderer.getWidth(text) * textScale); 44 | height = (int)(mc.textRenderer.fontHeight * textScale); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/Positionable.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import io.github.itzispyder.improperui.config.ConfigKey; 4 | import io.github.itzispyder.improperui.config.PropertyCache; 5 | import io.github.itzispyder.improperui.render.KeyHolderElement; 6 | 7 | public class Positionable extends KeyHolderElement { 8 | 9 | public Positionable() { 10 | super(); 11 | queueProperty("size: 100 200"); 12 | queueProperty("border-radius: 5"); 13 | queueProperty("shadow-distance: 5"); 14 | queueProperty("background-color: #80FFFFFF"); 15 | queueProperty("draggable: true"); 16 | } 17 | 18 | @Override 19 | public void style() { 20 | if (getId() == null) 21 | throw new IllegalStateException("a Positionable element cannot have a null ID!"); 22 | if (super.getConfigKey() == null) 23 | throw new IllegalStateException("a Positionable element needs to have a ConfigKey attribute \"-modid:config.properties:keyname\""); 24 | super.style(); 25 | } 26 | 27 | @Override 28 | public void onLoadKey(PropertyCache cache, ConfigKey key) { 29 | var property = cache.getProperty(key); 30 | if (property != null) 31 | moveTo(property.get(0).toInt(), property.get(1).toInt()); 32 | } 33 | 34 | @Override 35 | public void onSaveKey(PropertyCache cache, ConfigKey key) { 36 | cache.setProperty(key, "%s %s".formatted(x, y), true); 37 | } 38 | 39 | @Override 40 | public ConfigKey getConfigKey() { 41 | var key = super.getConfigKey(); 42 | return new ConfigKey(key.modId, key.path, "improperui.elements.positionable.%s.%s".formatted(getId(), key.key)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/Radio.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import io.github.itzispyder.improperui.config.ConfigKey; 4 | import io.github.itzispyder.improperui.config.PropertyCache; 5 | import io.github.itzispyder.improperui.render.KeyHolderElement; 6 | import io.github.itzispyder.improperui.render.math.Color; 7 | import io.github.itzispyder.improperui.script.ScriptParser; 8 | 9 | public class Radio extends KeyHolderElement { 10 | 11 | private Color radioFill; 12 | 13 | public Radio() { 14 | super(); 15 | queueProperty("size: 6"); 16 | queueProperty("background-color: white"); 17 | queueProperty("border: 2 360 black"); 18 | queueProperty("shadow: 1 white"); 19 | queueProperty("shadow-fade-color: white"); 20 | queueProperty("margin: 2"); 21 | } 22 | 23 | @Override 24 | public void init() { 25 | super.init(); 26 | registerProperty("active", args -> setActive(args.get(0).toBool(), false)); 27 | registerProperty("fill-color", args -> fillColor = radioFill = args.get(0).toColor()); 28 | registerProperty("background-color", args -> fillColor = radioFill = args.get(0).toColor()); 29 | } 30 | 31 | public boolean isActive() { 32 | return classList.contains("active"); 33 | } 34 | 35 | public void setActive(boolean active) { 36 | setActive(active, true); 37 | } 38 | 39 | public void setActive(boolean active, boolean deep) { 40 | if (active) { 41 | if (parent != null && deep) 42 | for (var child : parent.getChildren()) 43 | if (child instanceof Radio radio) 44 | radio.setActive(false); 45 | classList.add("active"); 46 | fillColor = radioFill; 47 | } 48 | else { 49 | classList.remove("active"); 50 | fillColor = borderColor; 51 | } 52 | 53 | var key = getConfigKey(); 54 | if (key != null) 55 | onSaveKey(ScriptParser.getCache(key.modId), key); 56 | } 57 | 58 | @Override 59 | public void onLeftClick(int mx, int my, boolean release) { 60 | if (!release) 61 | setActive(!isActive()); 62 | } 63 | 64 | @Override 65 | public void onLoadKey(PropertyCache cache, ConfigKey key) { 66 | var property = cache.getProperty(key); 67 | setActive(property != null && property.get(0).toBool(), true); 68 | } 69 | 70 | @Override 71 | public void style() { 72 | super.style(); 73 | if (getConfigKey() == null) 74 | setActive(isActive(), false); // fix for defaulting to active 75 | } 76 | 77 | @Override 78 | public void onSaveKey(PropertyCache cache, ConfigKey key) { 79 | cache.setProperty(key, isActive(), true); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/Slider.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import io.github.itzispyder.improperui.config.ConfigKey; 5 | import io.github.itzispyder.improperui.config.PropertyCache; 6 | import io.github.itzispyder.improperui.render.KeyHolderElement; 7 | import io.github.itzispyder.improperui.script.ScriptParser; 8 | import io.github.itzispyder.improperui.util.MathUtils; 9 | import io.github.itzispyder.improperui.util.RenderUtils; 10 | import net.minecraft.client.gui.DrawContext; 11 | 12 | import static io.github.itzispyder.improperui.util.RenderUtils.drawText; 13 | import static io.github.itzispyder.improperui.util.RenderUtils.fillRect; 14 | 15 | public class Slider extends KeyHolderElement { 16 | 17 | public double min, max, val; 18 | public int decimalPlaces; 19 | 20 | private int fillEnd; 21 | 22 | public Slider() { 23 | super(); 24 | queueProperty("size: 100 17"); 25 | queueProperty("range: 0 10"); 26 | queueProperty("value: 10"); 27 | queueProperty("decimal-places: 1"); 28 | queueProperty("border-radius: 360"); 29 | queueProperty("background-color: white"); 30 | queueProperty("inner-text: \"Slider\""); 31 | this.fillEnd = x + width; 32 | } 33 | 34 | @Override 35 | public void init() { 36 | super.init(); 37 | registerProperty("min", args -> min = args.get(0).toDouble()); 38 | registerProperty("minimum", args -> min = args.get(0).toDouble()); 39 | registerProperty("max", args -> max = args.get(0).toDouble()); 40 | registerProperty("maximum", args -> max = args.get(0).toDouble()); 41 | registerProperty("val", args -> val = args.get(0).toDouble()); 42 | registerProperty("value", args -> val = args.get(0).toDouble()); 43 | registerProperty("decimal-places", args -> decimalPlaces = args.get(0).toInt()); 44 | registerProperty("range", args -> { 45 | min = args.get(0).toDouble(); 46 | max = args.get(1).toDouble(); 47 | }); 48 | } 49 | 50 | @Override 51 | public void onRender(DrawContext context, int mx, int my, float delta) { 52 | int x = getPosX() + marginLeft - paddingLeft; 53 | int y = getPosY() + marginTop - paddingTop; 54 | 55 | boolean notOpaque = opacity < 1.0F; 56 | if (notOpaque) 57 | RenderSystem.setShaderColor(1, 1, 1, opacity); 58 | 59 | if (parentPanel != null && parentPanel.selected == this) { 60 | this.fillEnd = MathUtils.clamp(mx, x, x + width); 61 | double range = max - min; 62 | double ratio = (double)(fillEnd - x) / (double)width; 63 | double value = range * ratio; 64 | val = MathUtils.round(value + min, decimalPlaces); 65 | 66 | var configKey = getConfigKey(); 67 | if (configKey != null) 68 | ScriptParser.getCache(configKey.modId).setProperty(configKey, val, true); 69 | } 70 | 71 | double range = max - min; 72 | double value = val - min; 73 | double ratio = value / range; 74 | int len = (int)(width * ratio); 75 | this.fillEnd = x + len; 76 | 77 | val = MathUtils.round(range * ratio + min, decimalPlaces); 78 | 79 | String text = "(%s)".formatted(val); 80 | drawText(context, text, x + width + 10, y + (height - 7) / 2, 0.9F, false); 81 | fillRect(context, x, y + height / 2 - 1, width, 2, borderColor.getHex()); 82 | fillRect(context, x, y + height / 2 - 1, len, 2, fillColor.getHex()); 83 | 84 | x = fillEnd - (height / 2 + paddingLeft + paddingRight) / 2; 85 | y = y + (height - (height / 2 + paddingTop + paddingBottom)) / 2; 86 | 87 | RenderUtils.fillRoundShadow(context, 88 | x - borderThickness, 89 | y - borderThickness, 90 | height / 2 + paddingLeft + paddingRight + borderThickness * 2, 91 | height / 2 + paddingTop + paddingBottom + borderThickness * 2, 92 | borderRadius, 93 | shadowDistance, 94 | shadowColor.getHex(), 95 | shadowColor.getHexCustomAlpha(0) 96 | ); 97 | RenderUtils.fillRoundShadow(context, x, y, 98 | height / 2 + paddingLeft + paddingRight, 99 | height / 2 + paddingTop + paddingBottom, 100 | borderRadius, 101 | borderThickness, 102 | borderColor.getHex(), 103 | borderColor.getHex() 104 | ); 105 | RenderUtils.fillRoundRect(context, x, y, 106 | height / 2 + paddingLeft + paddingRight, 107 | height / 2 + paddingTop + paddingBottom, 108 | borderRadius, 109 | fillColor.getHex() 110 | ); 111 | if (backgroundImage != null) { 112 | RenderUtils.drawRoundTexture(context, 113 | backgroundImage, x, y, 114 | height / 2 + paddingLeft + paddingRight, 115 | height / 2 + paddingTop + paddingBottom, 116 | borderRadius 117 | ); 118 | } 119 | 120 | if (notOpaque) 121 | RenderSystem.setShaderColor(1, 1, 1, 1); 122 | } 123 | 124 | @Override 125 | public void onLoadKey(PropertyCache cache, ConfigKey key) { 126 | var property = cache.getProperty(key); 127 | if (property != null) 128 | val = property.get(0).toDouble(); 129 | } 130 | 131 | @Override 132 | public void onSaveKey(PropertyCache cache, ConfigKey key) { 133 | cache.setProperty(key, val, true); 134 | } 135 | } -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/TextBox.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import io.github.itzispyder.improperui.config.ConfigKey; 5 | import io.github.itzispyder.improperui.config.PropertyCache; 6 | import io.github.itzispyder.improperui.render.KeyHolderElement; 7 | import io.github.itzispyder.improperui.render.constants.BackgroundClip; 8 | import io.github.itzispyder.improperui.render.constants.Visibility; 9 | import io.github.itzispyder.improperui.render.math.Color; 10 | import io.github.itzispyder.improperui.render.math.Dimensions; 11 | import io.github.itzispyder.improperui.util.RenderUtils; 12 | import io.github.itzispyder.improperui.util.StringUtils; 13 | import net.minecraft.client.gui.DrawContext; 14 | import net.minecraft.text.Text; 15 | import net.minecraft.util.math.RotationAxis; 16 | import org.lwjgl.glfw.GLFW; 17 | 18 | import java.util.function.Function; 19 | 20 | public class TextBox extends KeyHolderElement { 21 | 22 | public String defaultText, pattern; 23 | private boolean selectionBlinking; 24 | private int selectionBlink; 25 | 26 | public TextBox() { 27 | super(); 28 | queueProperty("size: 90 12"); 29 | queueProperty("border: 1 0 white"); 30 | queueProperty("background-color: dark_gray"); 31 | innerText = ""; 32 | } 33 | 34 | @Override 35 | public void init() { 36 | super.init(); 37 | registerProperty("default-text", args -> defaultText = args.getQuote()); 38 | registerProperty("placeholder", args -> defaultText = args.getQuote()); 39 | registerProperty("text-pattern", args -> pattern = args.getQuote()); 40 | registerProperty("text-format", args -> pattern = args.getQuote()); 41 | registerProperty("text-mask", args -> pattern = args.getQuote()); 42 | registerProperty("input-pattern", args -> pattern = args.getQuote()); 43 | registerProperty("input-format", args -> pattern = args.getQuote()); 44 | registerProperty("input-mask", args -> pattern = args.getQuote()); 45 | registerProperty("pattern", args -> pattern = args.getQuote()); 46 | registerProperty("format", args -> pattern = args.getQuote()); 47 | registerProperty("mask", args -> pattern = args.getQuote()); 48 | } 49 | 50 | @Override 51 | public void onRender(DrawContext context, int mx, int my, float delta) { 52 | int x = getPosX(); 53 | int y = getPosY(); 54 | 55 | if (visibility == Visibility.INVISIBLE) 56 | return; 57 | 58 | context.getMatrices().push(); 59 | int cx = x + width / 2; 60 | int cy = y + height / 2; 61 | context.getMatrices().multiply(RotationAxis.POSITIVE_X.rotationDegrees(rotateX), cx, cy, 0); 62 | context.getMatrices().multiply(RotationAxis.POSITIVE_Y.rotationDegrees(rotateY), cx, cy, 0); 63 | context.getMatrices().multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotateZ), cx, cy, 0); 64 | 65 | if (visibility != Visibility.ONLY_CHILDREN) { 66 | boolean notOpaque = opacity < 1.0F; 67 | boolean focused = parentPanel != null && parentPanel.focused == this; 68 | if (notOpaque) 69 | RenderSystem.setShaderColor(1, 1, 1, opacity); 70 | 71 | RenderUtils.fillRoundShadow(context, 72 | x + marginLeft - paddingLeft - borderThickness, 73 | y + marginTop - paddingTop - borderThickness, 74 | width + paddingLeft + paddingRight + borderThickness * 2, 75 | height + paddingTop + paddingBottom + borderThickness * 2, 76 | borderRadius, 77 | shadowDistance, 78 | shadowColor.getHex(), 79 | shadowColor.getHexCustomAlpha(0) 80 | ); 81 | RenderUtils.fillRoundShadow(context, 82 | x + marginLeft - paddingLeft, 83 | y + marginTop - paddingTop, 84 | width + paddingLeft + paddingRight, 85 | height + paddingTop + paddingBottom, 86 | borderRadius, 87 | borderThickness, 88 | focused ? borderColor.getHex() : borderColor.darker().getHex(), 89 | focused ? borderColor.getHex() : borderColor.darker().getHex() 90 | ); 91 | RenderUtils.fillRoundRect(context, 92 | x + marginLeft - paddingLeft, 93 | y + marginTop - paddingTop, 94 | width + paddingLeft + paddingRight, 95 | height + paddingTop + paddingBottom, 96 | borderRadius, 97 | focused ? fillColor.getHex() : fillColor.darker().getHex() 98 | ); 99 | if (backgroundImage != null) { 100 | RenderUtils.drawRoundTexture(context, 101 | backgroundImage, 102 | x + marginLeft - paddingLeft, 103 | y + marginTop - paddingTop, 104 | width + paddingLeft + paddingRight, 105 | height + paddingTop + paddingBottom, 106 | borderRadius 107 | ); 108 | } 109 | 110 | if (parentPanel != null) { 111 | String text = innerText != null ? innerText : ""; 112 | while (!text.isEmpty() && mc.textRenderer.getWidth(text) * 0.9F > width - height - 4) { 113 | text = text.substring(1); 114 | } 115 | 116 | Text display = Text.of(text); 117 | if (!queryMatchesPattern()) 118 | RenderUtils.drawDefaultScaledText(context, display, x + height / 2 + 2, y + height / 3, 0.9F, false, Color.ORANGE.getHex()); 119 | else if (parentPanel.focused == this && !text.isEmpty()) 120 | RenderUtils.drawDefaultScaledText(context, display, x + height / 2 + 2, y + height / 3, 0.9F, false, textColor.getHex()); 121 | else if (!text.isEmpty()) 122 | RenderUtils.drawDefaultScaledText(context, display, x + height / 2 + 2, y + height / 3, 0.9F, false, textColor.darker().darker().getHex()); 123 | else 124 | RenderUtils.drawDefaultScaledText(context, Text.of(getDefaultText()), x + height / 2 + 2, y + height / 3, 0.9F, false, textColor.darker().darker().getHex()); 125 | 126 | if (selectionBlinking) { 127 | int tx = (int)(x + height / 2 + 2 + mc.textRenderer.getWidth(text) * 0.9); 128 | int ty = y + 2; 129 | RenderUtils.drawVerLine(context, tx, ty, height - 4, 0xE0FFFFFF); 130 | } 131 | } 132 | 133 | if (notOpaque) 134 | RenderSystem.setShaderColor(1, 1, 1, 1); 135 | } 136 | 137 | if (visibility != Visibility.ONLY_SELF) { 138 | boolean shouldClip = backgroundClip != BackgroundClip.NONE; 139 | 140 | if (shouldClip) { 141 | Dimensions shape; 142 | switch (backgroundClip) { 143 | case PADDING -> shape = getPaddedDimensions(); 144 | case BORDER -> shape = getBorderedDimensions(); 145 | case MARGIN -> shape = getMarginalDimensions(); 146 | default -> shape = getDimensions(); 147 | } 148 | context.enableScissor(shape.x, shape.y, shape.x + shape.width, shape.y + shape.height); 149 | } 150 | 151 | onRenderChildren(context, mx, my, delta); 152 | 153 | if (shouldClip) 154 | context.disableScissor(); 155 | } 156 | 157 | context.getMatrices().pop(); 158 | } 159 | 160 | @Override 161 | public void onKey(int key, int scan, boolean release) { 162 | if (parentPanel != null && !release) { 163 | String typed = GLFW.glfwGetKeyName(key, scan); 164 | 165 | if (key == GLFW.GLFW_KEY_ESCAPE) { 166 | parentPanel.focused = null; 167 | } 168 | else if (key == GLFW.GLFW_KEY_BACKSPACE) { 169 | onInput(input -> { 170 | if (!input.isEmpty()) { 171 | return input.substring(0, input.length() - 1); 172 | } 173 | return input; 174 | }, false); 175 | } 176 | else if (key == GLFW.GLFW_KEY_SPACE) { 177 | onInput(input -> input.concat(" "), true); 178 | } 179 | else if (key == GLFW.GLFW_KEY_V && parentPanel.ctrlKeyPressed) { 180 | onInput(input -> input.concat(mc.keyboard.getClipboard()), true); 181 | } 182 | else if (typed != null){ 183 | onInput(input -> input.concat(parentPanel.shiftKeyPressed ? StringUtils.keyPressWithShift(typed) : typed), true); 184 | } 185 | } 186 | } 187 | 188 | public void onInput(Function factory, boolean append) { 189 | innerText = factory.apply(innerText != null ? innerText : ""); 190 | } 191 | 192 | @Override 193 | public void onTick() { 194 | super.onTick(); 195 | 196 | if (parentPanel != null) { 197 | if (parentPanel.focused != this) { 198 | selectionBlinking = false; 199 | return; 200 | } 201 | 202 | if (selectionBlink++ >= 20) { 203 | selectionBlink = 0; 204 | } 205 | if (selectionBlink % 10 == 0 && selectionBlink > 0) { 206 | selectionBlinking = !selectionBlinking; 207 | } 208 | } 209 | } 210 | 211 | @Override 212 | public void onLoadKey(PropertyCache cache, ConfigKey key) { 213 | var property = cache.getProperty(key); 214 | if (property != null) 215 | innerText = property.getQuote(); 216 | } 217 | 218 | @Override 219 | public void onSaveKey(PropertyCache cache, ConfigKey key) { 220 | cache.setProperty(key, "\"%s\"".formatted(innerText), true); 221 | } 222 | 223 | public String getQuery() { 224 | return innerText; 225 | } 226 | 227 | public String getLowercaseQuery() { 228 | return innerText.toLowerCase(); 229 | } 230 | 231 | public void setQuery(String query) { 232 | this.innerText = query; 233 | } 234 | 235 | public String getDefaultText() { 236 | return defaultText == null ? "" : defaultText; 237 | } 238 | 239 | public void setDefaultText(String defaultText) { 240 | this.defaultText = defaultText; 241 | } 242 | 243 | public String getPattern() { 244 | return pattern; 245 | } 246 | 247 | public void setPattern(String pattern) { 248 | this.pattern = pattern; 249 | } 250 | 251 | public boolean queryMatchesPattern() { 252 | return pattern == null || innerText.matches(pattern); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/elements/TextField.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.elements; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import io.github.itzispyder.improperui.config.ConfigKey; 5 | import io.github.itzispyder.improperui.config.PropertyCache; 6 | import io.github.itzispyder.improperui.render.Element; 7 | import io.github.itzispyder.improperui.render.KeyHolderElement; 8 | import io.github.itzispyder.improperui.render.constants.BackgroundClip; 9 | import io.github.itzispyder.improperui.render.constants.Visibility; 10 | import io.github.itzispyder.improperui.render.math.Dimensions; 11 | import io.github.itzispyder.improperui.script.ScriptParser; 12 | import io.github.itzispyder.improperui.util.MathUtils; 13 | import io.github.itzispyder.improperui.util.RenderUtils; 14 | import io.github.itzispyder.improperui.util.StringUtils; 15 | import net.minecraft.client.gui.DrawContext; 16 | import net.minecraft.util.math.RotationAxis; 17 | import org.lwjgl.glfw.GLFW; 18 | 19 | import java.awt.*; 20 | import java.util.ArrayList; 21 | import java.util.function.Function; 22 | 23 | import static io.github.itzispyder.improperui.util.RenderUtils.*; 24 | 25 | public class TextField extends KeyHolderElement { 26 | 27 | public static final int CHAR_W = 4, CHAR_H = 6; 28 | private int limitW, limitH; 29 | private HistoryQueue editHistory; 30 | private CharacterElement[][] chars; 31 | private Point selStart, selEnd; 32 | private int cursor, selectionBlink; 33 | private boolean selectionBlinking, mouseDown; 34 | 35 | public TextField(String innerText, int x, int y, int w, int h) { 36 | super(x, y, Math.max(w, 50), Math.max(h, 18)); 37 | int addW = w % CHAR_W == 0 ? 0 : 1; 38 | int addH = h % CHAR_H == 0 ? 0 : 1; 39 | this.limitW = (int)(Math.floor(w / (double) CHAR_W) + addW) - 2; 40 | this.limitH = (int)(Math.floor(h / (double) CHAR_H) + addH) - 2; 41 | this.width = (limitW + 2) * CHAR_W; 42 | this.height = (limitH + 2) * CHAR_H; 43 | this.innerText = innerText; 44 | this.chars = new CharacterElement[limitW][limitH]; 45 | this.updateInnerText(); 46 | this.selStart = new Point(); 47 | this.selEnd = new Point(); 48 | this.cursor = -1; 49 | this.mouseDown = false; 50 | this.editHistory = new HistoryQueue(100); 51 | 52 | queueProperty("inner-text: %s".formatted(innerText)); 53 | queueProperty("size: %s %s".formatted(this.width, this.height)); 54 | queueProperty("border: 1 0 white"); 55 | queueProperty("background-color: dark_gray"); 56 | } 57 | 58 | public TextField() { 59 | this("", 0, 0, 100, 18); 60 | } 61 | 62 | @Override 63 | public void style() { 64 | super.style(); 65 | int addW = width % CHAR_W == 0 ? 0 : 1; 66 | int addH = height % CHAR_H == 0 ? 0 : 1; 67 | this.limitW = (int)(Math.floor(width / (double) CHAR_W) + addW) - 2; 68 | this.limitH = (int)(Math.floor(height / (double) CHAR_H) + addH) - 2; 69 | this.width = (limitW + 2) * CHAR_W; 70 | this.height = (limitH + 2) * CHAR_H; 71 | this.chars = new CharacterElement[limitW][limitH]; 72 | this.updateInnerText(); 73 | this.selStart = new Point(); 74 | this.selEnd = new Point(); 75 | this.cursor = -1; 76 | this.mouseDown = false; 77 | this.editHistory = new HistoryQueue(100); 78 | } 79 | 80 | @Override 81 | public void onRender(DrawContext context, int mx, int my, float delta) { 82 | int x = getPosX(); 83 | int y = getPosY(); 84 | 85 | if (visibility == Visibility.INVISIBLE) 86 | return; 87 | 88 | context.getMatrices().push(); 89 | int cx = x + width / 2; 90 | int cy = y + height / 2; 91 | context.getMatrices().multiply(RotationAxis.POSITIVE_X.rotationDegrees(rotateX), cx, cy, 0); 92 | context.getMatrices().multiply(RotationAxis.POSITIVE_Y.rotationDegrees(rotateY), cx, cy, 0); 93 | context.getMatrices().multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotateZ), cx, cy, 0); 94 | 95 | if (visibility != Visibility.ONLY_CHILDREN) { 96 | boolean notOpaque = opacity < 1.0F; 97 | boolean focused = parentPanel != null && parentPanel.focused == this; 98 | if (notOpaque) 99 | RenderSystem.setShaderColor(1, 1, 1, opacity); 100 | 101 | RenderUtils.fillRoundShadow(context, 102 | x + marginLeft - paddingLeft - borderThickness, 103 | y + marginTop - paddingTop - borderThickness, 104 | width + paddingLeft + paddingRight + borderThickness * 2, 105 | height + paddingTop + paddingBottom + borderThickness * 2, 106 | borderRadius, 107 | shadowDistance, 108 | shadowColor.getHex(), 109 | shadowColor.getHexCustomAlpha(0) 110 | ); 111 | RenderUtils.fillRoundShadow(context, 112 | x + marginLeft - paddingLeft, 113 | y + marginTop - paddingTop, 114 | width + paddingLeft + paddingRight, 115 | height + paddingTop + paddingBottom, 116 | borderRadius, 117 | borderThickness, 118 | focused ? borderColor.getHex() : borderColor.darker().getHex(), 119 | focused ? borderColor.getHex() : borderColor.darker().getHex() 120 | ); 121 | RenderUtils.fillRoundRect(context, 122 | x + marginLeft - paddingLeft, 123 | y + marginTop - paddingTop, 124 | width + paddingLeft + paddingRight, 125 | height + paddingTop + paddingBottom, 126 | borderRadius, 127 | focused ? fillColor.getHex() : fillColor.darker().getHex() 128 | ); 129 | if (backgroundImage != null) { 130 | RenderUtils.drawRoundTexture(context, 131 | backgroundImage, 132 | x + marginLeft - paddingLeft, 133 | y + marginTop - paddingTop, 134 | width + paddingLeft + paddingRight, 135 | height + paddingTop + paddingBottom, 136 | borderRadius 137 | ); 138 | } 139 | 140 | if (notOpaque) 141 | RenderSystem.setShaderColor(1, 1, 1, 1); 142 | } 143 | 144 | if (visibility != Visibility.ONLY_SELF) { 145 | boolean shouldClip = backgroundClip != BackgroundClip.NONE; 146 | 147 | if (shouldClip) { 148 | Dimensions shape; 149 | switch (backgroundClip) { 150 | case PADDING -> shape = getPaddedDimensions(); 151 | case BORDER -> shape = getBorderedDimensions(); 152 | case MARGIN -> shape = getMarginalDimensions(); 153 | default -> shape = getDimensions(); 154 | } 155 | context.enableScissor(shape.x, shape.y, shape.x + shape.width, shape.y + shape.height); 156 | } 157 | 158 | onRenderChildren(context, mx, my, delta); 159 | 160 | if (shouldClip) 161 | context.disableScissor(); 162 | } 163 | 164 | context.getMatrices().pop(); 165 | 166 | if (mouseDown) 167 | pollMouseSelection(mx, my); 168 | } 169 | 170 | @Override 171 | public void onKey(int key, int scan, boolean release) { 172 | if (parentPanel != null && !release) { 173 | String typed = GLFW.glfwGetKeyName(key, scan); 174 | 175 | if (key == GLFW.GLFW_KEY_ESCAPE) { 176 | if (isSelecting()) 177 | updateCursor(); 178 | else 179 | parentPanel.focused = null; 180 | } 181 | else if (key == GLFW.GLFW_KEY_A && parentPanel.ctrlKeyPressed) { 182 | cursor = -1; 183 | selStart.setLocation(0, 0); 184 | selEnd.setLocation(limitW, limitH); 185 | } 186 | else if (key == GLFW.GLFW_KEY_BACKSPACE) { 187 | editHistory.push(); 188 | if (isSelecting()) { 189 | deleteSelectedText(); 190 | setCursor(cursor - 1); 191 | return; 192 | } 193 | onInput(input -> StringUtils.insertString(innerText, cursor + 1, null), false); 194 | } 195 | else if (key == GLFW.GLFW_KEY_DELETE) { 196 | editHistory.push(); 197 | if (isSelecting()) { 198 | deleteSelectedText(); 199 | setCursor(cursor - 1); 200 | return; 201 | } 202 | onInput(input -> StringUtils.insertString(innerText, cursor + 2, null), false); 203 | setCursor(cursor + 1); 204 | } 205 | else if (key == GLFW.GLFW_KEY_SPACE) { 206 | editHistory.push(); 207 | onInput(input -> StringUtils.insertString(innerText, cursor + 1, " "), true); 208 | } 209 | else if (key == GLFW.GLFW_KEY_V && parentPanel.ctrlKeyPressed) { 210 | editHistory.push(); 211 | String s = mc.keyboard.getClipboard().replace('\n', ' '); 212 | boolean sel = isSelecting(); 213 | int len = s.length(); 214 | 215 | if (sel) { 216 | for (int i = len - 1; i >= 0; i--) { 217 | char c = s.charAt(i); 218 | onInput(input -> { 219 | setCursor(cursor - 1); 220 | return StringUtils.insertString(innerText, cursor + 1, String.valueOf(c)); 221 | }, true); 222 | } 223 | } 224 | else { 225 | for (char c : s.toCharArray()) 226 | onInput(input -> StringUtils.insertString(innerText, cursor + 1, String.valueOf(c)), true); 227 | } 228 | } 229 | else if (key == GLFW.GLFW_KEY_C && parentPanel.ctrlKeyPressed) { 230 | mc.keyboard.setClipboard(getSelectedText()); 231 | } 232 | else if (key == GLFW.GLFW_KEY_Z && parentPanel.ctrlKeyPressed) { 233 | editHistory.revertLastEdit(); 234 | } 235 | else if (key == GLFW.GLFW_KEY_LEFT) { 236 | setCursor(cursor - 1); 237 | if (parentPanel.shiftKeyPressed) 238 | selEnd.setLocation(getCursor()); 239 | else 240 | updateCursor(); 241 | } 242 | else if (key == GLFW.GLFW_KEY_RIGHT) { 243 | setCursor(cursor + 1); 244 | if (parentPanel.shiftKeyPressed) 245 | selEnd.setLocation(getCursor()); 246 | else 247 | updateCursor(); 248 | } 249 | else if (key == GLFW.GLFW_KEY_UP) { 250 | var c = getCursor(); 251 | setCursor(c.x - 1, c.y - 1); 252 | if (parentPanel.shiftKeyPressed) 253 | selEnd.setLocation(getCursor()); 254 | else 255 | updateCursor(); 256 | } 257 | else if (key == GLFW.GLFW_KEY_DOWN) { 258 | var c = getCursor(); 259 | setCursor(c.x - 1, c.y + 1); 260 | if (parentPanel.shiftKeyPressed) 261 | selEnd.setLocation(getCursor()); 262 | else 263 | updateCursor(); 264 | } 265 | else if (typed != null) { 266 | editHistory.push(); 267 | String s = parentPanel.shiftKeyPressed ? StringUtils.keyPressWithShift(typed) : typed; 268 | boolean sel = isSelecting(); 269 | 270 | onInput(input -> { 271 | if (sel) 272 | setCursor(cursor - 1); 273 | return StringUtils.insertString(innerText, cursor + 1, s); 274 | }, true); 275 | } 276 | } 277 | 278 | var configKey = getConfigKey(); 279 | if (configKey != null && release) 280 | onSaveKey(ScriptParser.getCache(configKey.modId), configKey); 281 | } 282 | 283 | public void onInput(Function factory, boolean append) { 284 | if (cursor + 1 >= limitW * limitH && append) 285 | return; 286 | 287 | deleteSelectedText(); 288 | String typed = factory.apply(innerText); 289 | if (typed.length() > limitW * limitH && append) 290 | return; 291 | 292 | innerText = typed; 293 | updateInnerText(); 294 | cursor += append ? 1 : -1; 295 | cursor = MathUtils.clamp(cursor, -1, limitW * limitH - 1); 296 | updateCursor(); 297 | } 298 | 299 | 300 | @Override 301 | public void onLeftClick(int mx, int my, boolean release) { 302 | super.onLeftClick(mx, my, release); 303 | this.mouseDown = !release; 304 | 305 | for (Element child : getChildren()) { 306 | if (child.getHitboxDimensions().contains(mx, my)) { 307 | child.onLeftClick(mx, my, release); 308 | break; 309 | } 310 | } 311 | } 312 | 313 | @Override 314 | public void onLoadKey(PropertyCache cache, ConfigKey key) { 315 | var property = cache.getProperty(key); 316 | if (property != null) 317 | innerText = property.getQuote(); 318 | } 319 | 320 | @Override 321 | public void onSaveKey(PropertyCache cache, ConfigKey key) { 322 | cache.setProperty(key, "\"%s\"".formatted(innerText), true); 323 | } 324 | 325 | private void pollMouseSelection(int mx, int my) { 326 | CharacterElement ch = null; 327 | for (int x = 0; x < limitW; x++) { 328 | for (int y = 0; y < limitH; y++) { 329 | var c = chars[x][y]; 330 | if (c != null && c.getHitboxDimensions().contains(mx, my)) { 331 | ch = c; 332 | break; 333 | } 334 | } 335 | } 336 | if (ch != null) { 337 | selEnd.setLocation(ch.idx + (selEnd.x >= selStart.x ? 1 : 0), ch.idy); 338 | setCursor(selEnd.x - 1, selEnd.y); 339 | } 340 | } 341 | 342 | public String getInnerText() { 343 | return innerText; 344 | } 345 | 346 | public void setInnerText(String innerText) { 347 | this.innerText = innerText; 348 | updateInnerText(); 349 | } 350 | 351 | private void updateInnerText() { 352 | this.clearChars(); 353 | 354 | int len = innerText.length(); 355 | int cx = 0; 356 | int cy = 0; 357 | 358 | for (int i = 0; i < len; i++) { 359 | char c = innerText.charAt(i); 360 | 361 | if (cx < limitW) { 362 | var che = new CharacterElement(i, cx, cy, String.valueOf(c), cx * CHAR_W + CHAR_W, cy * CHAR_H + CHAR_H); 363 | chars[cx][cy] = che; 364 | this.addChild(che); 365 | cx++; 366 | 367 | if (cx >= limitW) { 368 | cx = 0; 369 | cy++; 370 | } 371 | } 372 | } 373 | } 374 | 375 | public Point getCursor() { 376 | int cx = 0; 377 | int cy = 0; 378 | 379 | for (int i = 0; i < (cursor + 1); i++) { 380 | if (cx < limitW) { 381 | cx++; 382 | 383 | if (cx >= limitW) { 384 | cx = 0; 385 | cy++; 386 | } 387 | } 388 | } 389 | return new Point(cx, cy); 390 | } 391 | 392 | public String getSelectedText() { 393 | StringBuilder b = new StringBuilder(); 394 | for (int y = 0; y < limitH; y++) { 395 | for (int x = 0; x < limitW; x++) { 396 | var c = chars[x][y]; 397 | if (c != null && c.isSelected()) 398 | b.append(c.ch); 399 | } 400 | } 401 | return b.toString(); 402 | } 403 | 404 | public void deleteSelectedText() { 405 | if (selStart.equals(selEnd)) 406 | return; 407 | 408 | int x, y, s1, s2; 409 | x = MathUtils.clamp(selStart.x, 0, limitW); 410 | y = MathUtils.clamp(selStart.y, 0, limitH); 411 | s1 = MathUtils.clamp(limitW * y + x, 0, innerText.length()); 412 | x = MathUtils.clamp(selEnd.x, 0, limitW); 413 | y = MathUtils.clamp(selEnd.y, 0, limitH); 414 | s2 = MathUtils.clamp(limitW * y + x, 0, innerText.length()); 415 | 416 | if (s1 == s2) 417 | return; 418 | 419 | this.innerText = innerText.substring(0, Math.min(s1, s2)) + innerText.substring(Math.max(s1, s2)); 420 | updateInnerText(); 421 | this.setCursor(Math.min(s1, s2)); 422 | updateCursor(); 423 | } 424 | 425 | public boolean isSelecting() { 426 | return !selStart.equals(selEnd); 427 | } 428 | 429 | public void setCursor(int x, int y) { 430 | x = MathUtils.clamp(x, 0, limitW); 431 | y = MathUtils.clamp(y, 0, limitH); 432 | this.setCursor(limitW * y + x); 433 | } 434 | 435 | public void setCursor(int cursor) { 436 | this.cursor = MathUtils.clamp(cursor, -1, innerText.length() - 1); 437 | } 438 | 439 | public CharacterElement getCursorChar() { 440 | var c = getCursor(); 441 | return chars[c.x][c.y]; 442 | } 443 | 444 | private void updateCursor() { 445 | this.selStart.setLocation(getCursor()); 446 | this.selEnd.setLocation(selStart); 447 | } 448 | 449 | private void clearChars() { 450 | for (int x = 0; x < limitW; x++) { 451 | for (int y = 0; y < limitH; y++) { 452 | chars[x][y] = null; 453 | } 454 | } 455 | this.clearChildren(); 456 | } 457 | 458 | @Override 459 | public void onTick() { 460 | super.onTick(); 461 | 462 | if (parentPanel != null) { 463 | if (parentPanel.focused != this) { 464 | selectionBlinking = false; 465 | return; 466 | } 467 | 468 | if (selectionBlink++ >= 20) { 469 | selectionBlink = 0; 470 | } 471 | if (selectionBlink % 10 == 0 && selectionBlink > 0) { 472 | selectionBlinking = !selectionBlinking; 473 | } 474 | } 475 | } 476 | 477 | public class CharacterElement extends Element { 478 | private final String ch; 479 | public final int idc, idx, idy; 480 | 481 | public CharacterElement(int idc, int idx, int idy, String ch, int x, int y) { 482 | super(x, y, CHAR_W, CHAR_H); 483 | this.ch = ch; 484 | this.idc = idc; 485 | this.idx = idx; 486 | this.idy = idy; 487 | } 488 | 489 | @Override 490 | public void onRender(DrawContext context, int mx, int my, float delta) { 491 | int x = getPosX(); 492 | int y = getPosY(); 493 | 494 | boolean selected = this.isSelected(); 495 | if (selected) { 496 | fillRect(context, x, y, width, height, 0xD000B7FF); 497 | } 498 | 499 | if (TextField.this.parentPanel != null) { 500 | if (TextField.this.parentPanel.altKeyPressed) { 501 | int color = 0xFFFFFFFF; 502 | if (idx == selStart.x && idy == selStart.y) 503 | color = 0xFF00FF00; 504 | if (idx == selEnd.x && idy == selEnd.y) 505 | color = 0xFFFF0000; 506 | drawBox(context, x, y, width, height, color); 507 | } 508 | int color = TextField.this.parentPanel.focused == TextField.this ? 509 | (selected ? 0xFF000000 : TextField.this.textColor.brighter().brighter().getHex()) : 510 | TextField.this.textColor.getHex(); 511 | drawDefaultCode(context, ch, x, y - 2, false, color); 512 | } 513 | 514 | if (selectionBlinking && cursor == idc) { 515 | int tx = x + width; 516 | int ty = y - 2; 517 | drawVerLine(context, tx, ty, height + 2, 0xFFFFFFFF); 518 | } 519 | } 520 | 521 | @Override 522 | public void onLeftClick(int mx, int my, boolean release) { 523 | if (release) 524 | return; 525 | setCursor(idx, idy); 526 | updateCursor(); 527 | if (TextField.this.parentPanel != null) { 528 | TextField.this.parentPanel.focused = TextField.this; 529 | TextField.this.parentPanel.selected = TextField.this; 530 | } 531 | } 532 | 533 | public boolean isSelected() { 534 | int len = TextField.this.innerText == null ? 0 : TextField.this.innerText.length(); 535 | int s1 = MathUtils.clamp(limitW * selStart.y + selStart.x, 0, len); 536 | int s2 = MathUtils.clamp(limitW * selEnd.y + selEnd.x, 0, len); 537 | return idc >= Math.min(s1, s2) && idc < Math.max(s1, s2); 538 | } 539 | } 540 | 541 | public class HistoryQueue extends ArrayList { 542 | private final int limit; 543 | 544 | public HistoryQueue(int limit) { 545 | this.limit = limit; 546 | } 547 | 548 | public void pop() { 549 | if (!this.isEmpty()) 550 | this.remove(this.size() - 1); 551 | } 552 | 553 | public void push() { 554 | if (innerText.isEmpty()) 555 | return; 556 | this.add(innerText); 557 | if (size() > limit) 558 | this.remove(0); 559 | } 560 | 561 | public String peek() { 562 | if (this.isEmpty()) 563 | return null; 564 | return this.get(this.size() - 1); 565 | } 566 | 567 | public void revertLastEdit() { 568 | String peek = this.peek(); 569 | if (peek == null) 570 | return; 571 | 572 | innerText = peek; 573 | updateInnerText(); 574 | setCursor(innerText.length() - 1); 575 | updateCursor(); 576 | 577 | pop(); 578 | } 579 | } 580 | } 581 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/math/Color.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.math; 2 | 3 | import io.github.itzispyder.improperui.util.MathUtils; 4 | 5 | public class Color { 6 | 7 | public static final Color WHITE = new Color(0xFFFFFFFF); 8 | public static final Color LIGHT_GRAY = new Color(0xFFC0C0C0); 9 | public static final Color GRAY = new Color(0xFF808080); 10 | public static final Color DARK_GRAY = new Color(0xFF404040); 11 | public static final Color BLACK = new Color(0xFF000000); 12 | public static final Color NONE = new Color(0x00000000); 13 | public static final Color BROWN = new Color(0xFF805100); 14 | public static final Color RED = new Color(0xFFFF0000); 15 | public static final Color ORANGE = new Color(0xFFFF8000); 16 | public static final Color YELLOW = new Color(0xFFFFFF00); 17 | public static final Color GREEN = new Color(0xFF008000); 18 | public static final Color LIME = new Color(0xFF80FF00); 19 | public static final Color BLUE = new Color(0xFF0000FF); 20 | public static final Color AQUA = new Color(0xFF00D0FF); 21 | public static final Color MAGENTA = new Color(0xFFE000FF); 22 | public static final Color PURPLE = new Color(0xFF871FFF); 23 | 24 | public static Color parse(String color) { 25 | color = color.trim().toLowerCase(); 26 | Color result = BLACK; 27 | 28 | if (color.isEmpty()) 29 | return result; 30 | 31 | if (color.startsWith("#")) { 32 | color = color.substring(1); 33 | int len = color.length(); 34 | 35 | if (len != 6 && len != 8) 36 | return result; 37 | int hex = Integer.parseUnsignedInt(color, 16); 38 | int a = len == 8 ? (hex >> 24 & 0xFF) : 0xFF; 39 | int r = hex >> 16 & 0xFF; 40 | int g = hex >> 8 & 0xFF; 41 | int b = hex & 0xFF; 42 | return new Color(a, r, g, b); 43 | } 44 | else switch (color) { 45 | case "white" -> result = WHITE; 46 | case "light_gray", "light-gray" -> result = LIGHT_GRAY; 47 | case "gray" -> result = GRAY; 48 | case "dark_gray", "dark-gray" -> result = DARK_GRAY; 49 | case "brown" -> result = BROWN; 50 | case "red" -> result = RED; 51 | case "orange" -> result = ORANGE; 52 | case "yellow" -> result = YELLOW; 53 | case "green" -> result = GREEN; 54 | case "lime" -> result = LIME; 55 | case "blue" -> result = BLUE; 56 | case "aqua" -> result = AQUA; 57 | case "magenta" -> result = MAGENTA; 58 | case "purple" -> result = PURPLE; 59 | case "none" -> result = NONE; 60 | } 61 | return result; 62 | } 63 | 64 | private final int r, g, b, a, hex; 65 | 66 | private Color(int hex, int a, int r, int g, int b) { 67 | this.hex = hex; 68 | this.a = MathUtils.clamp(a, 0, 255); 69 | this.r = MathUtils.clamp(r, 0, 255); 70 | this.g = MathUtils.clamp(g, 0, 255); 71 | this.b = MathUtils.clamp(b, 0, 255); 72 | } 73 | 74 | public Color(int hex) { 75 | this(hex, hex >> 24 & 0xFF, hex >> 16 & 0xFF, hex >> 8 & 0xFF, hex & 0xFF); 76 | } 77 | 78 | public Color() { 79 | this(0x00000000); 80 | } 81 | 82 | public Color(int a, int r, int g, int b) { 83 | this.a = MathUtils.clamp(a, 0, 255); 84 | this.r = MathUtils.clamp(r, 0, 255); 85 | this.g = MathUtils.clamp(g, 0, 255); 86 | this.b = MathUtils.clamp(b, 0, 255); 87 | this.hex = (this.a << 24 | this.r << 16 | this.g << 8 | this.b); 88 | } 89 | 90 | public Color(float a, float r, float g, float b) { 91 | this((int)(a * 0xFF), (int)(r * 0xFF), (int)(g * 0xFF), (int)(b * 0xFF)); 92 | } 93 | 94 | public Color(double a, double r, double g, double b) { 95 | this((float)a, (float)r, (float)g, (float)b); 96 | } 97 | 98 | public Color(java.awt.Color awtColor) { 99 | this(awtColor.getAlpha(), awtColor.getRed(), awtColor.getGreen(), awtColor.getBlue()); 100 | } 101 | 102 | public int getHex() { 103 | return hex; 104 | } 105 | 106 | public int getHexOpaque() { 107 | return 0xFF << 24 | r << 16 | g << 8 | b; 108 | } 109 | 110 | public int getHexCustomAlpha(int alpha) { 111 | return alpha << 24 | r << 16 | g << 8 | b; 112 | } 113 | 114 | public int getHexCustomAlpha(float alpha) { 115 | return getHexCustomAlpha((int)(alpha * 0xFF)); 116 | } 117 | 118 | public int getHexCustomAlpha(double alpha) { 119 | return getHexCustomAlpha((int)(alpha * 0xFF)); 120 | } 121 | 122 | public Color withAlpha(int a) { 123 | return new Color(a, r, g, b); 124 | } 125 | 126 | public Color withRed(int r) { 127 | return new Color(a, r, g, b); 128 | } 129 | 130 | public Color withBlue(int g) { 131 | return new Color(a, r, g, b); 132 | } 133 | 134 | public Color withGreen(int b) { 135 | return new Color(a, r, g, b); 136 | } 137 | 138 | public int getAlpha() { 139 | return a; 140 | } 141 | 142 | public int getRed() { 143 | return r; 144 | } 145 | 146 | public int getGreen() { 147 | return g; 148 | } 149 | 150 | public int getBlue() { 151 | return b; 152 | } 153 | 154 | public float getAlphaF() { 155 | return a / 255.0F; 156 | } 157 | 158 | public float getRedF() { 159 | return r / 255.0F; 160 | } 161 | 162 | public float getGreenF() { 163 | return g / 255.0F; 164 | } 165 | 166 | public float getBlueF() { 167 | return b / 255.0F; 168 | } 169 | 170 | public Color lerp(Color color, float delta) { 171 | delta = (float) MathUtils.clamp(delta); 172 | float iDelta = 1 - delta; 173 | int a = (int)(this.a * delta + color.a * iDelta); 174 | int r = (int)(this.r * delta + color.r * iDelta); 175 | int g = (int)(this.g * delta + color.g * iDelta); 176 | int b = (int)(this.b * delta + color.b * iDelta); 177 | return new Color(a, r, g, b); 178 | } 179 | 180 | public Color brighter() { 181 | return new Color(a, r + 20, g + 20, b + 20); 182 | } 183 | 184 | public Color darker() { 185 | return new Color(a, r - 20, g - 20, b - 20); 186 | } 187 | 188 | public java.awt.Color toAwtColor() { 189 | return new java.awt.Color(r, g, b, a); 190 | } 191 | 192 | @Override 193 | public String toString() { 194 | return "#" + Integer.toHexString(hex).toUpperCase(); 195 | } 196 | 197 | @Override 198 | public boolean equals(Object obj) { 199 | if (!(obj instanceof Color c)) { 200 | return false; 201 | } 202 | return c.a == a && c.r == r && c.g == g && c.b == b && c.hex == hex; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/math/Dimensions.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.math; 2 | 3 | public class Dimensions { 4 | 5 | public final int x, y, width, height, widthX, heightY; 6 | 7 | public Dimensions(int x, int y, int width, int height) { 8 | this.x = x; 9 | this.y = y; 10 | this.width = width; 11 | this.height = height; 12 | this.widthX = x + width; 13 | this.heightY = y + height; 14 | } 15 | 16 | public boolean isOverlapping(Dimensions dim) { 17 | int tx = this.getX(); 18 | int ty = this.getY(); 19 | int tw = this.getWidth(); 20 | int th = this.getHeight(); 21 | int txTw = tx + tw; 22 | int tyTh = ty + th; 23 | 24 | int ox = dim.getX(); 25 | int oy = dim.getY(); 26 | int ow = dim.getWidth(); 27 | int oh = dim.getHeight(); 28 | int oxOw = ox + ow; 29 | int oyOh = oy + oh; 30 | 31 | var topLeft = (txTw >= ox && txTw <= oxOw) && (tyTh >= oy && tyTh <= oyOh); 32 | var topRight = (tx >= ox && tx <= oxOw) && (tyTh >= oy && tyTh <= oyOh); 33 | var bottomRight = (tx >= ox && tx <= oxOw) && (ty >= oy && ty <= oyOh); 34 | var bottomLeft = (txTw >= ox && txTw <= oxOw) && (ty >= oy && ty <= oyOh); 35 | 36 | return topLeft || topRight || bottomLeft || bottomRight; 37 | } 38 | 39 | public boolean contains(int x, int y) { 40 | return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height; 41 | } 42 | 43 | public boolean contains(double x, double y) { 44 | return contains((int)x, (int)y); 45 | } 46 | 47 | public int getX() { 48 | return x; 49 | } 50 | 51 | public Dimensions withX(int x) { 52 | return new Dimensions(x, y, width, height); 53 | } 54 | 55 | public int getY() { 56 | return y; 57 | } 58 | 59 | public Dimensions withY(int y) { 60 | return new Dimensions(x, y, width, height); 61 | } 62 | 63 | public int getWidth() { 64 | return width; 65 | } 66 | 67 | public Dimensions withWidth(int width) { 68 | return new Dimensions(x, y, width, height); 69 | } 70 | 71 | public int getHeight() { 72 | return height; 73 | } 74 | 75 | public Dimensions withHeight(int height) { 76 | return new Dimensions(x, y, width, height); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/math/animation/Animator.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.math.animation; 2 | 3 | import io.github.itzispyder.improperui.util.MathUtils; 4 | 5 | import static net.minecraft.util.math.ColorHelper.Argb.*; 6 | 7 | public class Animator { 8 | 9 | private long start, length; 10 | private boolean reversed; 11 | 12 | public Animator(long length) { 13 | this.start = System.currentTimeMillis(); 14 | this.length = length; 15 | this.reversed = false; 16 | } 17 | 18 | public double getProgress() { 19 | long pass = System.currentTimeMillis() - start; 20 | double rat = pass / (double)length; 21 | return reversed ? 1 - rat : rat; 22 | } 23 | 24 | public double getProgressClamped() { 25 | return MathUtils.clamp(getProgress(), 0.0, 1.0); 26 | } 27 | 28 | public double getProgressReversed() { 29 | return 1 - getProgress(); 30 | } 31 | 32 | public double getProgressClampedReversed() { 33 | return MathUtils.clamp(getProgressReversed(), 0.0, 1.0); 34 | } 35 | 36 | public boolean isFinished() { 37 | double p = getProgress(); 38 | return reversed ? p <= 0.0 : p >= 1.0; 39 | } 40 | 41 | public void reverse() { 42 | reversed = !reversed; 43 | } 44 | 45 | public void setReversed(boolean reversed) { 46 | this.reversed = reversed; 47 | } 48 | 49 | public boolean isReversed() { 50 | return reversed; 51 | } 52 | 53 | public void reset(long length) { 54 | this.start = System.currentTimeMillis(); 55 | this.length = length; 56 | } 57 | 58 | public void reset() { 59 | this.start = System.currentTimeMillis(); 60 | } 61 | 62 | public static int transformColorOpacity(Animator animator, int hex) { 63 | return getArgb((int)(255 * animator.getProgressClamped()), getRed(hex), getGreen(hex), getBlue(hex)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/render/math/animation/PollingAnimator.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.render.math.animation; 2 | 3 | import java.util.function.BooleanSupplier; 4 | 5 | public class PollingAnimator extends Animator { 6 | 7 | private final BooleanSupplier poll; 8 | private boolean pollSuccess; 9 | 10 | public PollingAnimator(int length, BooleanSupplier poll) { 11 | super(length); 12 | this.poll = poll; 13 | 14 | boolean bool = poll.getAsBoolean(); 15 | this.pollSuccess = bool; 16 | this.setReversed(!bool); 17 | } 18 | 19 | @Override 20 | public double getProgress() { 21 | poll(); 22 | return super.getProgress(); 23 | } 24 | 25 | public void poll() { 26 | if (poll.getAsBoolean() && !pollSuccess) { 27 | pollSuccess = true; 28 | this.setReversed(false); 29 | this.reset(); 30 | } 31 | else if (!poll.getAsBoolean() && pollSuccess) { 32 | pollSuccess = false; 33 | this.setReversed(true); 34 | this.reset(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/CallbackHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface CallbackHandler { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/CallbackListener.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script; 2 | 3 | public interface CallbackListener { 4 | 5 | default void runCallbacks(String methodName, E target) { 6 | if (methodName == null || methodName.trim().isEmpty()) 7 | return; 8 | 9 | for (var method : this.getClass().getDeclaredMethods()) { 10 | if (!methodName.equals(method.getName())) 11 | continue; 12 | try { 13 | if (method.getAnnotation(CallbackHandler.class) == null) 14 | error("specified callback method must have annotation: @io.github.itzispyder.improperui.script.CallbackHandler"); 15 | if (method.getParameterCount() == 0) 16 | error("specified callback method must have one Event parameter"); 17 | 18 | var params = method.getParameters(); 19 | 20 | if (params[0].getType() != target.getClass()) 21 | continue; 22 | 23 | method.setAccessible(true); 24 | method.invoke(this, target); 25 | } 26 | catch (Exception ex) { 27 | error("encountered error invoking method: %s", ex.getMessage()); 28 | } 29 | return; 30 | } 31 | // error("method \"%s.%s()\" not found!", this.getClass().getSimpleName(), methodName); 32 | } 33 | 34 | default void error(String message, Object... args) { 35 | throw new IllegalArgumentException(message.formatted(args)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/Event.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script; 2 | 3 | public class Event { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/ScriptArgs.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script; 2 | 3 | import io.github.itzispyder.improperui.render.math.Color; 4 | 5 | public class ScriptArgs { 6 | 7 | private String[] args; 8 | 9 | public ScriptArgs(String... args) { 10 | this.args = args; 11 | } 12 | 13 | public Arg getAll() { 14 | return getAll(0); 15 | } 16 | 17 | public Arg getAll(int beginIndex) { 18 | String str = ""; 19 | for (int i = beginIndex; i < args.length; i++) { 20 | str = str.concat(args[i] + " "); 21 | } 22 | return new Arg(str.trim()); 23 | } 24 | 25 | public Arg get(int index) { 26 | if (args.length == 0) { 27 | return new Arg(""); 28 | } 29 | return new Arg(args[Math.min(Math.max(index, 0), args.length - 1)]); 30 | } 31 | 32 | public String getQuoteAndRemove(int beginIndex) { 33 | String all = getAll(beginIndex).toString(); 34 | var section = ScriptReader.firstSectionWithIndex(all, '"', '"'); 35 | 36 | if (section.left.isEmpty()) { 37 | args = all.split("\\s+"); 38 | return all; 39 | } 40 | 41 | args = all.substring(section.right).trim().split("\\s+"); 42 | return section.left; 43 | } 44 | 45 | public String getQuoteAndRemove() { 46 | return getQuoteAndRemove(0); 47 | } 48 | 49 | public String getQuote(int beginIndex) { 50 | String all = getAll(beginIndex).toString(); 51 | String quote = ScriptReader.firstSection(all, '"'); 52 | return quote.isEmpty() ? all : quote; 53 | } 54 | 55 | public String getQuote() { 56 | return getQuote(0); 57 | } 58 | 59 | public Arg first() { 60 | return get(0); 61 | } 62 | 63 | public Arg last() { 64 | return get(args.length - 1); 65 | } 66 | 67 | public boolean match(int index, String arg) { 68 | if (index < 0 || index >= args.length) { 69 | return false; 70 | } 71 | return get(index).toString().equalsIgnoreCase(arg); 72 | } 73 | 74 | public int getSize() { 75 | return args.length; 76 | } 77 | 78 | public boolean isEmpty() { 79 | return args.length == 0; 80 | } 81 | 82 | public String[] args() { 83 | return args; 84 | } 85 | 86 | 87 | 88 | public static class Arg { 89 | 90 | private final String arg; 91 | 92 | public Arg(String arg) { 93 | this.arg = arg; 94 | } 95 | 96 | public int toInt() { 97 | return (int) toDouble(); 98 | } 99 | 100 | public long toLong() { 101 | return (long) toDouble(); 102 | } 103 | 104 | public byte toByte() { 105 | return (byte) toDouble(); 106 | } 107 | 108 | public short toShort() { 109 | return (short) toDouble(); 110 | } 111 | 112 | public float toFloat() { 113 | return (float) toDouble(); 114 | } 115 | 116 | public double toDouble() { 117 | return Double.parseDouble(arg.replaceAll("[^0-9-+e.]", "")); 118 | } 119 | 120 | public boolean toBool() { 121 | return Boolean.parseBoolean(arg); 122 | } 123 | 124 | public char toChar() { 125 | return arg.isEmpty() ? ' ' : arg.charAt(0); 126 | } 127 | 128 | public Color toColor() { 129 | return Color.parse(arg); 130 | } 131 | 132 | @Override 133 | public String toString() { 134 | return arg; 135 | } 136 | 137 | public > T toEnum(Class enumType) { 138 | return toEnum(enumType, null); 139 | } 140 | 141 | public > T toEnum(Class enumType, T fallback) { 142 | String arg = this.arg.replace('-', '_'); 143 | for (T constant : enumType.getEnumConstants()) { 144 | if (arg.equalsIgnoreCase(constant.name())) { 145 | return constant; 146 | } 147 | } 148 | 149 | if (fallback == null) { 150 | throw new IllegalArgumentException("'%s' is not a value of %s".formatted(arg, enumType.getSimpleName())); 151 | } 152 | return fallback; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/ScriptParser.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script; 2 | 3 | import io.github.itzispyder.improperui.config.PropertyCache; 4 | import io.github.itzispyder.improperui.render.Element; 5 | import io.github.itzispyder.improperui.render.elements.*; 6 | import io.github.itzispyder.improperui.util.ChatUtils; 7 | import io.github.itzispyder.improperui.util.StringUtils; 8 | 9 | import java.io.File; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.function.Supplier; 15 | 16 | public class ScriptParser { 17 | 18 | private static final String ELEMENT = "[a-zA-Z0-9]+(\\s+[#-][a-zA-Z0-9:._-]+)*?\\s*\\{.*\\}"; 19 | private static final String SECTION = "\\s*\\{.*\\}\\s*$"; 20 | public static final Map CACHE = new HashMap<>(); 21 | 22 | public static PropertyCache getCache(String modId) { 23 | if (!CACHE.containsKey(modId)) 24 | CACHE.put(modId, new PropertyCache(modId)); 25 | return CACHE.get(modId); 26 | } 27 | 28 | private static final Map> tagSuppliers = new HashMap<>() {{ 29 | this.put("checkbox", CheckBox::new); 30 | this.put("textfield", TextField::new); 31 | this.put("textarea", TextField::new); 32 | this.put("div", Element::new); 33 | this.put("e", Element::new); 34 | this.put("button", Button::new); 35 | this.put("link", HyperLink::new); 36 | this.put("a", HyperLink::new); 37 | this.put("slider", Slider::new); 38 | this.put("radio", Radio::new); 39 | this.put("textbox", TextBox::new); 40 | this.put("input", TextBox::new); 41 | this.put("textlabel", Label::new); 42 | this.put("label", Label::new); 43 | this.put("positionable", Positionable::new); 44 | this.put("header1", () -> new Header(1.8F)); 45 | this.put("header2", () -> new Header(1.6F)); 46 | this.put("header3", () -> new Header(1.4F)); 47 | this.put("header4", () -> new Header(1.2F)); 48 | this.put("header5", () -> new Header(1.0F)); 49 | this.put("header6", () -> new Header(0.8F)); 50 | this.put("h1", () -> new Header(1.8F)); 51 | this.put("h2", () -> new Header(1.6F)); 52 | this.put("h3", () -> new Header(1.4F)); 53 | this.put("h4", () -> new Header(1.2F)); 54 | this.put("h5", () -> new Header(1.0F)); 55 | this.put("h6", () -> new Header(0.8F)); 56 | }}; 57 | 58 | 59 | 60 | public static List parseFile(File file) { 61 | if (file == null || !file.exists()) 62 | return new ArrayList<>(); 63 | 64 | String script = ScriptReader.readFile(file.getPath()); 65 | return parse(script); 66 | } 67 | 68 | public static List parse(String script) { 69 | List result = new ArrayList<>(); 70 | try { 71 | script = ScriptReader.condenseLines(script); 72 | for (String section : ScriptReader.parse(script)) { 73 | result.add(parseInternal(section)); 74 | } 75 | for (Element element : result) { 76 | element.style(); 77 | //element.printAll(); 78 | } 79 | } 80 | catch (Exception ex) { 81 | ChatUtils.sendMessage(StringUtils.color("&cError parsing script: " + ex.getMessage())); 82 | } 83 | return result; 84 | } 85 | 86 | private static Element parseInternal(String excerpt) { 87 | String[] attr = getAttributes(excerpt); 88 | return parseInternal0(attr[0], excerpt); 89 | } 90 | 91 | private static Element parseInternal0(String tag, String excerpt) { 92 | Element element = tagSuppliers.getOrDefault(tag, Element::new).get(); 93 | 94 | if (excerpt.matches(ELEMENT)) { 95 | callAttributes(element, getAttributes(excerpt)); 96 | excerpt = ScriptReader.firstSection(excerpt, '{', '}'); 97 | } 98 | 99 | for (String line : ScriptReader.parse(excerpt)) { 100 | if (!line.matches(ELEMENT)) { 101 | element.queueProperty(line); 102 | continue; 103 | } 104 | String[] attr = getAttributes(line); 105 | Element child = parseInternal0(attr[0], ScriptReader.firstSection(line, '{', '}')); 106 | callAttributes(child, attr); 107 | element.addChild(child); 108 | } 109 | 110 | return element; 111 | } 112 | 113 | private static String[] getAttributes(String scriptLine) { 114 | return scriptLine.replaceFirst(SECTION, "").split("\s+"); 115 | } 116 | 117 | private static void callAttributes(Element element, String[] attrs) { 118 | for (var attr : attrs) 119 | element.callAttribute(attr); 120 | } 121 | } -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/ScriptReader.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script; 2 | 3 | import io.github.itzispyder.improperui.util.misc.Pair; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.FileReader; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class ScriptReader { 11 | 12 | public static List getAllSections(String line, char openingChar, char closingChar) { 13 | List lines = new ArrayList<>(); 14 | int index = 0; 15 | var section = firstSectionWithIndex(line, openingChar, closingChar); 16 | 17 | while (!section.left.isEmpty()) { 18 | lines.add(section.left); 19 | index += section.right; 20 | section = firstSectionWithIndex(line.substring(index), openingChar, closingChar); 21 | } 22 | 23 | return lines; 24 | } 25 | 26 | public static String firstSection(String line, char enclosingChar) { 27 | return firstSection(line, enclosingChar, enclosingChar); 28 | } 29 | 30 | public static String firstSection(String line, char openingChar, char closingChar) { 31 | return firstSectionWithIndex(line, openingChar, closingChar).left; 32 | } 33 | 34 | public static Pair firstSectionWithIndex(String line, char openingChar, char closingChar) { 35 | line = line == null ? "" : line; 36 | StringBuilder result = new StringBuilder(); 37 | 38 | if (line.isEmpty()) { 39 | return Pair.of(line, 0); 40 | } 41 | 42 | char[] chars = line.toCharArray(); 43 | boolean began = false; 44 | int toIgnore = 0; 45 | 46 | for (int i = 0; i < chars.length; i++) { 47 | char c = chars[i]; 48 | boolean skip = i > 0 && chars[i - 1] == '\\'; 49 | 50 | if (c == openingChar && !skip) { // start of the quote 51 | if (began) { 52 | if (openingChar != closingChar) { // support deep operations 53 | toIgnore++; 54 | } 55 | } 56 | else { 57 | began = true; 58 | continue; 59 | } 60 | } 61 | 62 | if (c == closingChar && !skip && began) { // end of the quote 63 | if (toIgnore-- > 0) { 64 | result.append(c); 65 | continue; 66 | } 67 | return Pair.of(result.toString().trim(), i + 1); 68 | } 69 | else if (c == '\\' && !skip) { 70 | continue; 71 | } 72 | 73 | if (began) { 74 | result.append(c); 75 | } 76 | } 77 | 78 | String r = result.toString().trim(); 79 | if (r.isEmpty()) { 80 | return Pair.of(r, chars.length); 81 | } 82 | 83 | String msg = "unclosed enclosing chars %s%s to mark string section".formatted( 84 | String.valueOf(openingChar), 85 | String.valueOf(closingChar) 86 | ); 87 | throw new IllegalArgumentException(msg); 88 | } 89 | 90 | // script specific 91 | 92 | public static String readFile(String path) { 93 | try { 94 | FileReader fr = new FileReader(path); 95 | BufferedReader br = new BufferedReader(fr); 96 | StringBuilder result = new StringBuilder(); 97 | String line; 98 | 99 | while ((line = br.readLine()) != null) { 100 | line = line.trim(); 101 | result.append(line); 102 | 103 | if (!line.isEmpty()) { 104 | if (!line.endsWith("{") && !line.endsWith("}") && !line.endsWith(";")) { 105 | result.append(";"); 106 | } 107 | result.append(" "); 108 | } 109 | } 110 | 111 | br.close(); 112 | return result.toString().trim(); 113 | } 114 | catch (Exception ex) { 115 | return ""; 116 | } 117 | } 118 | 119 | public static String condenseLines(String string) { 120 | try { 121 | StringBuilder result = new StringBuilder(); 122 | 123 | for (String line : string.lines().toArray(String[]::new)) { 124 | line = line.trim(); 125 | result.append(line); 126 | 127 | if (!line.isEmpty()) { 128 | if (!line.endsWith("{") && !line.endsWith("}") && !line.endsWith(";")) { 129 | result.append(";"); 130 | } 131 | result.append(" "); 132 | } 133 | } 134 | 135 | return result.toString().trim(); 136 | } 137 | catch (Exception ex) { 138 | return ""; 139 | } 140 | } 141 | 142 | public static List parse(String line) { 143 | line = line == null ? "" : line; 144 | List lines = new ArrayList<>(); 145 | 146 | StringBuilder temp = new StringBuilder(); 147 | char[] chars = line.toCharArray(); 148 | boolean inQuote = false; 149 | int i = 0; 150 | 151 | while (i < chars.length) { 152 | char c = chars[i]; 153 | boolean skip = i > 0 && chars[i - 1] == '\\'; 154 | 155 | if (c == '"' && !skip) { 156 | inQuote = !inQuote; 157 | } 158 | 159 | if (c == '{' && !skip && !inQuote) { 160 | String subLine = line.substring(i); 161 | var section = firstSectionWithIndex(subLine, '{', '}'); 162 | 163 | String lead = temp.toString().trim(); 164 | lines.add((lead.isEmpty() ? "%s%s" : "%s {%s}").formatted(lead, section.left)); 165 | 166 | temp = new StringBuilder(); 167 | i += section.right; 168 | continue; 169 | } 170 | if (c == ';' && !skip && !inQuote) { 171 | String subLine = temp.toString().trim(); 172 | 173 | if (!subLine.isEmpty()) { 174 | lines.add(subLine); 175 | } 176 | 177 | temp = new StringBuilder(); 178 | i++; 179 | continue; 180 | } 181 | else { 182 | temp.append(c); 183 | } 184 | i++; 185 | } 186 | 187 | String remaining = temp.toString().trim(); 188 | if (!remaining.isEmpty()) { 189 | lines.add(remaining); 190 | } 191 | 192 | if (inQuote) { 193 | throw new IllegalArgumentException("unclosed quotation marks scanned in script"); 194 | } 195 | return lines; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/callbacks/BuiltInCallbacks.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script.callbacks; 2 | 3 | import io.github.itzispyder.improperui.ImproperUIAPI; 4 | import io.github.itzispyder.improperui.script.CallbackHandler; 5 | import io.github.itzispyder.improperui.script.CallbackListener; 6 | import io.github.itzispyder.improperui.script.events.KeyEvent; 7 | import io.github.itzispyder.improperui.script.events.MouseEvent; 8 | import io.github.itzispyder.improperui.util.ChatUtils; 9 | import net.minecraft.util.Util; 10 | import org.lwjgl.glfw.GLFW; 11 | 12 | public class BuiltInCallbacks implements CallbackListener { 13 | 14 | @CallbackHandler 15 | public void openGithub(MouseEvent e) { 16 | if (e.input.isDown()) 17 | Util.getOperatingSystem().open("https://github.com/itzispyder/improperui"); 18 | } 19 | 20 | @CallbackHandler 21 | public void openModrinth(MouseEvent e) { 22 | if (e.input.isDown()) 23 | Util.getOperatingSystem().open("https://modrinth.com/mod/improperui"); 24 | } 25 | 26 | @CallbackHandler 27 | public void openDiscord(MouseEvent e) { 28 | if (e.input.isDown()) 29 | Util.getOperatingSystem().open("https://discord.gg/tMaShNzNtP"); 30 | } 31 | 32 | @CallbackHandler 33 | public void openWiki(MouseEvent e) { 34 | if (e.input.isDown()) 35 | Util.getOperatingSystem().open("https://github.com/itzispyder/improperui/wiki"); 36 | } 37 | 38 | @CallbackHandler 39 | public void openExampleScreen(MouseEvent e) { 40 | if (e.input.isDown()) 41 | ImproperUIAPI.parseAndRunFile("improperui", "example.ui"); 42 | } 43 | 44 | @CallbackHandler 45 | public void sendHelloWorld(MouseEvent e) { 46 | if (e.input.isDown()) 47 | ChatUtils.sendMessage("Hello World"); 48 | } 49 | 50 | @CallbackHandler 51 | public void sendHelloWorld(KeyEvent e) { 52 | if (e.input.isDown()) 53 | ChatUtils.sendFormatted("Hello World + %s", GLFW.glfwGetKeyName(e.key, e.scan)); 54 | } 55 | 56 | @CallbackHandler 57 | public void printSelf(MouseEvent e) { 58 | if (e.input.isDown()) 59 | ChatUtils.sendMessage("target: " + e.target); 60 | } 61 | 62 | @CallbackHandler 63 | public void printSelf(KeyEvent e) { 64 | if (e.input.isDown()) 65 | ChatUtils.sendMessage("target: " + e.target); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/events/KeyEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script.events; 2 | 3 | import io.github.itzispyder.improperui.render.Element; 4 | import io.github.itzispyder.improperui.render.constants.InputType; 5 | import io.github.itzispyder.improperui.script.Event; 6 | 7 | public class KeyEvent extends Event { 8 | 9 | public final int key, scan; 10 | public final InputType input; 11 | public final Element target; 12 | 13 | public KeyEvent(int key, int scan, InputType input, Element target) { 14 | this.key = key; 15 | this.scan = scan; 16 | this.input = input; 17 | this.target = target; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/script/events/MouseEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.script.events; 2 | 3 | import io.github.itzispyder.improperui.render.Element; 4 | import io.github.itzispyder.improperui.render.constants.InputType; 5 | import io.github.itzispyder.improperui.script.Event; 6 | 7 | public class MouseEvent extends Event { 8 | 9 | public final int button; 10 | public final int delta; 11 | public final InputType input; 12 | public final Element target; 13 | 14 | public MouseEvent(int button, int delta, InputType input, Element target) { 15 | this.button = button; 16 | this.delta = delta; 17 | this.input = input; 18 | this.target = target; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/util/ChatUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.util; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.sound.PositionedSoundInstance; 5 | import net.minecraft.client.sound.SoundInstance; 6 | import net.minecraft.client.sound.SoundManager; 7 | import net.minecraft.sound.SoundEvent; 8 | import net.minecraft.sound.SoundEvents; 9 | import net.minecraft.text.Text; 10 | 11 | public final class ChatUtils { 12 | 13 | private static final MinecraftClient mc = MinecraftClient.getInstance(); 14 | 15 | public static void sendMessage(String message) { 16 | if (message != null && mc.player != null) { 17 | mc.player.sendMessage(Text.literal(message)); 18 | } 19 | } 20 | 21 | public static void sendFormatted(String message, Object... args) { 22 | if (message != null && mc.player != null) { 23 | mc.player.sendMessage(Text.literal(StringUtils.color(String.format(message, args)))); 24 | } 25 | } 26 | 27 | public static void sendRawText(Text text) { 28 | if (mc.player != null && text != null) { 29 | mc.player.sendMessage(text); 30 | } 31 | } 32 | 33 | public static void sendChatCommand(String cmd) { 34 | if (mc.player != null) { 35 | mc.player.networkHandler.sendCommand(cmd); 36 | } 37 | } 38 | 39 | public static void sendChatMessage(String msg) { 40 | if (mc.player != null) { 41 | mc.player.networkHandler.sendChatMessage(msg); 42 | } 43 | } 44 | 45 | public static void sendBlank(int lines) { 46 | for (int i = 0; i < lines; i++) { 47 | sendMessage(""); 48 | } 49 | } 50 | 51 | public static void sendBlank() { 52 | sendBlank(1); 53 | } 54 | 55 | public static void pingPlayer() { 56 | SoundManager sm = mc.getSoundManager(); 57 | SoundEvent event = SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP; 58 | SoundInstance sound = PositionedSoundInstance.master(event, 0.1F, 10.0F); 59 | sm.play(sound); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/util/FileValidationUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.util; 2 | 3 | import java.io.*; 4 | 5 | public final class FileValidationUtils { 6 | 7 | public static boolean validate(File file) { 8 | try { 9 | if (!file.getParentFile().exists()) 10 | if (!file.getParentFile().mkdirs()) 11 | return false; 12 | if (!file.exists()) 13 | if (!file.createNewFile()) 14 | return false; 15 | return true; 16 | } 17 | catch (Exception ex) { 18 | return false; 19 | } 20 | } 21 | 22 | public static boolean quickWrite(File file, String string) { 23 | if (file == null || !validate(file)) { 24 | return false; 25 | } 26 | 27 | try { 28 | BufferedWriter bw = new BufferedWriter(new FileWriter(file)); 29 | bw.write(string); 30 | bw.close(); 31 | return true; 32 | } 33 | catch (Exception ex) { 34 | return false; 35 | } 36 | } 37 | 38 | public static String quickRead(File file, boolean inline) { 39 | try { 40 | BufferedReader br = new BufferedReader(new FileReader(file)); 41 | StringBuilder sb = new StringBuilder(); 42 | br.lines().forEach(line -> sb.append(line.trim()).append(inline ? " " : "\n")); 43 | br.close(); 44 | return sb.toString(); 45 | } 46 | catch (Exception ex) { 47 | return ""; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/util/MathUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.util; 2 | 3 | public class MathUtils { 4 | 5 | public static int clamp(int val, int min, int max) { 6 | return Math.max(min, Math.min(max, val)); 7 | } 8 | 9 | public static double clamp(double val, double min, double max) { 10 | return Math.max(min, Math.min(max, val)); 11 | } 12 | 13 | public static double clamp(double progress) { 14 | return clamp(progress, 0.0, 1.0); 15 | } 16 | 17 | public static double round(double val, int decimalPlaces) { 18 | double nth = Math.pow(10, decimalPlaces); 19 | return Math.floor(val * nth) / nth; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/util/RenderUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.util; 2 | 3 | import io.github.itzispyder.improperui.client.ImproperUIClient; 4 | import net.minecraft.client.MinecraftClient; 5 | import net.minecraft.client.gui.DrawContext; 6 | import net.minecraft.client.render.*; 7 | import net.minecraft.client.util.Window; 8 | import net.minecraft.client.util.math.MatrixStack; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.text.Text; 11 | import net.minecraft.util.Identifier; 12 | import org.joml.Matrix4f; 13 | import org.lwjgl.glfw.GLFW; 14 | 15 | import java.awt.*; 16 | 17 | import static com.mojang.blaze3d.systems.RenderSystem.*; 18 | 19 | public final class RenderUtils { 20 | 21 | private static final MinecraftClient mc = MinecraftClient.getInstance(); 22 | private static final ImproperUIClient system = ImproperUIClient.getInstance(); 23 | 24 | // fill 25 | 26 | public static void fillRect(DrawContext context, int x, int y, int w, int h, int color) { 27 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); 28 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 29 | 30 | buf.vertex(mat, (float)x, (float)y, 0).color(color); 31 | buf.vertex(mat, (float)(x + w), (float)y, 0).color(color); 32 | buf.vertex(mat, (float)(x + w), (float)(y + h), 0).color(color); 33 | buf.vertex(mat, (float)x, (float)(y + h), 0).color(color); 34 | 35 | beginRendering(); 36 | BufferRenderer.drawWithGlobalProgram(buf.end()); 37 | finishRendering(); 38 | } 39 | 40 | public static void fillArc(DrawContext context, int cX, int cY, int radius, int start, int end, int color) { 41 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); 42 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 43 | 44 | buf.vertex(mat, (float)cX, (float)cY, 0).color(color); 45 | 46 | for (int i = start - 90; i <= end - 90; i ++) { 47 | double angle = Math.toRadians(i); 48 | float x = (float)(Math.cos(angle) * radius) + cX; 49 | float y = (float)(Math.sin(angle) * radius) + cY; 50 | buf.vertex(mat, x, y, 0).color(color); 51 | } 52 | 53 | beginRendering(); 54 | drawBuffer(buf); 55 | finishRendering(); 56 | } 57 | 58 | public static void fillCircle(DrawContext context, int cX, int cY, int radius, int color) { 59 | fillArc(context, cX, cY, radius, 0, 360, color); 60 | } 61 | 62 | public static void fillAnnulusArc(DrawContext context, int cx, int cy, int radius, int start, int end, int thickness, int color) { 63 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR); 64 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 65 | 66 | for (int i = start - 90; i <= end - 90; i ++) { 67 | float angle = (float)Math.toRadians(i); 68 | float cos = (float)Math.cos(angle); 69 | float sin = (float)Math.sin(angle); 70 | float x1 = cx + cos * radius; 71 | float y1 = cy + sin * radius; 72 | float x2 = cx + cos * (radius + thickness); 73 | float y2 = cy + sin * (radius + thickness); 74 | buf.vertex(mat, x1, y1, 0).color(color); 75 | buf.vertex(mat, x2, y2, 0).color(color); 76 | } 77 | 78 | beginRendering(); 79 | drawBuffer(buf); 80 | finishRendering(); 81 | } 82 | 83 | public static void fillAnnulus(DrawContext context, int cx, int cy, int radius, int thickness, int color) { 84 | fillAnnulusArc(context, cx, cy, radius, 0, 360, thickness, color); 85 | } 86 | 87 | public static void fillRoundRect(DrawContext context, int x, int y, int w, int h, int r, int color) { 88 | r = MathUtils.clamp(r, 0, Math.min(w, h) / 2); 89 | 90 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); 91 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 92 | 93 | buf.vertex(mat, x + w / 2F, y + h / 2F, 0).color(color); 94 | 95 | int[][] corners = { 96 | { x + w - r, y + r }, 97 | { x + w - r, y + h - r}, 98 | { x + r, y + h - r }, 99 | { x + r, y + r } 100 | }; 101 | 102 | for (int corner = 0; corner < 4; corner++) { 103 | int cornerStart = (corner - 1) * 90; 104 | int cornerEnd = cornerStart + 90; 105 | for (int i = cornerStart; i <= cornerEnd; i += 10) { 106 | float angle = (float)Math.toRadians(i); 107 | float rx = corners[corner][0] + (float)(Math.cos(angle) * r); 108 | float ry = corners[corner][1] + (float)(Math.sin(angle) * r); 109 | buf.vertex(mat, rx, ry, 0).color(color); 110 | } 111 | } 112 | 113 | buf.vertex(mat, corners[0][0], y, 0).color(color); // connect last to first vertex 114 | 115 | beginRendering(); 116 | drawBuffer(buf); 117 | finishRendering(); 118 | } 119 | 120 | public static void fillRoundShadow(DrawContext context, int x, int y, int w, int h, int r, int thickness, int innerColor, int outerColor) { 121 | r = MathUtils.clamp(r, 0, Math.min(w, h) / 2); 122 | 123 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR); 124 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 125 | 126 | int[][] corners = { 127 | { x + w - r, y + r }, 128 | { x + w - r, y + h - r}, 129 | { x + r, y + h - r }, 130 | { x + r, y + r } 131 | }; 132 | 133 | for (int corner = 0; corner < 4; corner++) { 134 | int cornerStart = (corner - 1) * 90; 135 | int cornerEnd = cornerStart + 90; 136 | for (int i = cornerStart; i <= cornerEnd; i += 10) { 137 | float angle = (float)Math.toRadians(i); 138 | float rx1 = corners[corner][0] + (float)(Math.cos(angle) * r); 139 | float ry1 = corners[corner][1] + (float)(Math.sin(angle) * r); 140 | float rx2 = corners[corner][0] + (float)(Math.cos(angle) * (r + thickness)); 141 | float ry2 = corners[corner][1] + (float)(Math.sin(angle) * (r + thickness)); 142 | buf.vertex(mat, rx1, ry1, 0).color(innerColor); 143 | buf.vertex(mat, rx2, ry2, 0).color(outerColor); 144 | } 145 | } 146 | 147 | buf.vertex(mat, corners[0][0], y, 0).color(innerColor); // connect last to first vertex 148 | buf.vertex(mat, corners[0][0], y - thickness, 0).color(outerColor); // connect last to first vertex 149 | 150 | beginRendering(); 151 | drawBuffer(buf); 152 | finishRendering(); 153 | } 154 | 155 | public static void fillRoundTabTop(DrawContext context, int x, int y, int w, int h, int r, int color) { 156 | r = MathUtils.clamp(r, 0, Math.min(w, h) / 2); 157 | 158 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); 159 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 160 | 161 | buf.vertex(mat, x + w / 2F, y + h / 2F, 0).color(color); 162 | 163 | int[][] corners = { 164 | { x + r, y + r }, 165 | { x + w - r, y + r } 166 | }; 167 | 168 | for (int corner = 0; corner < 2; corner++) { 169 | int cornerStart = (corner - 2) * 90; 170 | int cornerEnd = cornerStart + 90; 171 | for (int i = cornerStart; i <= cornerEnd; i += 10) { 172 | float angle = (float)Math.toRadians(i); 173 | float rx = corners[corner][0] + (float)(Math.cos(angle) * r); 174 | float ry = corners[corner][1] + (float)(Math.sin(angle) * r); 175 | buf.vertex(mat, rx, ry, 0).color(color); 176 | } 177 | } 178 | 179 | buf.vertex(mat, x + w, y + h, 0).color(color); 180 | buf.vertex(mat, x, y + h, 0).color(color); 181 | buf.vertex(mat, x, corners[0][1], 0).color(color); // connect last to first vertex 182 | 183 | beginRendering(); 184 | drawBuffer(buf); 185 | finishRendering(); 186 | } 187 | 188 | public static void fillRoundTabBottom(DrawContext context, int x, int y, int w, int h, int r, int color) { 189 | r = MathUtils.clamp(r, 0, Math.min(w, h) / 2); 190 | 191 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); 192 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 193 | 194 | buf.vertex(mat, x + w / 2F, y + h / 2F, 0).color(color); 195 | 196 | int[][] corners = { 197 | { x + w - r, y + h - r}, 198 | { x + r, y + h - r } 199 | }; 200 | 201 | for (int corner = 0; corner < 2; corner++) { 202 | int cornerStart = corner * 90; 203 | int cornerEnd = cornerStart + 90; 204 | for (int i = cornerStart; i <= cornerEnd; i += 10) { 205 | float angle = (float)Math.toRadians(i); 206 | float rx = corners[corner][0] + (float)(Math.cos(angle) * r); 207 | float ry = corners[corner][1] + (float)(Math.sin(angle) * r); 208 | buf.vertex(mat, rx, ry, 0).color(color); 209 | } 210 | } 211 | 212 | buf.vertex(mat, x, y, 0).color(color); 213 | buf.vertex(mat, x + w, y, 0).color(color); 214 | buf.vertex(mat, x + w, corners[0][1], 0).color(color); // connect last to first vertex 215 | 216 | beginRendering(); 217 | drawBuffer(buf); 218 | finishRendering(); 219 | } 220 | 221 | public static void fillRoundHoriLine(DrawContext context, int x, int y, int length, int thickness, int color) { 222 | fillRoundRect(context, x, y, length, thickness, thickness / 2, color); 223 | } 224 | 225 | public static void fillRoundVertLine(DrawContext context, int x, int y, int length, int thickness, int color) { 226 | fillRoundRect(context, x, y, thickness, length, thickness / 2, color); 227 | } 228 | 229 | // draw 230 | 231 | public static void drawRect(DrawContext context, int x, int y, int w, int h, int color) { 232 | drawHorLine(context, x, y, w, color); 233 | drawVerLine(context, x, y + 1, h - 2, color); 234 | drawVerLine(context, x + w - 1, y + 1, h - 2, color); 235 | drawHorLine(context, x, y + h - 1, w, color); 236 | } 237 | 238 | public static void drawBox(DrawContext context, int x, int y, int w, int h, int color) { 239 | drawLine(context, x, y, x + w, y, color); 240 | drawLine(context, x, y + h, x + w, y + h, color); 241 | drawLine(context, x, y, x, y + h, color); 242 | drawLine(context, x + w, y, x + w, y + h, color); 243 | } 244 | 245 | public static void drawHorLine(DrawContext context, int x, int y, int length, int color) { 246 | fillRect(context, x, y, length, 1, color); 247 | } 248 | 249 | public static void drawVerLine(DrawContext context, int x, int y, int length, int color) { 250 | fillRect(context, x, y, 1, length, color); 251 | } 252 | 253 | public static void drawLine(DrawContext context, int x1, int y1, int x2, int y2, int color) { 254 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR); 255 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 256 | 257 | buf.vertex(mat, (float)x1, (float)y1, 0).color(color); 258 | buf.vertex(mat, (float)x2, (float)y2, 0).color(color); 259 | 260 | beginRendering(); 261 | drawBuffer(buf); 262 | finishRendering(); 263 | } 264 | 265 | public static void drawArc(DrawContext context, int cX, int cY, int radius, int start, int end, int color) { 266 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.DEBUG_LINE_STRIP, VertexFormats.POSITION_COLOR); 267 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 268 | 269 | for (int i = start - 90; i <= end - 90; i++) { 270 | double angle = Math.toRadians(i); 271 | float x = (float)(Math.cos(angle) * radius) + cX; 272 | float y = (float)(Math.sin(angle) * radius) + cY; 273 | buf.vertex(mat, x, y, 0).color(color); 274 | } 275 | 276 | beginRendering(); 277 | drawBuffer(buf); 278 | finishRendering(); 279 | } 280 | 281 | public static void drawCircle(DrawContext context, int cX, int cY, int radius, int color) { 282 | drawArc(context, cX, cY, radius, 0, 360, color); 283 | } 284 | 285 | public static void drawRoundRect(DrawContext context, int x, int y, int w, int h, int r, int color) { 286 | r = MathUtils.clamp(r, 0, Math.min(w, h) / 2); 287 | 288 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.DEBUG_LINE_STRIP, VertexFormats.POSITION_COLOR); 289 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 290 | 291 | int[][] corners = { 292 | { x + w - r, y + r }, 293 | { x + w - r, y + h - r}, 294 | { x + r, y + h - r }, 295 | { x + r, y + r } 296 | }; 297 | 298 | for (int corner = 0; corner < 4; corner++) { 299 | int cornerStart = (corner - 1) * 90; 300 | int cornerEnd = cornerStart + 90; 301 | for (int i = cornerStart; i <= cornerEnd; i += 10) { 302 | float angle = (float)Math.toRadians(i); 303 | float rx = corners[corner][0] + (float)(Math.cos(angle) * r); 304 | float ry = corners[corner][1] + (float)(Math.sin(angle) * r); 305 | buf.vertex(mat, rx, ry, 0).color(color); 306 | } 307 | } 308 | 309 | buf.vertex(mat, corners[0][0], y, 0).color(color); // connect last to first vertex 310 | 311 | beginRendering(); 312 | drawBuffer(buf); 313 | finishRendering(); 314 | } 315 | 316 | public static void drawRoundHoriLine(DrawContext context, int x, int y, int length, int thickness, int color) { 317 | drawRoundRect(context, x, y, length, thickness, thickness / 2, color); 318 | } 319 | 320 | public static void drawRoundVertLine(DrawContext context, int x, int y, int length, int thickness, int color) { 321 | drawRoundRect(context, x, y, thickness, length, thickness / 2, color); 322 | } 323 | 324 | // default text 325 | 326 | public static void drawDefaultScaledText(DrawContext context, Text text, int x, int y, float scale, boolean shadow, int color) { 327 | MatrixStack m = context.getMatrices(); 328 | m.scale(scale, scale, scale); 329 | 330 | float rescale = 1 / scale; 331 | x = (int)(x * rescale); 332 | y = (int)(y * rescale); 333 | 334 | drawDefaultText(context, text, x, y, shadow, color); 335 | m.scale(rescale, rescale, rescale); 336 | } 337 | 338 | public static void drawDefaultCenteredScaledText(DrawContext context, Text text, int centerX, int y, float scale, boolean shadow, int color) { 339 | MatrixStack m = context.getMatrices(); 340 | m.scale(scale, scale, scale); 341 | 342 | float rescale = 1 / scale; 343 | centerX = (int)(centerX * rescale); 344 | centerX = centerX - (mc.textRenderer.getWidth(text) / 2); 345 | y = (int)(y * rescale); 346 | 347 | drawDefaultText(context, text, centerX, y, shadow, color); 348 | m.scale(rescale, rescale, rescale); 349 | } 350 | 351 | public static void drawDefaultRightScaledText(DrawContext context, Text text, int rightX, int y, float scale, boolean shadow, int color) { 352 | MatrixStack m = context.getMatrices(); 353 | m.scale(scale, scale, scale); 354 | 355 | float rescale = 1 / scale; 356 | rightX = (int)(rightX * rescale); 357 | rightX = rightX - mc.textRenderer.getWidth(text); 358 | y = (int)(y * rescale); 359 | 360 | drawDefaultText(context, text, rightX, y, shadow, color); 361 | m.scale(rescale, rescale, rescale); 362 | } 363 | 364 | public static void drawDefaultScaledText(DrawContext context, Text text, int x, int y, float scale, boolean shadow) { 365 | drawDefaultScaledText(context, text, x, y, scale, shadow, 0xFFFFFFFF); 366 | } 367 | 368 | public static void drawDefaultCenteredScaledText(DrawContext context, Text text, int centerX, int y, float scale, boolean shadow) { 369 | drawDefaultCenteredScaledText(context, text, centerX, y, scale, shadow, 0xFFFFFFFF); 370 | } 371 | 372 | public static void drawDefaultRightScaledText(DrawContext context, Text text, int rightX, int y, float scale, boolean shadow) { 373 | drawDefaultRightScaledText(context, text, rightX, y, scale, shadow, 0xFFFFFFFF); 374 | } 375 | 376 | public static void drawDefaultText(DrawContext context, Text text, int x, int y, boolean shadow, int color) { 377 | context.drawText(mc.textRenderer, text, x, y, color, shadow); 378 | } 379 | 380 | public static void drawDefaultCode(DrawContext context, String code, int x, int y, boolean shadow, int color) { 381 | context.drawText(system.codeRenderer, code, x, y, color, shadow); 382 | } 383 | 384 | // non-default 385 | // draw normal text 386 | 387 | public static void drawText(DrawContext context, String text, int x, int y, float scale, boolean shadow) { 388 | drawDefaultScaledText(context, Text.literal(text), x, y, scale, shadow); 389 | } 390 | 391 | public static void drawText(DrawContext context, String text, int x, int y, boolean shadow) { 392 | drawDefaultScaledText(context, Text.literal(text), x, y, 1.0F, shadow); 393 | } 394 | 395 | // draw right-aligned text 396 | 397 | public static void drawRightText(DrawContext context, String text, int leftX, int y, float scale, boolean shadow) { 398 | drawDefaultRightScaledText(context, Text.literal(text), leftX, y, scale, shadow); 399 | } 400 | 401 | public static void drawRightText(DrawContext context, String text, int leftX, int y, boolean shadow) { 402 | drawDefaultRightScaledText(context, Text.literal(text), leftX, y, 1.0F, shadow); 403 | } 404 | 405 | public static void drawRightText(DrawContext context, Text text, int leftX, int y, float scale, boolean shadow) { 406 | drawDefaultRightScaledText(context, text, leftX, y, scale, shadow); 407 | } 408 | 409 | public static void drawRightText(DrawContext context, Text text, int leftX, int y, boolean shadow) { 410 | drawDefaultRightScaledText(context, text, leftX, y, 1.0F, shadow); 411 | } 412 | 413 | // draw centered text 414 | 415 | public static void drawCenteredText(DrawContext context, String text, int centerX, int y, float scale, boolean shadow) { 416 | drawDefaultCenteredScaledText(context, Text.literal(text), centerX, y, scale, shadow); 417 | } 418 | 419 | public static void drawCenteredText(DrawContext context, String text, int centerX, int y, boolean shadow) { 420 | drawDefaultCenteredScaledText(context, Text.literal(text), centerX, y, 1.0F, shadow); 421 | } 422 | 423 | public static void drawCenteredText(DrawContext context, Text text, int centerX, int y, float scale, boolean shadow) { 424 | drawDefaultCenteredScaledText(context, text, centerX, y, scale, shadow); 425 | } 426 | 427 | public static void drawCenteredText(DrawContext context, Text text, int centerX, int y, boolean shadow) { 428 | drawDefaultCenteredScaledText(context, text, centerX, y, 1.0F, shadow); 429 | } 430 | 431 | // misc 432 | 433 | public static void drawTexture(DrawContext context, Identifier texture, int x, int y, int w, int h) { 434 | BufferBuilder buf = Tessellator.getInstance().begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE); 435 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 436 | 437 | buf.vertex(mat, x, y, 0).texture(0, 0); 438 | buf.vertex(mat, x, y + h, 0).texture(0, 1); 439 | buf.vertex(mat, x + w, y + h, 0).texture(1, 1); 440 | buf.vertex(mat, x + w, y, 0).texture(1, 0); 441 | 442 | disableCull(); 443 | setShader(GameRenderer::getPositionTexProgram); 444 | setShaderTexture(0, texture); 445 | 446 | BufferRenderer.drawWithGlobalProgram(buf.end()); 447 | 448 | enableCull(); 449 | } 450 | 451 | public static void drawRoundTexture(DrawContext context, Identifier texture, int x, int y, int w, int h, int r) { 452 | r = MathUtils.clamp(r, 0, Math.min(w, h) / 2); 453 | 454 | BufferBuilder buf = getTessellator().begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_TEXTURE); 455 | Matrix4f mat = context.getMatrices().peek().getPositionMatrix(); 456 | 457 | buf.vertex(mat, x + w / 2F, y + h / 2F, 0).texture(0.5F, 0.5F); 458 | 459 | int[][] corners = { 460 | { x + w - r, y + r }, 461 | { x + w - r, y + h - r}, 462 | { x + r, y + h - r }, 463 | { x + r, y + r } 464 | }; 465 | 466 | for (int corner = 0; corner < 4; corner++) { 467 | int cornerStart = (corner - 1) * 90; 468 | int cornerEnd = cornerStart + 90; 469 | for (int i = cornerStart; i <= cornerEnd; i += 10) { 470 | float angle = (float)Math.toRadians(i); 471 | float rx = corners[corner][0] + (float)(Math.cos(angle) * r); 472 | float ry = corners[corner][1] + (float)(Math.sin(angle) * r); 473 | float u = (rx - x) / w; 474 | float v = (ry - y) / h; 475 | buf.vertex(mat, rx, ry, 0).texture(u, v); 476 | } 477 | } 478 | 479 | buf.vertex(mat, corners[0][0], y, 0).texture(((float)corners[0][0] - x) / w, 0); // connect last to first vertex 480 | 481 | disableCull(); 482 | setShader(GameRenderer::getPositionTexProgram); 483 | setShaderTexture(0, texture); 484 | 485 | BufferRenderer.drawWithGlobalProgram(buf.end()); 486 | 487 | enableCull(); 488 | } 489 | 490 | public static void drawItem(DrawContext context, ItemStack item, int x, int y, float scale) { 491 | x = (int)(x / scale); 492 | y = (int)(y / scale); 493 | context.getMatrices().push(); 494 | context.getMatrices().scale(scale, scale, scale); 495 | context.drawItem(item, x, y); 496 | context.drawItemInSlot(mc.textRenderer, item, x, y); 497 | context.getMatrices().pop(); 498 | } 499 | 500 | public static void drawItem(DrawContext context, ItemStack item, int x, int y, float scale, String text) { 501 | x = (int)(x / scale); 502 | y = (int)(y / scale); 503 | context.getMatrices().push(); 504 | context.getMatrices().scale(scale, scale, scale); 505 | context.drawItem(item, x, y); 506 | context.drawItemInSlot(mc.textRenderer, item, x, y, text); 507 | context.getMatrices().pop(); 508 | } 509 | 510 | public static void drawItem(DrawContext context, ItemStack item, int x, int y, int size) { 511 | drawItem(context, item, x, y, size / 16.0F); 512 | } 513 | 514 | public static void drawItem(DrawContext context, ItemStack item, int x, int y) { 515 | drawItem(context, item, x, y, 1.0F); 516 | } 517 | 518 | // util 519 | 520 | public static void beginRendering() { 521 | disableCull(); 522 | enableBlend(); 523 | defaultBlendFunc(); 524 | setShader(GameRenderer::getPositionColorProgram); 525 | } 526 | 527 | public static void finishRendering() { 528 | enableCull(); 529 | disableBlend(); 530 | setShader(GameRenderer::getPositionTexProgram); 531 | } 532 | 533 | public static void check(boolean check, String msg) { 534 | if (!check) { 535 | throw new IllegalArgumentException(msg); 536 | } 537 | } 538 | 539 | public static void drawBuffer(BufferBuilder buf) { 540 | BufferRenderer.drawWithGlobalProgram(buf.end()); 541 | } 542 | 543 | public static Tessellator getTessellator() { return Tessellator.getInstance(); 544 | } 545 | 546 | public static int width() { 547 | return mc.getWindow().getScaledWidth(); 548 | } 549 | 550 | public static int height() { 551 | return mc.getWindow().getScaledHeight(); 552 | } 553 | 554 | public static void setCursor(int x, int y) { 555 | Window win = mc.getWindow(); 556 | int w1 = win.getWidth(); 557 | int w2 = win.getScaledWidth(); 558 | int h1 = win.getHeight(); 559 | int h2 = win.getScaledHeight(); 560 | double ratW = (double)w2 / (double)w1; 561 | double ratH = (double)h2 / (double)h1; 562 | GLFW.glfwSetCursorPos(win.getHandle(), x / ratW, y / ratH); 563 | } 564 | 565 | public static Point getCursor() { 566 | Window win = mc.getWindow(); 567 | int w1 = win.getWidth(); 568 | int w2 = win.getScaledWidth(); 569 | int h1 = win.getHeight(); 570 | int h2 = win.getScaledHeight(); 571 | double rW = (double)w2 / (double)w1; 572 | double rH = (double)h2 / (double)h1; 573 | return new Point((int)(rW * mc.mouse.getX()), (int)(rH * mc.mouse.getY())); 574 | } 575 | } 576 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.util; 2 | 3 | import java.math.BigInteger; 4 | import java.time.LocalDateTime; 5 | import java.util.UUID; 6 | 7 | public final class StringUtils { 8 | 9 | public static String color(String s) { 10 | return s.replace('&', '§'); 11 | } 12 | 13 | public static String decolor(String s) { 14 | return s.replaceAll("[§|&][1234567890abcdefklmnor]", ""); 15 | } 16 | 17 | public static boolean isNumber(String str) { 18 | return str.matches("^-?\\d*\\.?\\d*$"); 19 | } 20 | 21 | /** 22 | * passing insert as null would world as backspace, deleting a character 23 | */ 24 | public static String insertString(String str, int index, String insert) { 25 | if (str == null || str.isEmpty()) { 26 | return insert != null ? insert : ""; 27 | } 28 | 29 | index = Math.max(Math.min(index, str.length()), 0); 30 | String begin = str.substring(0, Math.max(Math.min(insert != null ? index : index - 1, str.length()), 0)); 31 | String end = str.substring(index); 32 | return begin + (insert != null ? insert : "") + end; 33 | } 34 | 35 | public static String format(String s, Object... args) { 36 | for (int i = 0; i < args.length; i++) { 37 | Object obj = args[i]; 38 | String arg = obj == null ? "null" : obj.toString(); 39 | s = s.replaceAll("%" + i, arg); 40 | s = s.replaceFirst("%s", arg); 41 | } 42 | return s.replace("%n", "\n"); 43 | } 44 | 45 | public static int calcEditDist(String from, String to) { 46 | from = from.trim().toLowerCase(); 47 | to = to.trim().toLowerCase(); 48 | 49 | if (from.isEmpty()) 50 | return to.length(); 51 | if (to.isEmpty()) 52 | return from.length(); 53 | if (from.charAt(0) == to.charAt(0)) 54 | return calcEditDist(from.substring(1), to.substring(1)); 55 | 56 | int e1 = calcEditDist(from.substring(1), to); 57 | int e2 = calcEditDist(from, to.substring(1)); 58 | int e3 = calcEditDist(from.substring(1), to.substring(1)); 59 | return 1 + Math.min(e1, Math.min(e2, e3)); 60 | } 61 | 62 | public static String reverse(String s) { 63 | return new StringBuilder(s).reverse().toString(); 64 | } 65 | 66 | public static String capitalize(String s) { 67 | if (s.length() <= 1) return s.toUpperCase(); 68 | s = s.toLowerCase(); 69 | return String.valueOf(s.charAt(0)).toUpperCase() + s.substring(1); 70 | } 71 | 72 | public static String capitalizeWords(String s) { 73 | s = s.replaceAll("[_-]"," "); 74 | String[] sArray = s.split(" "); 75 | StringBuilder sb = new StringBuilder(); 76 | for (String str : sArray) sb.append(capitalize(str)).append(" "); 77 | return sb.toString().trim(); 78 | } 79 | 80 | public static String getCurrentTimeStamp() { 81 | LocalDateTime now = LocalDateTime.now(); 82 | String hr = min2placeDigit(now.getHour()); 83 | String min = min2placeDigit(now.getMinute()); 84 | String sec = min2placeDigit(now.getSecond()); 85 | return "%s:%s:%s".formatted(hr, min, sec); 86 | } 87 | 88 | public static String min2placeDigit(int digit) { 89 | return (digit >= 0 && digit <= 9 ? "0" : "") + digit; 90 | } 91 | 92 | public static UUID toUUID(String uuid) { 93 | uuid = uuid.trim().replace("-", ""); 94 | return new UUID( 95 | new BigInteger(uuid.substring(0, 16), 16).longValue(), 96 | new BigInteger(uuid.substring(16), 16).longValue() 97 | ); 98 | } 99 | 100 | public static String keyPressWithShift(String s) { 101 | return s.length() != 1 ? s : Character.toString(charWithShift(s.charAt(0))); 102 | } 103 | 104 | private static char charWithShift(char c) { 105 | char upper = Character.toUpperCase(c); 106 | if (upper != c) { 107 | return upper; 108 | } 109 | 110 | switch (c) { 111 | case '1' -> upper = '!'; 112 | case '2' -> upper = '@'; 113 | case '3' -> upper = '#'; 114 | case '4' -> upper = '$'; 115 | case '5' -> upper = '%'; 116 | case '6' -> upper = '^'; 117 | case '7' -> upper = '&'; 118 | case '8' -> upper = '*'; 119 | case '9' -> upper = '('; 120 | case '0' -> upper = ')'; 121 | case '-' -> upper = '_'; 122 | case '=' -> upper = '+'; 123 | case '`' -> upper = '~'; 124 | case '[' -> upper = '{'; 125 | case ']' -> upper = '}'; 126 | case '\\' -> upper = '|'; 127 | case '\'' -> upper = '"'; 128 | case ';' -> upper = ':'; 129 | case '/' -> upper = '?'; 130 | case '.' -> upper = '>'; 131 | case ',' -> upper = '<'; 132 | } 133 | 134 | return upper; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/util/misc/ManualMap.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.util.misc; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class ManualMap { 7 | 8 | @SafeVarargs 9 | public static Map fromEntries(Map.Entry... entries) { 10 | return Map.ofEntries(entries); 11 | } 12 | 13 | @SafeVarargs 14 | public static Map fromPairs(Pair... pairs) { 15 | Map map = new HashMap<>(); 16 | for (Pair pair : pairs) { 17 | try { 18 | map.put(pair.left, pair.right); 19 | } 20 | catch (Exception ignore) {} 21 | } 22 | return map; 23 | } 24 | 25 | @SuppressWarnings("unchecked") 26 | public static Map fromItems(Object... items) { 27 | if (items.length % 2 != 0) { 28 | throw new IllegalArgumentException("items amount must be even for each key to have a value!"); 29 | } 30 | 31 | Map map = new HashMap<>(); 32 | for (int i = 0; i < items.length; i += 2) { 33 | try { 34 | map.put((K)items[i], (V)items[i + 1]); 35 | } 36 | catch (Exception ex) { 37 | break; 38 | } 39 | } 40 | return map; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/util/misc/Pair.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.util.misc; 2 | 3 | public class Pair { 4 | 5 | public L left; 6 | public R right; 7 | 8 | public Pair(L left, R right) { 9 | this.left = left; 10 | this.right = right; 11 | } 12 | 13 | public static Pair of(L left, R right) { 14 | return new Pair<>(left, right); 15 | } 16 | 17 | public L getLeft() { 18 | return left; 19 | } 20 | 21 | public void setLeft(L left) { 22 | this.left = left; 23 | } 24 | 25 | public boolean hasLeft() { 26 | return left != null; 27 | } 28 | 29 | public R getRight() { 30 | return right; 31 | } 32 | 33 | public void setRight(R right) { 34 | this.right = right; 35 | } 36 | 37 | public boolean hasRight() { 38 | return right != null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/github/itzispyder/improperui/util/misc/Voidable.java: -------------------------------------------------------------------------------- 1 | package io.github.itzispyder.improperui.util.misc; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.Function; 5 | 6 | public class Voidable { 7 | 8 | private final T value; 9 | 10 | private Voidable(T value) { 11 | this.value = value; 12 | } 13 | 14 | public T get() { 15 | return value; 16 | } 17 | 18 | public boolean isPresent() { 19 | return value != null; 20 | } 21 | 22 | public Voidable map(Function function) { 23 | return isPresent() ? of(function.apply(value)) : of(null); 24 | } 25 | 26 | public void accept(Consumer action) { 27 | if (isPresent()) { 28 | action.accept(value); 29 | } 30 | } 31 | 32 | public void accept(Consumer action, Runnable orElse) { 33 | if (isPresent()) { 34 | action.accept(value); 35 | } 36 | else { 37 | orElse.run(); 38 | } 39 | } 40 | 41 | public T getOrDef(T fallback) { 42 | return isPresent() ? value : fallback; 43 | } 44 | 45 | public T getOrThrow(String msg, Object... args) { 46 | if (isPresent()) 47 | return value; 48 | throw new IllegalArgumentException(msg.formatted(args)); 49 | } 50 | 51 | public T getOrThrow() { 52 | return getOrThrow("value is not present."); 53 | } 54 | 55 | public static Voidable of(T value) { 56 | return new Voidable<>(value); 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/resources/assets/improperui/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItziSpyder/ImproperUI/930da42889d10c073bf1e904fbb39997c2d13811/src/main/resources/assets/improperui/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/improperui/improperui/example.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | div #center { 4 | background-color: #40FFFFFF 5 | border-radius: 5 6 | size: 420 240 7 | center: both 8 | padding: 10 9 | 10 | child-align: grid 11 | 12 | slider -config.properties:testing-slider { 13 | 14 | } 15 | 16 | checkbox -config.properties:testing-checkbox { 17 | 18 | } 19 | 20 | button { 21 | 22 | } 23 | 24 | div { 25 | child-align: grid 26 | grid-columns: 100 27 | height: 15 28 | 29 | radio -config.properties:radio {} 30 | radio -config.properties:radio1 {} 31 | radio -config.properties:radio2 {} 32 | radio -config.properties:radio3 {} 33 | radio -config.properties:radio4 {} 34 | radio -config.properties:radio5 {} 35 | } 36 | 37 | checkbox -config.properties:testing-checkbox1 { 38 | active: true 39 | } 40 | 41 | input -config.properties:testing-input { 42 | placeholder: "real" 43 | input-mask: "\\\\\\d*" 44 | } 45 | 46 | textarea -config.properties:testing-textarea { 47 | size: 100 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/resources/assets/improperui/improperui/homescreen.ui: -------------------------------------------------------------------------------- 1 | 2 | #2d162c 3 | #412752 4 | #683a68 5 | #9775a6 6 | 7 | div #background-gradient { 8 | size: 100% 9 | margin-top: 100% 10 | shadow-distance: 50% 11 | shadow-color: #9775a6 12 | } 13 | 14 | div #display { 15 | size: 420 240 16 | center: both 17 | border-radius: 10 18 | border-thickness: 1 19 | border-color: #412752 20 | background-color: #2d162c 21 | shadow-distance: 5 22 | shadow-color: #683a68 23 | 24 | child-align: grid 25 | grid-columns: 1 26 | 27 | div #title { 28 | inner-text: "ImproperUI Interactives" 29 | size: 100% 10 30 | text-align: center 31 | text-scale: 1.69 32 | text-color: #9775a6 33 | background-color: none 34 | margin-top: 15 35 | } 36 | 37 | div #motto { 38 | inner-text: "We got CSS in Minecraft before GTA 6" 39 | size: 100% 10 40 | text-align: center 41 | text-scale: 0.8 42 | background-color: none 43 | margin-top: 10 44 | } 45 | div #motto { 46 | inner-text: "The Ultimate Solution To Minecraft Rendering Being Too Difficult" 47 | size: 100% 10 48 | text-align: center 49 | text-scale: 0.8 50 | background-color: none 51 | margin-bottom: 10 52 | } 53 | 54 | div #navbar { 55 | size: 100% 15 56 | margin-top: 10 57 | background-color: none 58 | 59 | child-align: grid 60 | grid-columns: 100 61 | 62 | button #discord { 63 | inner-text: "Discord" 64 | background-color: #9775a6 65 | border-radius: 0 66 | margin: 0 67 | width: 20% 68 | padding-left: 0 69 | padding-right: 0 70 | 71 | on-click: openDiscord 72 | 73 | hovered => { padding: 3; border-thickness: 0; border-radius: 2; shadow-distance: 2; } 74 | } 75 | button #github { 76 | inner-text: "GitHub" 77 | background-color: #9775a6 78 | border-radius: 0 79 | margin: 0 80 | width: 20% 81 | padding-left: 0 82 | padding-right: 0 83 | 84 | on-click: openGithub 85 | 86 | hovered => { padding: 3; border-thickness: 0; border-radius: 2; shadow-distance: 2; } 87 | } 88 | button #modrinth { 89 | inner-text: "Modrinth" 90 | background-color: #9775a6 91 | border-radius: 0 92 | margin: 0 93 | width: 20% 94 | padding-left: 0 95 | padding-right: 0 96 | 97 | on-click: openModrinth 98 | 99 | hovered => { padding: 3; border-thickness: 0; border-radius: 2; shadow-distance: 2; } 100 | } 101 | button #wiki { 102 | inner-text: "Wiki" 103 | background-color: #9775a6 104 | border-radius: 0 105 | margin: 0 106 | width: 20% 107 | padding-left: 0 108 | padding-right: 0 109 | 110 | on-click: openWiki 111 | 112 | hovered => { padding: 3; border-thickness: 0; border-radius: 2; shadow-distance: 2; } 113 | } 114 | button #wiki { 115 | inner-text: "Example" 116 | background-color: #9775a6 117 | border-radius: 0 118 | margin: 0 119 | width: 20% 120 | padding-left: 0 121 | padding-right: 0 122 | 123 | on-click: openExampleScreen 124 | 125 | hovered => { padding: 3; border-thickness: 0; border-radius: 2; shadow-distance: 2; } 126 | } 127 | } 128 | 129 | div #mini-world { 130 | size: 100% 140 131 | background-color: none 132 | 133 | child-align: grid 134 | grid-columns: 21 135 | 136 | scrollable: true 137 | background-clip: padding 138 | 139 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 140 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 141 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 142 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 143 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 144 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 145 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 146 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 147 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 148 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 149 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 150 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 151 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 152 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 153 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 154 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 155 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 156 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 157 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 158 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 159 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 160 | 161 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 162 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 163 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 164 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 165 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 166 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 167 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 168 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 169 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 170 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 171 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 172 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 173 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 174 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 175 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 176 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 177 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 178 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 179 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 180 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 181 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 182 | 183 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 184 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 185 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 186 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 187 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 188 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 189 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 190 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 191 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 192 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 193 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 194 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 195 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 196 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 197 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 198 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 199 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 200 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 201 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 202 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 203 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 204 | 205 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 206 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 207 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 208 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 209 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 210 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 211 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 212 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 213 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 214 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 215 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 216 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 217 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 218 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 219 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 220 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 221 | div #block { background-image: textures/block/oak_leaves.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } div #greenshade { size: 100%; background-color: green; opacity: 0.5; click-through: true; } } 222 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 223 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 224 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 225 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 226 | 227 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 228 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 229 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 230 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 231 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 232 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 233 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 234 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 235 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 236 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 237 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 238 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 239 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 240 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 241 | div #block { background-image: textures/block/oak_log.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 242 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 243 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 244 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 245 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 246 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 247 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 248 | 249 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 250 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 251 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 252 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 253 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 254 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 255 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 256 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 257 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 258 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 259 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 260 | div #block { background-image: textures/block/stone.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 261 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 262 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 263 | div #block { background-image: textures/block/oak_log.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 264 | div #block { background-image: textures/block/ice.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 265 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 266 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 267 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 268 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 269 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 270 | 271 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 272 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 273 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 274 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 275 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 276 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 277 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 278 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 279 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 280 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 281 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 282 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 283 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 284 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 285 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 286 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 287 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 288 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 289 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 290 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 291 | div #block { background-image: textures/block/grass_block_side.png; size: 20; draggable: true; hovered => { border-thickness: 1; border-color: white; } } 292 | } 293 | } 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | -------------------------------------------------------------------------------- /src/main/resources/assets/improperui/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "binds.improperui": "Improper UI API", 3 | "binds.improperui.menu": "Open Home Screen" 4 | } -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "improperui", 4 | "version": "${version}", 5 | "name": "ImproperUI", 6 | "description": "", 7 | "authors": [], 8 | "contact": { 9 | "repo": "https://github.com/ItziSpyder/ImproperUI" 10 | }, 11 | "license": "All-Rights-Reserved", 12 | "icon": "assets/improperui/icon.png", 13 | "environment": "*", 14 | "entrypoints": { 15 | "client": [ 16 | "io.github.itzispyder.improperui.client.ImproperUIClient" 17 | ], 18 | "main": [ 19 | "io.github.itzispyder.improperui.ImproperUI" 20 | ] 21 | }, 22 | "mixins": [ 23 | "improperui.mixins.json" 24 | ], 25 | "depends": { 26 | "fabricloader": ">=${loader_version}", 27 | "fabric": "*", 28 | "minecraft": ">=${minecraft_version}" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/improperui.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "io.github.itzispyder.improperui.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | ], 8 | "client": [ 9 | "MixinFontManager", 10 | "MixinMinecraftClient" 11 | ], 12 | "injectors": { 13 | "defaultRequire": 1 14 | } 15 | } 16 | --------------------------------------------------------------------------------