diff --git a/CHANGES b/CHANGES index d3d71a1..e25db01 100644 --- a/CHANGES +++ b/CHANGES @@ -1,10 +1,14 @@ -[Unreleased] -- Nothing here so far. +[1.4.1] +- BREAKING CHANGE: The dependency on libGDX has been updated to 1.14.0. This precipitated another breaking change. +- BREAKING CHANGE: TypingLabel#getOriginalText() now returns a CharArray, not a libGDX StringBuilder, because libGDX no longer contains that class. +- The test now uses LWJGL3 instead of LWJGL2, which allows it to run on newer MacOS hardware. +- The RegExodus dependency has updated to 0.1.20, which shouldn't have any major breaking changes, just feature additions and fixes for some platforms (RoboVM and GWT). It also should be a slightly smaller dependency. +- For building Typing-Label, Gradle was updated to 9.2.1, which is allows building with Java 25 if that's all you have on your machine. [1.4.0] - BREAKING CHANGE: Added support for multiple TypingListeners in TypingLabel. Methods "getTypingListener" and "getTypingListeners" were replaced by "setTypingListener" and "addTypingListener". - Added If Conditionals! {IF=GENDER;f=She;m=He;t=Them;Unknown} -- See details in the wiki. -- Updated Regexodus dependency for GWT. If you use GWT and inherit Regexodus in your project, follow the installation instructions again, as some of them have changed (namely the `inherits` line). +- Updated RegExodus dependency for GWT. If you use GWT and inherit RegExodus in your project, follow the installation instructions again, as some of them have changed (namely the `inherits` line). - Fixed color tags being applied to the incorrect glyph indices. Thanks @tommyettinger! - Fixed alpha not being applied to glyphs not affected by color-altering tokens. Thanks @tommyettinger! diff --git a/README.md b/README.md index 9b30245..93969a7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Open _build.gradle_ in project root and add this to the _ext_ section under _all ```groovy typingLabelVersion = '1.4.0' -regExodusVersion = '0.1.13' // Only if you're using HTML / GWT +regExodusVersion = '0.1.20' // Only if you're using HTML / GWT ``` #### Core module @@ -52,12 +52,45 @@ api "com.rafaskoberg.gdx:typing-label:$typingLabelVersion:sources" Check the Wiki: - [Usage examples](https://github.com/rafaskb/typing-label/wiki/Examples) - [Tokens](https://github.com/rafaskb/typing-label/wiki/Tokens) -- [Fine tuning](https://github.com/rafaskb/typing-label/wiki/Fine-Tuning) +- [Fine-tuning](https://github.com/rafaskb/typing-label/wiki/Fine-Tuning) - [Custom Effects](https://github.com/rafaskb/typing-label/wiki/Tokens#custom-effects) ## textratypist and SDF / MSDF Support -_Multi-channel Signed Distance Field_ fonts allow you to prepare and load just one font file and render it in any scale you want, while mantaining the quality and cripsness of the original texture, as if you were working directly with vectors. - -Since TypingLabel aims to be a replacement for regular scene2d.ui Labels though, that means it relies on BitmapFonts, which have a specific size and don't work well with scaling. - -If you're using SDF fonts in your project and want TypingLabel features, then make sure to take a look at [Textramode](https://github.com/tommyettinger/textramode) and [Textratypist](https://github.com/tommyettinger/textramode) libraries by [Tommy Ettinger](https://github.com/tommyettinger). They support SDF and MSDF fonts, have much of the TypingLabel features, as well extended markup such as bold and oblique, and much more. +_Signed Distance Field_ and _Multi-channel Signed Distance Field_ fonts allow you to prepare and load just one font file +and render it in any scale you want, while maintaining the quality and crispness of the original texture, as if you were +working directly with vectors. + +Since TypingLabel aims to be a replacement for regular scene2d.ui Labels though, that means it relies on BitmapFonts, +which have a specific size and don't work well with scaling. There is a DistanceFieldFont class in libGDX that extends +BitmapFont, but it flushes its Batch a lot more than a BitmapFont does, and isn't exactly easy to use. + +If you're using SDF or MSDF fonts in your project and want TypingLabel features, then make sure to take a look at the +[Textratypist](https://github.com/tommyettinger/textratypist) library by +[Tommy Ettinger](https://github.com/tommyettinger). It supports SDF and MSDF fonts, has most of the TypingLabel +features, offers extended markup such as bold and oblique, and much more. + +(Tommy Ettinger has hijacked the README.md at this point.) + +Other features in TextraTypist include inline images such as emoji, click-able links that go to a URL, click-able words +that trigger an event via TypingListener, rotating glyphs in-place (with various effects that use this), +stretching/squashing individual glyphs (also used by various effects), named colors that can mix and alter their parts +(like `darker dull blue green`), no usage of reflection except what Skin uses (useful for Graal Native Images), various +ways to adjust fonts to force monospace, change line-height, increase or decrease glyph width, experimental support for +justifying text... It goes on for a while. + +However, TextraTypist isn't as drop-in compatible as Typing-Label with scene2d.ui support! The `TypingLabel` in this +library extends `Label` from scene2d.ui, while the one in TextraTypist does not, and in fact there's a whole duplicate +set of scene2d.ui widgets that use TextraTypist's `Font` class instead of `BitmapFont`. These aren't always necessary to +use, but they are needed if you need most of the mentioned TextraTypist features, like how a TextraSelectBox can have +emoji icons in its text rows. + +A few other features are present in Typing-Label but are absent from TextraTypist. TextraTypist uses both square braces +and curly brackets for different types of markup, so it doesn't have an easy way to swap out an effect like `{RAINBOW}` +and make it use a different syntax, like `<>`. Typing-Label can do this! TextraTypist can, at most, use the +alternate syntax `[-RAINBOW]` to be compatible with I18N properties files. + +When gdx-liftoff added effects to warning messages to draw attention to them, it added Typing-Label as a dependency, and +it applied smoothly in-place. When gdx-liftoff updated to libGDX 1.14.0, Typing-Label wasn't compatible immediately, so +the dependency switched to TextraTypist -- because TextraTypist ~~stole~~ built upon mostly the same code as +Typing-Label, the needed changes were small. Typing-Label should be compatible with libGDX 1.14.0 as of now (at least +via JitPack, and probably via a Maven Central release), so this isn't going to be a problem in the future. diff --git a/build.gradle b/build.gradle index c014b67..bd3e3cd 100644 --- a/build.gradle +++ b/build.gradle @@ -2,22 +2,23 @@ buildscript { repositories { mavenLocal() mavenCentral() - maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + maven { url = "https://oss.sonatype.org/content/repositories/snapshots/" } gradlePluginPortal() } dependencies { - classpath 'com.vanniktech:gradle-maven-publish-plugin:0.17.0' + classpath 'com.vanniktech:gradle-maven-publish-plugin:0.35.0' } } apply plugin: 'java-library' apply plugin: 'com.vanniktech.maven.publish' +apply plugin: 'idea' -sourceCompatibility = 1.7 +java.sourceCompatibility = 1.8 -def projectName = 'typing-label' -version "$VERSION_NAME" -group 'com.rafaskoberg.gdx' +def projectName = "typing-label" +version = "$VERSION_NAME" +group = "com.rafaskoberg.gdx" [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' @@ -25,42 +26,54 @@ group 'com.rafaskoberg.gdx' // http://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html if (JavaVersion.current().isJava8Compatible()) { allprojects { - tasks.withType(Javadoc) { - // The -quiet is because of some sort of weird JDK JavaCompiler bug: - // https://discuss.gradle.org/t/passing-arguments-to-compiler-and-javadoc/1661 - options.addStringOption('Xdoclint:none,-missing', '-quiet') + tasks.withType(Javadoc).tap { + configureEach { + // The -quiet is because of some sort of weird JDK JavaCompiler bug: + // https://discuss.gradle.org/t/passing-arguments-to-compiler-and-javadoc/1661 + options.addStringOption('Xdoclint:none,-missing', '-quiet') + } } } } if(JavaVersion.current().isJava9Compatible()) { allprojects { - tasks.withType(JavaCompile) { - options.release.set(7) + tasks.withType(JavaCompile).tap { + configureEach { + options.release.set(8) + } } } } sourceSets.test.resources.srcDirs = [ file('src/test/resources').path ] +tasks.withType(AbstractTestTask).configureEach { + failOnNoDiscoveredTests = false +} + +idea.module.excludeDirs += [file("docs/")] + javadoc.destinationDir = file('docs/apidocs') jar { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE archiveBaseName.set(projectName) manifest { - attributes 'Implementation-Title': projectName, 'Implementation-Version': archiveVersion + attributes 'Implementation-Title': projectName, 'Implementation-Version': archiveVersion, 'Enable-Native-Access': 'ALL-UNNAMED' } } repositories { + mavenLocal() mavenCentral() - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + maven { url = 'https://central.sonatype.com/repository/maven-snapshots/' } } dependencies { - api group: 'com.badlogicgames.gdx', name: 'gdx', version: gdxVersion - api group: 'com.github.tommyettinger', name: 'regexodus', version: regExodusVersion - testImplementation group: 'com.badlogicgames.gdx', name: 'gdx-backend-lwjgl', version: gdxVersion - testImplementation group: 'com.badlogicgames.gdx', name: 'gdx-platform', version: gdxVersion, classifier: 'natives-desktop' + api "com.badlogicgames.gdx:gdx:$gdxVersion" + api "com.github.tommyettinger:regexodus:$regExodusVersion" + + testImplementation "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion" + testImplementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" } diff --git a/gradle.properties b/gradle.properties index 01e153a..fb78fab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms128m -Xmx512m -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 org.gradle.configureondemand=false -gdxVersion=1.11.0 -regExodusVersion=0.1.15 +gdxVersion=1.14.0 +regExodusVersion=0.1.20 GROUP=com.rafaskoberg.gdx POM_ARTIFACT_ID=typing-label @@ -26,3 +26,10 @@ POM_DEVELOPER_ID=rafaskb POM_DEVELOPER_NAME=Rafa Skoberg POM_DEVELOPER_URL=https://github.com/rafaskb/ POM_DEVELOPER_EMAIL=rafa.skoberg@gmail.com + +SONATYPE_HOST=CENTRAL_PORTAL +# This next line might need changing if JitPack.io can't build a released version. It might not... +# It must be true when running Gradle tasks to publish this project to Maven Central. +# JitPack should be able to avoid trying to sign the release now, though. +# Any attempt to sign JARs by JitPack will fail because it doesn't have any secrets. +RELEASE_SIGNING_ENABLED=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index cc4fdc2..f8e1ee3 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb70..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7..adff685 100644 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,80 +15,114 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,87 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 24467a1..e509b2d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,10 +27,14 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -51,48 +57,35 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/jitpack.yml b/jitpack.yml index f360b0a..f0f0e09 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,5 +1,5 @@ jdk: - - openjdk16 + - openjdk21 install: - echo "Installing with Gradle" - chmod 755 gradlew diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/GlyphUtils.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/GlyphUtils.java index 6cdea72..512468a 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/GlyphUtils.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/GlyphUtils.java @@ -3,28 +3,28 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.DefaultPool; import com.badlogic.gdx.utils.Pool; -import com.badlogic.gdx.utils.ReflectionPool; /** Utility class to manage {@link Glyph} pooling and cloning. */ class GlyphUtils { - private static final Pool pool = new ReflectionPool(TypingGlyph.class) { + private static final Pool pool = new DefaultPool(TypingGlyph::new) { protected void reset(TypingGlyph glyph) { GlyphUtils.reset(glyph); } }; /** - * Returns a glyph from this pool. The glyph may be new (from {@link Pool#newObject()}) or reused (previously {@link - * Pool#free(Object) freed}). + * Returns a glyph from this pool. The glyph may be new (from {@link Pool#obtain()}) or reused (previously + * {@link Pool#free(Object) freed}). */ static TypingGlyph obtain() { return pool.obtain(); } /** - * Returns a glyph from this pool and clones it from the given one. The glyph may be new (from {@link - * Pool#newObject()}) or reused (previously {@link Pool#free(Object) freed}). + * Returns a glyph from this pool and clones it from the given one. The glyph may be new (from + * {@link #obtain()}) or reused (previously {@link Pool#free(Object) freed}). */ static Glyph obtainClone(Glyph from) { TypingGlyph glyph = pool.obtain(); diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/InternalToken.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/InternalToken.java index c441f57..b487650 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/InternalToken.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/InternalToken.java @@ -24,7 +24,7 @@ enum InternalToken { final String name; final TokenCategory category; - private InternalToken(String name, TokenCategory category) { + InternalToken(String name, TokenCategory category) { this.name = name; this.category = category; } diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/Parser.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/Parser.java index 491acbb..1970987 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/Parser.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/Parser.java @@ -14,11 +14,10 @@ /** Utility class to parse tokens from a {@link TypingLabel}. */ class Parser { - private static TokenDelimiter CURRENT_DELIMITER = TypingConfig.TOKEN_DELIMITER; - private static Pattern PATTERN_TOKEN_STRIP = compileTokenPattern(); - private static Pattern PATTERN_MARKUP_STRIP = Pattern.compile("(\\[{2})|(\\[#?\\w*(\\[|\\])?)"); - - private static final Pattern PATTERN_COLOR_HEX_NO_HASH = Pattern.compile("[A-F0-9]{6}"); + private static TokenDelimiter CURRENT_DELIMITER = TypingConfig.TOKEN_DELIMITER; + private static Pattern PATTERN_TOKEN_STRIP = compileTokenPattern(); + private static final Pattern PATTERN_MARKUP_STRIP = Pattern.compile("(\\[{2})|(\\[#?\\w*(\\[|\\])?)"); + private static final Pattern PATTERN_COLOR_HEX_NO_HASH = Pattern.compile("[A-F0-9]{6}"); private static final String[] BOOLEAN_TRUE = {"true", "yes", "t", "y", "on", "1"}; private static final int INDEX_TOKEN = 1; @@ -69,14 +68,12 @@ private static void parseReplacements(TypingLabel label) { boolean hasMarkup = label.getBitmapFontCache().getFont().getData().markupEnabled; // Create string builder - StringBuilder sb = new StringBuilder(text.length()); Matcher m = PATTERN_TOKEN_STRIP.matcher(text); int matcherIndexOffset = 0; // Iterate through matches while(true) { - // Reset StringBuilder and matcher - sb.setLength(0); + // Reset matcher m.setTarget(text); m.setPosition(matcherIndexOffset); @@ -224,14 +221,12 @@ private static void parseRegularTokens(TypingLabel label) { // Create matcher and StringBuilder Matcher m = PATTERN_TOKEN_STRIP.matcher(text); - StringBuilder sb = new StringBuilder(text.length()); int matcherIndexOffset = 0; // Iterate through matches while(true) { - // Reset matcher and StringBuilder + // Reset matcher m.setTarget(text); - sb.setLength(0); m.setPosition(matcherIndexOffset); // Make sure there's at least one regex match @@ -362,7 +357,7 @@ static float stringToFloat(String str, float defaultValue) { if(str != null) { try { return Float.parseFloat(str); - } catch(Exception e) { + } catch(Exception ignored) { } } return defaultValue; diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/TypingGlyph.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/TypingGlyph.java index f6e5172..7cfb9e0 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/TypingGlyph.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/TypingGlyph.java @@ -2,8 +2,6 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; -import com.badlogic.gdx.graphics.g2d.GlyphLayout; -import com.badlogic.gdx.graphics.g2d.GlyphLayout.GlyphRun; /** Extension of {@link Glyph} with additional data exposed to the user. */ public class TypingGlyph extends Glyph { diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/TypingLabel.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/TypingLabel.java index 3356b89..033a8bb 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/TypingLabel.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/TypingLabel.java @@ -12,8 +12,11 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.scenes.scene2d.utils.Drawable; -import com.badlogic.gdx.utils.*; -import com.badlogic.gdx.utils.StringBuilder; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.CharArray; +import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectMap.Entry; import com.badlogic.gdx.utils.reflect.ClassReflection; @@ -27,16 +30,16 @@ public class TypingLabel extends Label { /////////////////////// // Collections - private final ObjectMap variables = new ObjectMap(); - protected final Array tokenEntries = new Array(); + private final ObjectMap variables = new ObjectMap(); + final Array tokenEntries = new Array(); // Config - private Color clearColor = new Color(TypingConfig.DEFAULT_CLEAR_COLOR); - private final Array listeners = new Array<>(TypingListener.class); + private final Color clearColor = new Color(TypingConfig.DEFAULT_CLEAR_COLOR); + private final Array listeners = new Array<>(TypingListener[]::new); boolean forceMarkupColor = TypingConfig.FORCE_COLOR_MARKUP_BY_DEFAULT; // Internal state - private final StringBuilder originalText = new StringBuilder(); + private final CharArray originalText = new CharArray(); private final Array glyphCache = new Array(); private final IntArray glyphRunCapacities = new IntArray(); private final IntArray offsetCache = new IntArray(); @@ -139,12 +142,12 @@ protected void setText(CharSequence newText, boolean modifyOriginalText, boolean } /** Similar to {@link #getText()}, but returns the original text with all the tokens unchanged. */ - public StringBuilder getOriginalText() { + public CharArray getOriginalText() { return originalText; } /** - * Copies the content of {@link #getText()} to the {@link StringBuilder} containing the original text with all + * Copies the content of {@link #getText()} to the {@link CharArray} containing the original text with all * tokens unchanged. */ protected void saveOriginalText() { @@ -448,16 +451,16 @@ private void processCharProgression() { rawCharIndex++; // Get next character and calculate cooldown increment - int safeIndex = MathUtils.clamp(rawCharIndex, 0, getText().length - 1); + int safeIndex = MathUtils.clamp(rawCharIndex, 0, getText().size - 1); char primitiveChar = '\u0000'; // Null character by default - if(getText().length > 0) { + if(getText().size > 0) { primitiveChar = getText().charAt(safeIndex); float intervalMultiplier = TypingConfig.INTERVAL_MULTIPLIERS_BY_CHAR.get(primitiveChar, 1); charCooldown += textSpeed * intervalMultiplier; } // If char progression is finished, or if text is empty, notify listener and abort routine - int textLen = getText().length; + int textLen = getText().size; if(textLen == 0 || rawCharIndex >= textLen) { if(!ended) { ended = true; @@ -538,7 +541,7 @@ private void processCharProgression() { } // Notify listener about char progression - int nextIndex = MathUtils.clamp(rawCharIndex, 0, getText().length - 1); + int nextIndex = MathUtils.clamp(rawCharIndex, 0, getText().size - 1); Character nextChar = nextIndex == 0 ? null : getText().charAt(nextIndex); if(nextChar != null) { for(TypingListener listener : listeners) { @@ -633,7 +636,7 @@ public void setFontScaleY(float fontScaleY) { public void layout() { // --- SUPERCLASS IMPLEMENTATION --- BitmapFontCache cache = getBitmapFontCache(); - StringBuilder text = getText(); + CharArray text = getText(); GlyphLayout layout = super.getGlyphLayout(); int lineAlign = getLineAlign(); int labelAlign = getLabelAlign(); @@ -666,7 +669,7 @@ public void layout() { float textWidth, textHeight; if(wrap || text.indexOf("\n") != -1) { // If the text can span multiple lines, determine the text's actual size so it can be aligned within the label. - layout.setText(font, text, 0, text.length, Color.WHITE, width, lineAlign, wrap, ellipsis); + layout.setText(font, text, 0, text.size, Color.WHITE, width, lineAlign, wrap, ellipsis); textWidth = layout.width; textHeight = layout.height; @@ -692,7 +695,7 @@ public void layout() { } if(!cache.getFont().isFlipped()) y += textHeight; - layout.setText(font, text, 0, text.length, Color.WHITE, textWidth, lineAlign, wrap, ellipsis); + layout.setText(font, text, 0, text.size, Color.WHITE, textWidth, lineAlign, wrap, ellipsis); cache.setText(layout, x, y); if(fontScaleChanged) font.getData().setScale(oldScaleX, oldScaleY); diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/JumpEffect.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/JumpEffect.java index de1f1c7..a262dbc 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/JumpEffect.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/JumpEffect.java @@ -1,7 +1,6 @@ package com.rafaskoberg.gdx.typinglabel.effects; -import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; import com.badlogic.gdx.math.Interpolation; import com.rafaskoberg.gdx.typinglabel.Effect; import com.rafaskoberg.gdx.typinglabel.TypingGlyph; diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/ShakeEffect.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/ShakeEffect.java index 733ef4d..7efd23a 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/ShakeEffect.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/ShakeEffect.java @@ -1,7 +1,6 @@ package com.rafaskoberg.gdx.typinglabel.effects; -import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.FloatArray; diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/SickEffect.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/SickEffect.java index aea0e8e..ef25517 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/SickEffect.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/SickEffect.java @@ -1,7 +1,6 @@ package com.rafaskoberg.gdx.typinglabel.effects; -import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.utils.IntArray; import com.rafaskoberg.gdx.typinglabel.Effect; diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/WaveEffect.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/WaveEffect.java index 5af9387..845480e 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/WaveEffect.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/WaveEffect.java @@ -1,7 +1,6 @@ package com.rafaskoberg.gdx.typinglabel.effects; -import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; import com.badlogic.gdx.math.Interpolation; import com.rafaskoberg.gdx.typinglabel.Effect; import com.rafaskoberg.gdx.typinglabel.TypingGlyph; diff --git a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/WindEffect.java b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/WindEffect.java index 20bc8d5..ee41248 100644 --- a/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/WindEffect.java +++ b/src/main/java/com/rafaskoberg/gdx/typinglabel/effects/WindEffect.java @@ -15,7 +15,7 @@ public class WindEffect extends Effect { private static final float DISTANCE_Y_RATIO = 1.0f; private static final float IDEAL_DELTA = 60f; - private SimplexNoise noise = new SimplexNoise(1, 0.5f, 1f); + private static final SimplexNoise noise = new SimplexNoise(1, 0.5f, 1f); private float noiseCursorX = 0; private float noiseCursorY = 0; diff --git a/src/test/java/com/rafaskoberg/gdx/typinglabel/StartupHelper.java b/src/test/java/com/rafaskoberg/gdx/typinglabel/StartupHelper.java new file mode 100644 index 0000000..983587c --- /dev/null +++ b/src/test/java/com/rafaskoberg/gdx/typinglabel/StartupHelper.java @@ -0,0 +1,204 @@ +/* + * Copyright 2020 damios + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//Note, the above license and copyright applies to this file only. + +package com.rafaskoberg.gdx.typinglabel; + +import com.badlogic.gdx.Version; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3NativesLoader; +import org.lwjgl.system.macosx.LibC; +import org.lwjgl.system.macosx.ObjCRuntime; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.lang.management.ManagementFactory; +import java.util.ArrayList; + +import static org.lwjgl.system.JNI.invokePPP; +import static org.lwjgl.system.JNI.invokePPZ; +import static org.lwjgl.system.macosx.ObjCRuntime.objc_getClass; +import static org.lwjgl.system.macosx.ObjCRuntime.sel_getUid; + +/** + * Adds some utilities to ensure that the JVM was started with the + * {@code -XstartOnFirstThread} argument, which is required on macOS for LWJGL 3 + * to function. Also helps on Windows when users have names with characters from + * outside the Latin alphabet, a common cause of startup crashes. + *
+ * Based on this java-gaming.org post by kappa + * @author damios + */ +public class StartupHelper { + + private static final String JVM_RESTARTED_ARG = "jvmIsRestarted"; + + private StartupHelper() { + throw new UnsupportedOperationException(); + } + + /** + * Starts a new JVM if the application was started on macOS without the + * {@code -XstartOnFirstThread} argument. This also includes some code for + * Windows, for the case where the user's home directory includes certain + * non-Latin-alphabet characters (without this code, most LWJGL3 apps fail + * immediately for those users). Returns whether a new JVM was started and + * thus no code should be executed. + *

+ * Usage: + * + *


+     * public static void main(String... args) {
+     * 	if (StartupHelper.startNewJvmIfRequired(true)) return; // This handles macOS support and helps on Windows.
+     * 	// after this is the actual main method code
+     * }
+     * 
+ * + * @param redirectOutput + * whether the output of the new JVM should be rerouted to the + * old JVM, so it can be accessed in the same place; keeps the + * old JVM running if enabled + * @return whether a new JVM was started and thus no code should be executed + * in this one + */ + public static boolean startNewJvmIfRequired(boolean redirectOutput) { + String osName = System.getProperty("os.name").toLowerCase(); + if (!osName.contains("mac")) { + if (osName.contains("windows")) { +// Here, we are trying to work around an issue with how LWJGL3 loads its extracted .dll files. +// By default, LWJGL3 extracts to the directory specified by "java.io.tmpdir", which is usually the user's home. +// If the user's name has non-ASCII (or some non-alphanumeric) characters in it, that would fail. +// By extracting to the relevant "ProgramData" folder, which is usually "C:\ProgramData", we avoid this. +// We also temporarily change the "user.name" property to one without any chars that would be invalid. +// We revert our changes immediately after loading LWJGL3 natives. + String programData = System.getenv("ProgramData"); + if(programData == null) programData = "C:\\Temp\\"; // if ProgramData isn't set, try some fallback. + String prevTmpDir = System.getProperty("java.io.tmpdir", programData); + String prevUser = System.getProperty("user.name", "libGDX_User"); + System.setProperty("java.io.tmpdir", programData + "/libGDX-temp"); + System.setProperty("user.name", ("User_" + prevUser.hashCode() + "_GDX" + Version.VERSION).replace('.', '_')); + Lwjgl3NativesLoader.load(); + System.setProperty("java.io.tmpdir", prevTmpDir); + System.setProperty("user.name", prevUser); + } + return false; + } + + // There is no need for -XstartOnFirstThread on Graal native image + if (!System.getProperty("org.graalvm.nativeimage.imagecode", "").isEmpty()) { + return false; + } + + // Checks if we are already on the main thread, such as from running via Construo. + long objc_msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend"); + long NSThread = objc_getClass("NSThread"); + long currentThread = invokePPP(NSThread, sel_getUid("currentThread"), objc_msgSend); + boolean isMainThread = invokePPZ(currentThread, sel_getUid("isMainThread"), objc_msgSend); + if(isMainThread) return false; + + long pid = LibC.getpid(); + + // check whether -XstartOnFirstThread is enabled + if ("1".equals(System.getenv("JAVA_STARTED_ON_FIRST_THREAD_" + pid))) { + return false; + } + + // check whether the JVM was previously restarted + // avoids looping, but most certainly leads to a crash + if ("true".equals(System.getProperty(JVM_RESTARTED_ARG))) { + System.err.println( + "There was a problem evaluating whether the JVM was started with the -XstartOnFirstThread argument."); + return false; + } + + // Restart the JVM with -XstartOnFirstThread + ArrayList jvmArgs = new ArrayList<>(); + String separator = System.getProperty("file.separator", "/"); + // The following line is used assuming you target Java 8, the minimum for LWJGL3. + String javaExecPath = System.getProperty("java.home") + separator + "bin" + separator + "java"; + // If targeting Java 9 or higher, you could use the following instead of the above line: + //String javaExecPath = ProcessHandle.current().info().command().orElseThrow(); + + if (!(new File(javaExecPath)).exists()) { + System.err.println( + "A Java installation could not be found. If you are distributing this app with a bundled JRE, be sure to set the -XstartOnFirstThread argument manually!"); + return false; + } + + jvmArgs.add(javaExecPath); + jvmArgs.add("-XstartOnFirstThread"); + jvmArgs.add("-D" + JVM_RESTARTED_ARG + "=true"); + jvmArgs.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); + jvmArgs.add("-cp"); + jvmArgs.add(System.getProperty("java.class.path")); + String mainClass = System.getenv("JAVA_MAIN_CLASS_" + pid); + if (mainClass == null) { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + if (trace.length > 0) { + mainClass = trace[trace.length - 1].getClassName(); + } else { + System.err.println("The main class could not be determined."); + return false; + } + } + jvmArgs.add(mainClass); + + try { + if (!redirectOutput) { + ProcessBuilder processBuilder = new ProcessBuilder(jvmArgs); + processBuilder.start(); + } else { + Process process = (new ProcessBuilder(jvmArgs)) + .redirectErrorStream(true).start(); + BufferedReader processOutput = new BufferedReader( + new InputStreamReader(process.getInputStream())); + String line; + + while ((line = processOutput.readLine()) != null) { + System.out.println(line); + } + + process.waitFor(); + } + } catch (Exception e) { + System.err.println("There was a problem restarting the JVM"); + e.printStackTrace(); + } + + return true; + } + + /** + * Starts a new JVM if the application was started on macOS without the + * {@code -XstartOnFirstThread} argument. Returns whether a new JVM was + * started and thus no code should be executed. Redirects the output of the + * new JVM to the old one. + *

+ * Usage: + * + *

+     * public static void main(String... args) {
+     * 	if (StartupHelper.startNewJvmIfRequired()) return; // This handles macOS support and helps on Windows.
+     * 	// the actual main method code
+     * }
+     * 
+ * + * @return whether a new JVM was started and thus no code should be executed + * in this one + */ + public static boolean startNewJvmIfRequired() { + return startNewJvmIfRequired(true); + } +} \ No newline at end of file diff --git a/src/test/java/com/rafaskoberg/gdx/typinglabel/TypingLabelTest.java b/src/test/java/com/rafaskoberg/gdx/typinglabel/TypingLabelTest.java index 773c415..7e665e5 100644 --- a/src/test/java/com/rafaskoberg/gdx/typinglabel/TypingLabelTest.java +++ b/src/test/java/com/rafaskoberg/gdx/typinglabel/TypingLabelTest.java @@ -2,8 +2,8 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.backends.lwjgl.LwjglApplication; -import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture.TextureFilter; @@ -234,18 +234,15 @@ public void dispose() { } public static void main(String[] arg) { - LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); - config.title = "TypingLabel Test"; - config.width = 720; - config.height = 405; - config.depth = 16; - config.fullscreen = false; - config.resizable = false; - config.foregroundFPS = 60; - config.backgroundFPS = 60; - config.forceExit = false; - - new LwjglApplication(new TypingLabelTest(), config); + if(StartupHelper.startNewJvmIfRequired(true)) return; + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + config.setTitle("TypingLabel Test"); + config.setWindowedMode(720, 405); + config.setResizable(false); + config.setForegroundFPS(60); + config.setIdleFPS(60); + + new Lwjgl3Application(new TypingLabelTest(), config); } }