From 031f5e5aa770cab729fb970b6a4a3d45f027e8ed Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Wed, 27 May 2026 12:58:16 +0700 Subject: [PATCH 01/71] Initial work on switching to Rivet. --- README.md | 2 +- core/build.gradle.kts | 5 +- .../java/oxy/bascenario/EngineRenderer.java | 114 ++++++++++++++++++ .../bascenario/utils/ExtendableScreen.java | 4 + .../base/fonts/rivet/SFUIText-Regular.ttf | Bin 0 -> 292232 bytes editor/build.gradle.kts | 5 +- gradle/libs.versions.toml | 7 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 50 +++++--- gradlew.bat | 59 ++++----- launcher/build.gradle.kts | 4 +- 11 files changed, 192 insertions(+), 60 deletions(-) create mode 100644 core/src/main/resources/assets/base/fonts/rivet/SFUIText-Regular.ttf diff --git a/README.md b/README.md index 11d18767..0e70b6af 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ If you want to download the jar file to run this, just download in [releases](ht or alternatively in [actions](https://github.com/oryxel1/Base/actions) sections. ## Usage and examples -Download **Java 21** or above, and then run `java -jar Base.jar` (or whatever the jar file is called). +Download **Java 25** or above, and then run `java -jar Base.jar` (or whatever the jar file is called).

For example of how to use the program, you can look at the [wiki](https://github.com/oryxel1/Base/wiki). ## Contact diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e1a12440..77035ec4 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { implementation(project(":api")) implementation(project(":serializer")) - implementation(variantOf(libs.raphimc.thingl) { classifier("java17") }) + implementation(libs.raphimc.thingl) implementation(libs.commons.animations) implementation(libs.unnamed.mocha) @@ -56,4 +56,7 @@ dependencies { } implementation("com.badlogicgames.gdx:gdx-platform:$libGdx:natives-desktop") + + implementation(libs.rivet.core) + implementation(libs.rivet.thingl.backend) } \ No newline at end of file diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index 1cea0eab..50898509 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -14,9 +14,24 @@ import imgui.gl3.ImGuiImplGl3; import imgui.glfw.ImGuiImplGlfw; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import net.lenni0451.rivet.Rivet; +import net.lenni0451.rivet.backend.thingl.ThinGLBackend; +import net.lenni0451.rivet.backend.thingl.render.ThinGLRenderer; +import net.lenni0451.rivet.backend.thingl.util.GLFWMapper; +import net.lenni0451.rivet.input.keyboard.CharEvent; +import net.lenni0451.rivet.input.keyboard.KeyEvent; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.input.mouse.MouseScrollEvent; +import net.lenni0451.rivet.layout.fullsize.FullSizeLayout; +import net.lenni0451.rivet.math.Size; import net.raphimc.thingl.ThinGL; +import net.raphimc.thingl.resource.font.impl.FreeTypeFont; +import net.raphimc.thingl.text.font.FontSet; import org.joml.Matrix4fStack; import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWFramebufferSizeCallback; import oxy.bascenario.managers.AudioManager; import oxy.bascenario.screens.ScenarioScreen; import oxy.bascenario.utils.*; @@ -29,6 +44,10 @@ @RequiredArgsConstructor public final class EngineRenderer extends Game { + private FontSet fontSet; + private ThinGLBackend backend; + private Rivet rivet; + private final ImGuiImplGlfw imGuiGlfw = new ImGuiImplGlfw(); private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3(); @@ -37,6 +56,7 @@ public final class EngineRenderer extends Game { public double mouseX, mouseY; + @SneakyThrows @Override public void create() { long windowHandle = ((Lwjgl3Graphics) Gdx.graphics).getWindow().getWindowHandle(); @@ -71,6 +91,12 @@ public void create() { } }); + setupRivetCallbacks(); + + this.fontSet = new FontSet(new FreeTypeFont(EngineRenderer.class.getResourceAsStream("/assets/base/fonts/rivet/SFUIText-Regular.ttf").readAllBytes(), 40)); + this.backend = new ThinGLBackend(windowHandle, this.fontSet); + this.rivet = new Rivet(this.backend, FullSizeLayout.INSTANCE, new Size(ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight())); + ImGui.createContext(); ImPlot.createContext(); final ImGuiIO data = ImGui.getIO(); @@ -103,6 +129,10 @@ public void setScreen(Screen screen) { ScenarioScreen.RENDER_WITHIN_IMGUI = !(screen instanceof ScenarioScreen); TimeUtils.fakeTimeMillis = null; super.setScreen(screen); + + if (screen instanceof ExtendableScreen extendableScreen) { + extendableScreen.init(this.rivet); + } } private boolean someTempHackyBool; @@ -115,9 +145,18 @@ public void render() { AudioManager.getInstance().tick(); ThinGLUtils.GLOBAL_RENDER_STACK = new Matrix4fStack(8); + float x = ThinGL.windowInterface().getFramebufferWidth() / 1920F; ThinGLUtils.GLOBAL_RENDER_STACK.scale(x, ThinGL.windowInterface().getFramebufferHeight() / 1080F, x); + ThinGLUtils.start(); + ThinGL.programs().getMsaa().bindInput(); + ThinGLRenderer.renderList(new Matrix4fStack(8), this.rivet.render()); + ThinGL.programs().getMsaa().unbindInput(); + ThinGL.programs().getMsaa().renderFullscreen(); + ThinGL.programs().getMsaa().clearInput(); + ThinGLUtils.end(); + ImGuiUtils.COUNTER = 0; imGuiGl3.newFrame(); imGuiGlfw.newFrame(); @@ -126,8 +165,10 @@ public void render() { if (ImGui.getStyle().getFontScaleDpi() != 1) { ImGui.pushFont(FontUtils.IM_FONT_REGULAR, 17); } + super.render(); + if (ImGui.getStyle().getFontScaleDpi() != 1) { ImGui.popFont(); } @@ -175,4 +216,77 @@ public void dispose() { ImPlot.destroyContext(); ImGui.destroyContext(); } + + private void setupRivetCallbacks() { + long windowHandle = ((Lwjgl3Graphics) Gdx.graphics).getWindow().getWindowHandle(); + + GLFW.glfwSetCursorPosCallback(windowHandle, (_, xpos, ypos) -> { + float[] mouseScale = this.getMouseScale(); + this.rivet.onMouseMove(new MouseMoveEvent((float) xpos * mouseScale[0], (float) ypos * mouseScale[1])); + }); + GLFW.glfwSetMouseButtonCallback(windowHandle, (window, button, action, mods) -> { + float[] mouseScale = this.getMouseScale(); + final double[] xpos = new double[1]; + final double[] ypos = new double[1]; + GLFW.glfwGetCursorPos(window, xpos, ypos); + + MouseButtonEvent event = GLFWMapper.mapMouseButton((float) xpos[0] * mouseScale[0], (float) ypos[0] * mouseScale[1], button, mods); + if (event != null) { + if (action == GLFW.GLFW_PRESS) { + this.rivet.onMouseDown(event); + } else if (action == GLFW.GLFW_RELEASE) { + this.rivet.onMouseUp(event); + } + } + }); + GLFW.glfwSetScrollCallback(windowHandle, (window, xoffset, yoffset) -> { + float[] mouseScale = this.getMouseScale(); + final double[] xpos = new double[1]; + final double[] ypos = new double[1]; + GLFW.glfwGetCursorPos(window, xpos, ypos); + this.rivet.onMouseScroll(new MouseScrollEvent((float) xpos[0] * mouseScale[0], (float) ypos[0] * mouseScale[1], (float) xoffset, (float) yoffset)); + }); + GLFW.glfwSetKeyCallback(windowHandle, (_, key, _, action, mods) -> { + KeyEvent event = GLFWMapper.mapKey(key, mods); + if (event != null) { + if (action == GLFW.GLFW_PRESS) { + this.rivet.onKeyDown(event); + } else if (action == GLFW.GLFW_RELEASE) { + this.rivet.onKeyUp(event); + } else if (action == GLFW.GLFW_REPEAT) { + this.rivet.onKeyDown(event); + } + } + }); + GLFW.glfwSetCharCallback(windowHandle, (_, codepoint) -> { + if (Character.isBmpCodePoint(codepoint)) { + this.rivet.onCharTyped(new CharEvent((char) codepoint)); + } else if (Character.isValidCodePoint(codepoint)) { + this.rivet.onCharTyped(new CharEvent(Character.highSurrogate(codepoint))); + this.rivet.onCharTyped(new CharEvent(Character.lowSurrogate(codepoint))); + } + }); + GLFWFramebufferSizeCallback[] oldCallback = new GLFWFramebufferSizeCallback[1]; + oldCallback[0] = GLFW.glfwSetFramebufferSizeCallback(windowHandle, (window, width, height) -> { + if (oldCallback[0] != null) oldCallback[0].invoke(window, width, height); + this.rivet.size(new Size(width, height)); + }); + GLFW.glfwSetWindowFocusCallback(windowHandle, (window, focused) -> { + if (!focused) { + this.rivet.unfocus(); + } + }); + } + + private float[] getMouseScale() { + long windowHandle = ((Lwjgl3Graphics) Gdx.graphics).getWindow().getWindowHandle(); + + int[] windowSizeX = new int[1]; + int[] windowSizeY = new int[1]; + GLFW.glfwGetWindowSize(windowHandle, windowSizeX, windowSizeY); + int[] framebufferSizeX = new int[1]; + int[] framebufferSizeY = new int[1]; + GLFW.glfwGetFramebufferSize(windowHandle, framebufferSizeX, framebufferSizeY); + return new float[]{(float) framebufferSizeX[0] / windowSizeX[0], (float) framebufferSizeY[0] / windowSizeY[0]}; + } } diff --git a/core/src/main/java/oxy/bascenario/utils/ExtendableScreen.java b/core/src/main/java/oxy/bascenario/utils/ExtendableScreen.java index 2ee4f67f..074782c4 100644 --- a/core/src/main/java/oxy/bascenario/utils/ExtendableScreen.java +++ b/core/src/main/java/oxy/bascenario/utils/ExtendableScreen.java @@ -1,12 +1,16 @@ package oxy.bascenario.utils; import com.badlogic.gdx.Screen; +import net.lenni0451.rivet.Rivet; public class ExtendableScreen implements Screen { @Override public void show() { } + public void init(Rivet rivet) { + } + @Override public void render(float delta) { } diff --git a/core/src/main/resources/assets/base/fonts/rivet/SFUIText-Regular.ttf b/core/src/main/resources/assets/base/fonts/rivet/SFUIText-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..13b1295f9b6e208e9abc5ceb75629fe979240e8e GIT binary patch literal 292232 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%0Aj!eLbV2t0QWWXh-%?|tq71$=g=-%KOctoU& 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 IcHgc4ALWVPzyJUM literal 0 HcmV?d00001 diff --git a/editor/build.gradle.kts b/editor/build.gradle.kts index 39a2fd3a..470609a0 100644 --- a/editor/build.gradle.kts +++ b/editor/build.gradle.kts @@ -13,11 +13,12 @@ dependencies { implementation(libs.lwjgl.glfw) implementation(libs.lwjgl.nfd) - implementation(variantOf(libs.raphimc.thingl) { classifier("java17") }) + implementation(libs.raphimc.thingl) implementation(libs.raphimc.audiomixer) implementation(libs.jcraft.jorbis) implementation(libs.javazoom.jlayer) -// implementation(libs.jthink.jaudiotagger) + implementation(libs.rivet.core) + implementation(libs.rivet.thingl.backend) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8428ed1e..bcacc98c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,12 @@ [versions] -lombok = "1.18.38" +lombok = "1.18.46" gson = "2.13.1" lwjgl = "3.4.0" lwjgl-sdl = "3.4.0" libgdx = "1.13.1" spine = "4.2.5" thingl = "0.1.1-SNAPSHOT" +rivet = "0.0.1-SNAPSHOT" commons = "1.9.2" mocha = "3.0.1" configurate = "4.2.0" @@ -13,7 +14,6 @@ imgui = "1.92.0" audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" -#netty = "4.2.9.Final" zt-zip = "1.17" [libraries] @@ -49,6 +49,9 @@ gdx-spine = { module = "com.esotericsoftware.spine:spine-libgdx", version.ref = raphimc-thingl = { module = "net.raphimc:thingl", version.ref = "thingl" } raphimc-audiomixer = { module = "net.raphimc:audio-mixer", version.ref = "audiomixer" } +rivet-core = { module = "net.lenni0451.rivet:core", version.ref = "rivet" } +rivet-thingl-backend = { module = "net.lenni0451.rivet:backend-thingl-glfw", version.ref = "rivet" } + commons-animations = { module = "net.lenni0451.commons:animation", version.ref = "commons" } commons-core = { module = "net.lenni0451.commons:core", version.ref = "commons" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 17dea80b..efdb35ff 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Nov 16 11:40:51 ICT 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c7873..b9bb139f 100644 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original 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,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/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/. @@ -80,13 +82,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${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"' +# 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 @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -133,22 +132,29 @@ 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. + 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" && ! "$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 @@ -165,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# 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" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -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. diff --git a/gradlew.bat b/gradlew.bat index 107acd32..24c62d56 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,19 +13,22 @@ @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 @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions 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% @@ -40,15 +43,15 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +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 +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -56,34 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe 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 +"%COMSPEC%" /c exit 1 :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 %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="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 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/launcher/build.gradle.kts b/launcher/build.gradle.kts index c17b4607..b6f16312 100644 --- a/launcher/build.gradle.kts +++ b/launcher/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java") - id("com.gradleup.shadow") version("8.3.0") + id("com.gradleup.shadow") version("9.4.1") } dependencies { @@ -18,7 +18,7 @@ dependencies { implementation(libs.lwjgl.glfw) implementation(libs.lwjgl.nfd) - implementation(variantOf(libs.raphimc.thingl) { classifier("java17") }) + implementation(libs.raphimc.thingl) implementation(libs.raphimc.audiomixer) implementation(libs.commons.animations) From a7e05c1a9b860eaf3f73467481b56fcc5f935167 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Wed, 27 May 2026 13:04:43 +0700 Subject: [PATCH 02/71] rivet test. --- .../java/oxy/bascenario/EngineRenderer.java | 5 +- core/src/test/java/RivetTest.java | 106 ++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 core/src/test/java/RivetTest.java diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index 50898509..afba0925 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -24,7 +24,8 @@ import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.input.mouse.MouseScrollEvent; -import net.lenni0451.rivet.layout.fullsize.FullSizeLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.math.Size; import net.raphimc.thingl.ThinGL; import net.raphimc.thingl.resource.font.impl.FreeTypeFont; @@ -95,7 +96,7 @@ public void create() { this.fontSet = new FontSet(new FreeTypeFont(EngineRenderer.class.getResourceAsStream("/assets/base/fonts/rivet/SFUIText-Regular.ttf").readAllBytes(), 40)); this.backend = new ThinGLBackend(windowHandle, this.fontSet); - this.rivet = new Rivet(this.backend, FullSizeLayout.INSTANCE, new Size(ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight())); + this.rivet = new Rivet(this.backend, AnchorLayout.INSTANCE, new Size(ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight())); ImGui.createContext(); ImPlot.createContext(); diff --git a/core/src/test/java/RivetTest.java b/core/src/test/java/RivetTest.java new file mode 100644 index 00000000..8404ad8d --- /dev/null +++ b/core/src/test/java/RivetTest.java @@ -0,0 +1,106 @@ +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.Rivet; +import net.lenni0451.rivet.component.Container; +import net.lenni0451.rivet.component.base.Button; +import net.lenni0451.rivet.component.base.ComboBox; +import net.lenni0451.rivet.component.base.DecoratedContainer; +import net.lenni0451.rivet.component.base.ScrollContainer; +import net.lenni0451.rivet.component.impl.*; +import net.lenni0451.rivet.component.impl.graphics.SolidColor; +import net.lenni0451.rivet.component.impl.slider.Slider; +import net.lenni0451.rivet.input.keyboard.Key; +import net.lenni0451.rivet.layout.grid.GridAnchor; +import net.lenni0451.rivet.layout.grid.GridFill; +import net.lenni0451.rivet.layout.grid.GridLayout; +import net.lenni0451.rivet.layout.grid.GridLayoutOptions; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Padding; +import net.lenni0451.rivet.text.model.TextOrigin; +import net.lenni0451.rivet.theme.Theme; +import net.lenni0451.rivet.theme.impl.DefaultDark; +import oxy.bascenario.utils.ExtendableScreen; +import oxy.bascenario.utils.Launcher; + +public class RivetTest { + public static void main(String[] args) { + Launcher.launch(new ExtendableScreen() { + @Override + public void init(Rivet rivet) { + rivet.theme(new DefaultDark() { + @Override + protected void addValues(final Rivet rivet, final Values values) { + super.addValues(rivet, values); + values.put(Theme.BUTTON_INACTIVE_COLOR, Color.GRAY.withAlpha(50)); + values.put(Theme.BUTTON_INACTIVE_OUTLINE_COLOR, Color.BLACK); + values.put(Theme.BUTTON_ACTIVE_COLOR, Color.GRAY.withAlpha(150)); + values.put(Theme.BUTTON_ACTIVE_OUTLINE_COLOR, Color.fromRGB(116, 165, 229)); + values.put(Theme.BUTTON_CLICK_COLOR, Color.GRAY.withAlpha(150).darker()); + values.put(Theme.BUTTON_CLICK_OUTLINE_COLOR, Color.fromRGB(116, 165, 229).darker()); + values.put(Theme.BUTTON_CORNER_RADIUS, 0F); + values.put(Theme.BUTTON_OUTLINE_WIDTH, 3F); + values.put(Theme.SCROLL_BAR_TYPE, ScrollContainer.ScrollBarType.NORMAL); + } + }); + + + Container container = new Container(rivet, new GridLayout(10, 10).withHomogeneousColumns(true).withShrinkColumns(true)); + rivet.root().addChild(new DecoratedContainer(rivet, new SolidColor(rivet, solidColor -> { + solidColor.color(Color.fromARGB(Integer.MIN_VALUE)); + solidColor.cornerRadius(20F); + solidColor.outlineColor(Color.GREEN); + }), new ScrollContainer(rivet, container, false, true)), decoratedContainer -> { + decoratedContainer.innerPadding(new Padding(20, 20, 20, 20)); + }); + container.addChild(new FormattedLabel(rivet, "Hello this is a really cool test string how are you doing lol"), label -> { + label.horizontalOrigin(TextOrigin.Horizontal.VISUAL_LEFT); + label.layoutOptions(new GridLayoutOptions(0, 0).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + }); + container.addChild(new Button(rivet, new Label(rivet, "Singleplayer"), _ -> {}), button -> { + button.layoutOptions(new GridLayoutOptions(0, 1).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + }); + container.addChild(new Button(rivet, new Label(rivet, "Minecraft Realms"), _ -> {}), button -> { + button.layoutOptions(new GridLayoutOptions(0, 2).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL)); + }); + container.addChild(new Button(rivet, new Label(rivet, "Multiplayer"), _ -> {}), button -> { + button.layoutOptions(new GridLayoutOptions(1, 2).withAnchor(GridAnchor.EAST).withWeightX(1).withFill(GridFill.HORIZONTAL)); + }); + container.addChild(new Button(rivet, new Label(rivet, "Whatever"), _ -> {}), button -> { + button.layoutOptions(new GridLayoutOptions(0, 3).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + }); + container.addChild(new Button(rivet, new Label(rivet, "Options..."), _ -> {}), button -> { + button.layoutOptions(new GridLayoutOptions(0, 4).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withPadding(Padding.EMPTY.withTop(20))); + }); + container.addChild(new Button(rivet, new Label(rivet, "Quit Game"), _ -> {}), button -> { + button.layoutOptions(new GridLayoutOptions(1, 4).withAnchor(GridAnchor.EAST).withWeightX(1).withFill(GridFill.HORIZONTAL).withPadding(Padding.EMPTY.withTop(20))); + }); + container.addChild(new ComboBox(rivet, "Testing since 2k26", new DecoratedContainer(rivet, new SolidColor(rivet, solidColor -> { + solidColor.color(Color.GREEN); + }), new ScrollContainer(rivet, new Container(rivet, new VerticalListLayout(5, true)), comboBoxContainer -> { + for (int i = 0; i < 10; i++) { + comboBoxContainer.addChild(new Button(rivet, new Label(rivet, "Test " + i), _ -> {})); + } + }))), comboBox -> comboBox.layoutOptions(new GridLayoutOptions(0, 5).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2))); + container.addChild(new TextField(rivet), textField -> { + textField.layoutOptions(new GridLayoutOptions(0, 6).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + textField.keyDownListener().add(event -> { + if (event.key().isEquivalent(Key.ENTER)) { + System.out.println(textField.text()); + return true; + } + return false; + }); + }); + container.addChild(new Checkbox(rivet, "Test Test Test", true), checkbox -> { + checkbox.layoutOptions(new GridLayoutOptions(0, 7).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + checkbox.cornerRadius().set(8F); + }); + container.addChild(new Slider(rivet, 0, 100, 1), slider -> { + slider.layoutOptions(new GridLayoutOptions(0, 8).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + }); + container.addChild(new ColorPicker(rivet, Color.RED), colorPicker -> { + colorPicker.layoutOptions(new GridLayoutOptions(0, 9).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + }); + } + }, false); + } +} From 723cbc800ac41646343e37563ad5a1053ea6fa42 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Wed, 27 May 2026 14:56:58 +0700 Subject: [PATCH 03/71] yeeted imgui for now. --- core/build.gradle.kts | 6 - .../java/oxy/bascenario/EngineRenderer.java | 83 +---- .../bascenario/screens/ScenarioScreen.java | 17 +- .../renderer/element/SpriteRenderer.java | 23 +- .../java/oxy/bascenario/utils/ImGuiUtils.java | 234 ------------ .../oxy/bascenario/utils/font/FontUtils.java | 35 -- .../src/main/resources/assets/base/base.imgui | 73 ---- editor/build.gradle.kts | 3 - .../editor/inspector/Inspector.java | 206 ----------- .../impl/events/AnimationInspector.java | 40 --- .../impl/events/BackgroundInspector.java | 30 -- .../inspector/impl/events/ColorInspector.java | 36 -- .../impl/events/DialogueInspector.java | 96 ----- .../impl/events/ElementEffectInspector.java | 68 ---- .../inspector/impl/events/LogInspector.java | 58 --- .../impl/events/OptionsInspector.java | 39 -- .../impl/events/PositionInspector.java | 43 --- .../inspector/impl/events/SoundInspector.java | 70 ---- .../impl/events/TransitionInspector.java | 31 -- .../impl/objects/EmoticonInspector.java | 14 - .../impl/objects/ImageInspector.java | 63 ---- .../impl/objects/LocationInfoInspector.java | 15 - .../impl/objects/PreviewInspector.java | 35 -- .../impl/objects/ShapeInspector.java | 24 -- .../impl/objects/SpriteInspector.java | 35 -- .../inspector/impl/objects/TextInspector.java | 34 -- .../impl/other/TextSegmentInspector.java | 90 ----- .../bascenario/editor/miniuis/ActionsUI.java | 286 --------------- .../bascenario/editor/miniuis/AssetsUI.java | 152 -------- .../bascenario/editor/miniuis/ObjectsUI.java | 135 ------- .../screen/BaseScenarioEditorScreen.java | 166 --------- .../editor/screen/ScenarioEditorScreen.java | 144 -------- .../editor/timeline/ObjectDragDrop.java | 36 -- .../editor/timeline/ObjectOrEvent.java | 26 -- .../editor/timeline/ObjectRenderer.java | 256 ------------- .../bascenario/editor/timeline/Timeline.java | 338 ------------------ .../bascenario/editor/utils/AudioUtils.java | 26 -- .../bascenario/editor/utils/NameUtils.java | 88 ----- .../editor/utils/SelectionUtils.java | 14 - .../editor/utils/SoundAsElement.java | 9 - .../bascenario/editor/utils/TimeCompiler.java | 158 -------- .../bascenario/editor/utils/TrackParser.java | 174 --------- gradle/libs.versions.toml | 7 - launcher/build.gradle.kts | 3 - .../bascenario/screen/ScenarioListScreen.java | 315 ++++++++-------- 45 files changed, 173 insertions(+), 3661 deletions(-) delete mode 100644 core/src/main/java/oxy/bascenario/utils/ImGuiUtils.java delete mode 100644 core/src/main/resources/assets/base/base.imgui delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/Inspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/AnimationInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/BackgroundInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/ColorInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/DialogueInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/ElementEffectInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/LogInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/OptionsInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/PositionInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/SoundInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/TransitionInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/EmoticonInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/ImageInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/LocationInfoInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/PreviewInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/ShapeInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/SpriteInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/TextInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/inspector/impl/other/TextSegmentInspector.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/miniuis/ActionsUI.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/miniuis/AssetsUI.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/miniuis/ObjectsUI.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/screen/BaseScenarioEditorScreen.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/screen/ScenarioEditorScreen.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/ObjectDragDrop.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/ObjectOrEvent.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/ObjectRenderer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/Timeline.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/utils/AudioUtils.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/utils/NameUtils.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/utils/SelectionUtils.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/utils/SoundAsElement.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/utils/TimeCompiler.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/utils/TrackParser.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 77035ec4..254fc16c 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -14,12 +14,6 @@ dependencies { implementation(libs.commons.animations) implementation(libs.unnamed.mocha) - implementation(libs.imgui.lwjgl3) - implementation(libs.imgui.binding) - implementation(libs.imgui.natives.windows) - implementation(libs.imgui.natives.linux) - implementation(libs.imgui.natives.macos) - implementation(libs.lwjgl.base) implementation(libs.lwjgl.opengl) implementation(libs.lwjgl.stb) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index afba0925..c0720dc7 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -6,13 +6,6 @@ import com.badlogic.gdx.Screen; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Graphics; import com.badlogic.gdx.utils.ScreenUtils; -import imgui.ImGui; -import imgui.ImGuiIO; -import imgui.extension.implot.ImPlot; -import imgui.flag.ImGuiConfigFlags; -import imgui.flag.ImGuiKey; -import imgui.gl3.ImGuiImplGl3; -import imgui.glfw.ImGuiImplGlfw; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import net.lenni0451.rivet.Rivet; @@ -24,7 +17,6 @@ import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.input.mouse.MouseScrollEvent; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.math.Size; import net.raphimc.thingl.ThinGL; @@ -40,18 +32,11 @@ import oxy.bascenario.utils.thingl.ThinGLExtended; import oxy.bascenario.utils.thingl.ThinGLUtils; -import java.io.File; -import java.io.IOException; - @RequiredArgsConstructor public final class EngineRenderer extends Game { - private FontSet fontSet; private ThinGLBackend backend; private Rivet rivet; - private final ImGuiImplGlfw imGuiGlfw = new ImGuiImplGlfw(); - private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3(); - private final Screen initialScreen; private final boolean initialFullScreen; @@ -94,28 +79,9 @@ public void create() { setupRivetCallbacks(); - this.fontSet = new FontSet(new FreeTypeFont(EngineRenderer.class.getResourceAsStream("/assets/base/fonts/rivet/SFUIText-Regular.ttf").readAllBytes(), 40)); - this.backend = new ThinGLBackend(windowHandle, this.fontSet); + this.backend = new ThinGLBackend(windowHandle, new FontSet(new FreeTypeFont(EngineRenderer.class.getResourceAsStream("/assets/base/fonts/rivet/SFUIText-Regular.ttf").readAllBytes(), 40))); this.rivet = new Rivet(this.backend, AnchorLayout.INSTANCE, new Size(ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight())); - ImGui.createContext(); - ImPlot.createContext(); - final ImGuiIO data = ImGui.getIO(); - data.setIniFilename("base.imgui"); - data.setFontGlobalScale(1F); - data.setConfigFlags(ImGuiConfigFlags.DockingEnable); - imGuiGlfw.init(windowHandle, true); - imGuiGl3.init(); - - if (!new File("base.imgui").exists()) { - try { - ImGui.loadIniSettingsFromMemory(new String(EngineRenderer.class.getResourceAsStream("/assets/base/base.imgui").readAllBytes())); - } catch (IOException ignored) { - } - } - - ImGuiUtils.setupTheme(); - FontUtils.loadFonts(); this.setScreen(this.initialScreen); @@ -127,7 +93,6 @@ public void create() { @Override public void setScreen(Screen screen) { - ScenarioScreen.RENDER_WITHIN_IMGUI = !(screen instanceof ScenarioScreen); TimeUtils.fakeTimeMillis = null; super.setScreen(screen); @@ -158,39 +123,9 @@ public void render() { ThinGL.programs().getMsaa().clearInput(); ThinGLUtils.end(); - ImGuiUtils.COUNTER = 0; - imGuiGl3.newFrame(); - imGuiGlfw.newFrame(); - ImGui.newFrame(); - - if (ImGui.getStyle().getFontScaleDpi() != 1) { - ImGui.pushFont(FontUtils.IM_FONT_REGULAR, 17); - } - super.render(); - - if (ImGui.getStyle().getFontScaleDpi() != 1) { - ImGui.popFont(); - } - - ImGui.render(); - - imGuiGl3.renderDrawData(ImGui.getDrawData()); - -// if (GLFW.glfwGetWindowAttrib(((Lwjgl3Graphics) Gdx.graphics).getWindow().getWindowHandle(), GLFW.GLFW_MAXIMIZED) == 1) { -// int width = ThinGL.windowInterface().getFramebufferWidth(), height = ThinGL.windowInterface().getFramebufferHeight(); -//// System.out.println(width/(float)height); -// if (width/(float)height != 16/9f) { -// boolean widthLargerHeight = ThinGL.windowInterface().getFramebufferWidth() > ThinGL.windowInterface().getFramebufferHeight(); -// int fixedWidth = !widthLargerHeight ? width : (int) (height * 16/9f); -// int fixedHeight = !widthLargerHeight ? (int) (width * (9/16f)) : height; -// -// Gdx.graphics.setWindowedMode(fixedWidth, fixedHeight); -// } -// } - - if (ImGui.isKeyReleased(ImGuiKey.F11) || someTempHackyBool) { + if (/*ImGui.isKeyReleased(ImGuiKey.F11) || */ someTempHackyBool) { if (this.fullScreen) { Gdx.graphics.setUndecorated(false); Gdx.graphics.setWindowedMode(1280, 720); @@ -204,20 +139,6 @@ public void render() { } } - @Override - public void resize(int width, int height) { - super.resize(width, height); - } - - @Override - public void dispose() { - super.dispose(); - imGuiGlfw.shutdown(); - imGuiGl3.shutdown(); - ImPlot.destroyContext(); - ImGui.destroyContext(); - } - private void setupRivetCallbacks() { long windowHandle = ((Lwjgl3Graphics) Gdx.graphics).getWindow().getWindowHandle(); diff --git a/core/src/main/java/oxy/bascenario/screens/ScenarioScreen.java b/core/src/main/java/oxy/bascenario/screens/ScenarioScreen.java index 0c8b1fa0..fbe38abd 100644 --- a/core/src/main/java/oxy/bascenario/screens/ScenarioScreen.java +++ b/core/src/main/java/oxy/bascenario/screens/ScenarioScreen.java @@ -1,7 +1,5 @@ package oxy.bascenario.screens; -import imgui.ImGui; -import imgui.flag.ImGuiKey; import lombok.Getter; import lombok.Setter; import net.lenni0451.commons.animation.easing.EasingFunction; @@ -52,7 +50,6 @@ public class ScenarioScreen extends ScreenEffectScreen { @Setter private boolean playing = true; - public static boolean RENDER_WITHIN_IMGUI = false; private final boolean preload; public ScenarioScreen(Scenario scenario, boolean preload) { this.timestamps.addAll(scenario.getTimestamps()); @@ -304,13 +301,13 @@ public void render(float delta) { elements.removeIf(ElementRenderer::selfDestruct); elements.forEach(element -> element.getSubElements().values().removeIf(ElementRenderer::selfDestruct)); - if (ImGui.isKeyReleased(ImGuiKey.Space)) { - if (!this.dialogueRenderer.isBusy()) { - this.busyDialogue = false; - } else { - this.dialogueRenderer.finishAll(); - } - } +// if (ImGui.isKeyReleased(ImGuiKey.Space)) { +// if (!this.dialogueRenderer.isBusy()) { +// this.busyDialogue = false; +// } else { +// this.dialogueRenderer.finishAll(); +// } +// } } @Setter diff --git a/core/src/main/java/oxy/bascenario/screens/renderer/element/SpriteRenderer.java b/core/src/main/java/oxy/bascenario/screens/renderer/element/SpriteRenderer.java index 6f9328fc..ce625477 100644 --- a/core/src/main/java/oxy/bascenario/screens/renderer/element/SpriteRenderer.java +++ b/core/src/main/java/oxy/bascenario/screens/renderer/element/SpriteRenderer.java @@ -3,10 +3,7 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch; -import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.esotericsoftware.spine.*; -import imgui.ImGui; -import net.lenni0451.commons.color.Color; import net.raphimc.thingl.ThinGL; import net.raphimc.thingl.implementation.window.WindowInterface; import oxy.bascenario.Base; @@ -175,20 +172,20 @@ protected void render(ScenarioScreen screen) { private void updateSkeleton(Skeleton skeleton) { final WindowInterface window = ThinGL.windowInterface(); - float width = ScenarioScreen.RENDER_WITHIN_IMGUI ? ImGui.getWindowSizeX() : window.getFramebufferWidth(), height = ScenarioScreen.RENDER_WITHIN_IMGUI ? (ImGui.getWindowSizeY() - 46) : window.getFramebufferHeight(); + float width = window.getFramebufferWidth(), height = window.getFramebufferHeight(); float x = this.position.x() + this.offset.x() + this.animationOffset.x(), y = this.position.y() + this.offset.y() + this.animationOffset.y(); float posX = (x / 1920) * width, posY = (y / 1080) * -height; - if (ScenarioScreen.RENDER_WITHIN_IMGUI) { - posX += ImGui.getWindowPosX(); - - float ratio = (0.00064814813f * height) / (0.00064814813f * window.getFramebufferHeight()); - posY /= ratio; - - posY += window.getFramebufferHeight() - height; - posY -= ImGui.getWindowPosY() + 46; - } +// if (ScenarioScreen.RENDER_WITHIN_IMGUI) { +// posX += ImGui.getWindowPosX(); +// +// float ratio = (0.00064814813f * height) / (0.00064814813f * window.getFramebufferHeight()); +// posY /= ratio; +// +// posY += window.getFramebufferHeight() - height; +// posY -= ImGui.getWindowPosY() + 46; +// } skeleton.setPosition(posX, posY); diff --git a/core/src/main/java/oxy/bascenario/utils/ImGuiUtils.java b/core/src/main/java/oxy/bascenario/utils/ImGuiUtils.java deleted file mode 100644 index 39840d4c..00000000 --- a/core/src/main/java/oxy/bascenario/utils/ImGuiUtils.java +++ /dev/null @@ -1,234 +0,0 @@ -package oxy.bascenario.utils; - -import imgui.ImGui; -import imgui.ImGuiStyle; -import imgui.flag.ImGuiCol; -import imgui.flag.ImGuiInputTextFlags; -import imgui.type.ImFloat; -import imgui.type.ImInt; -import imgui.type.ImString; -import net.lenni0451.commons.color.Color; -import oxy.bascenario.Base; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.utils.files.NFDUtils; -import oxy.bascenario.utils.font.FontUtils; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.function.Consumer; -import java.util.stream.Stream; - -// Just so the code can look cleaner! -public class ImGuiUtils { - public static int COUNTER = 0; - - public static void pickFolder(Consumer consumer, Scenario.Builder scenario, final String name) { - if (!ImGui.button(name + "##" + COUNTER++)) { - return; - } - - final String pick = NFDUtils.pickFolder(); - if (pick.isEmpty()) { - return; - } - - File folder = new File(pick); - try (final Stream stream = Files.walk(folder.toPath())) { - List files = stream.filter(Files::isRegularFile).map(Path::toFile).toList(); - - new Thread(() -> { - try { - for (final File file : files) { - String strip = file.getAbsolutePath().replace(folder.getAbsolutePath() + "\\", ""); - FileInfo info = new FileInfo(folder.getName() + "\\" + strip, false, false); - - File save = Base.instance().scenarioManager().file(scenario.name(), info); - if (save.isDirectory() && !save.delete()) { - continue; - } - - Files.write(save.toPath(), Files.readAllBytes(file.toPath())); - consumer.accept(info); - } - } catch (Exception ignored) { - } - }).start(); - } catch (Exception ignored) { - } - } - - public static void pick(Consumer consumer, Scenario.Builder scenario, final String name, String filter) { - if (!ImGui.button(name + "##" + COUNTER++)) { - return; - } - - final String pick = NFDUtils.pickFile(filter); - if (pick.isEmpty()) { - return; - } - - final FileInfo fileInfo = new FileInfo(new File(pick).getName(), false, false); - final File file = Base.instance().scenarioManager().file(scenario.name(), fileInfo); - - try { - if (file.isDirectory() && !file.delete()) { - return; - } - - Files.write(file.toPath(), Files.readAllBytes(Path.of(pick))); - } catch (IOException e) { - return; - } - - consumer.accept(fileInfo); - } - - public static Color color(final String name, Color color) { - final float[] rgba = new float[] { color.getRedF(), color.getGreenF(), color.getBlueF(), color.getAlphaF() }; - ImGui.colorPicker4(name + "##" + COUNTER++, rgba); - return Color.fromRGBAF(rgba); - } - - public static int combo(final String label, int index, final String[] items) { - final ImInt currentItem = new ImInt(index); - ImGui.combo(label + "##" + COUNTER++, currentItem, items); - return currentItem.get(); - } - - public static int inputInt(String name, int value) { - final ImInt imInt = new ImInt(value); - ImGui.inputInt(name + "##" + COUNTER++, imInt); - return imInt.get(); - } - - public static float inputFloat(String name, float value) { - final ImFloat imFloat = new ImFloat(value); - ImGui.inputFloat(name + "##" + COUNTER++, imFloat); - return imFloat.get(); - } - - public static void inputInt4(String name, int[] value) { - ImGui.inputInt4(name + "##" + COUNTER++, value); - } - - public static String inputText(String name, String value) { -// ImGui.pushFont(FontUtils.CHILLGOTHIC_17, 17); - final ImString imString = new ImString(value); - ImGui.inputText(name + "##" + COUNTER++, imString, ImGuiInputTextFlags.NoHorizontalScroll | ImGuiInputTextFlags.CallbackResize); -// ImGui.popFont(); - return imString.get(); - } - - public static String inputMultiLineText(String name, String value) { -// ImGui.pushFont(FontUtils.CHILLGOTHIC_17, 17); - final ImString imString = new ImString(value); - ImGui.inputTextMultiline(name + "##" + COUNTER++, imString, ImGuiInputTextFlags.CallbackResize); -// ImGui.popFont(); - return imString.get(); - } - - public static boolean checkbox(String name, boolean value) { - return ImGui.checkbox(name + "##" + COUNTER++, value) != value; - } - - public static float sliderFloat(String name, float value, float min, float max) { - float[] newValue = new float[] {value}; - ImGui.sliderFloat(name + "##" + COUNTER++, newValue, min, max); - ImGui.sameLine(); - return inputFloat("", newValue[0]); - } - - public static int sliderInt(String name, int value, int min, int max) { - int[] newValue = new int[] {value}; - ImGui.sliderInt(name + "##" + COUNTER++, newValue, min, max); - ImGui.sameLine(); - return inputInt("", newValue[0]); - } - - // https://github.com/ocornut/imgui/issues/707#issuecomment-2732535348 - public static void setupTheme() { - ImGuiStyle style = ImGui.getStyle(); - - // Base colors for a pleasant and modern dark theme with dark accents - style.setColor(ImGuiCol.Text, 0.92f, 0.93f, 0.94f, 1.00f); // Light grey text for readability - style.setColor(ImGuiCol.TextDisabled, 0.50f, 0.52f, 0.54f, 1.00f); // Subtle grey for disabled text - style.setColor(ImGuiCol.WindowBg, 0.14f, 0.14f, 0.16f, 1.00f); // Dark background with a hint of blue - style.setColor(ImGuiCol.ChildBg, 0.16f, 0.16f, 0.18f, 1.00f); // Slightly lighter for child elements - style.setColor(ImGuiCol.PopupBg, 0.18f, 0.18f, 0.20f, 1.00f); // Popup background - style.setColor(ImGuiCol.Border, 0.28f, 0.29f, 0.30f, 0.60f); // Soft border color - style.setColor(ImGuiCol.BorderShadow, 0.00f, 0.00f, 0.00f, 0.00f); // No border shadow - style.setColor(ImGuiCol.FrameBg, 0.20f, 0.22f, 0.24f, 1.00f); // Frame background - style.setColor(ImGuiCol.FrameBgHovered, 0.22f, 0.24f, 0.26f, 1.00f); // Frame hover effect - style.setColor(ImGuiCol.FrameBgActive, 0.24f, 0.26f, 0.28f, 1.00f); // Active frame background - style.setColor(ImGuiCol.TitleBg, 0.14f, 0.14f, 0.16f, 1.00f); // Title background - style.setColor(ImGuiCol.TitleBgActive, 0.16f, 0.16f, 0.18f, 1.00f); // Active title background - style.setColor(ImGuiCol.TitleBgCollapsed, 0.14f, 0.14f, 0.16f, 1.00f); // Collapsed title background - style.setColor(ImGuiCol.MenuBarBg, 0.20f, 0.20f, 0.22f, 1.00f); // Menu bar background - style.setColor(ImGuiCol.ScrollbarBg, 0.16f, 0.16f, 0.18f, 1.00f); // Scrollbar background - style.setColor(ImGuiCol.ScrollbarGrab, 0.24f, 0.26f, 0.28f, 1.00f); // Dark accent for scrollbar grab - style.setColor(ImGuiCol.ScrollbarGrabHovered, 0.28f, 0.30f, 0.32f, 1.00f); // Scrollbar grab hover - style.setColor(ImGuiCol.ScrollbarGrabActive, 0.32f, 0.34f, 0.36f, 1.00f); // Scrollbar grab active - style.setColor(ImGuiCol.CheckMark, 0.46f, 0.56f, 0.66f, 1.00f); // Dark blue checkmark - style.setColor(ImGuiCol.SliderGrab, 0.36f, 0.46f, 0.56f, 1.00f); // Dark blue slider grab - style.setColor(ImGuiCol.SliderGrabActive, 0.40f, 0.50f, 0.60f, 1.00f); // Active slider grab - style.setColor(ImGuiCol.Button, 0.24f, 0.34f, 0.44f, 1.00f); // Dark blue button - style.setColor(ImGuiCol.ButtonHovered, 0.28f, 0.38f, 0.48f, 1.00f); // Button hover effect - style.setColor(ImGuiCol.ButtonActive, 0.32f, 0.42f, 0.52f, 1.00f); // Active button - style.setColor(ImGuiCol.Header, 0.24f, 0.34f, 0.44f, 1.00f); // Header color similar to button - style.setColor(ImGuiCol.HeaderHovered, 0.28f, 0.38f, 0.48f, 1.00f); // Header hover effect - style.setColor(ImGuiCol.HeaderActive, 0.32f, 0.42f, 0.52f, 1.00f); // Active header - style.setColor(ImGuiCol.Separator, 0.28f, 0.29f, 0.30f, 1.00f); // Separator color - style.setColor(ImGuiCol.SeparatorHovered, 0.46f, 0.56f, 0.66f, 1.00f); // Hover effect for separator - style.setColor(ImGuiCol.SeparatorActive, 0.46f, 0.56f, 0.66f, 1.00f); // Active separator - style.setColor(ImGuiCol.ResizeGrip, 0.36f, 0.46f, 0.56f, 1.00f); // Resize grip - style.setColor(ImGuiCol.ResizeGripHovered, 0.40f, 0.50f, 0.60f, 1.00f); // Hover effect for resize grip - style.setColor(ImGuiCol.ResizeGripActive, 0.44f, 0.54f, 0.64f, 1.00f); // Active resize grip - style.setColor(ImGuiCol.Tab, 0.20f, 0.22f, 0.24f, 1.00f); // Inactive tab - style.setColor(ImGuiCol.TabHovered, 0.28f, 0.38f, 0.48f, 1.00f); // Hover effect for tab - style.setColor(ImGuiCol.TabActive, 0.24f, 0.34f, 0.44f, 1.00f); // Active tab color - style.setColor(ImGuiCol.TabUnfocused, 0.20f, 0.22f, 0.24f, 1.00f); // Unfocused tab - style.setColor(ImGuiCol.TabUnfocusedActive, 0.24f, 0.34f, 0.44f, 1.00f); // Active but unfocused tab - style.setColor(ImGuiCol.PlotLines, 0.46f, 0.56f, 0.66f, 1.00f); // Plot lines - style.setColor(ImGuiCol.PlotLinesHovered, 0.46f, 0.56f, 0.66f, 1.00f); // Hover effect for plot lines - style.setColor(ImGuiCol.PlotHistogram, 0.36f, 0.46f, 0.56f, 1.00f); // Histogram color - style.setColor(ImGuiCol.PlotHistogramHovered, 0.40f, 0.50f, 0.60f, 1.00f); // Hover effect for histogram - style.setColor(ImGuiCol.TableHeaderBg, 0.20f, 0.22f, 0.24f, 1.00f); // Table header background - style.setColor(ImGuiCol.TableBorderStrong, 0.28f, 0.29f, 0.30f, 1.00f); // Strong border for tables - style.setColor(ImGuiCol.TableBorderLight, 0.24f, 0.25f, 0.26f, 1.00f); // Light border for tables - style.setColor(ImGuiCol.TableRowBg, 0.20f, 0.22f, 0.24f, 1.00f); // Table row background - style.setColor(ImGuiCol.TableRowBgAlt, 0.22f, 0.24f, 0.26f, 1.00f); // Alternate row background - style.setColor(ImGuiCol.TextSelectedBg, 0.24f, 0.34f, 0.44f, 0.35f); // Selected text background - style.setColor(ImGuiCol.DragDropTarget, 0.46f, 0.56f, 0.66f, 0.90f); // Drag and drop target - style.setColor(ImGuiCol.NavHighlight, 0.46f, 0.56f, 0.66f, 1.00f); // Navigation highlight - style.setColor(ImGuiCol.NavWindowingHighlight, 1.00f, 1.00f, 1.00f, 0.70f); // Windowing highlight - style.setColor(ImGuiCol.NavWindowingDimBg, 0.80f, 0.80f, 0.80f, 0.20f); // Dim background for windowing - style.setColor(ImGuiCol.ModalWindowDimBg, 0.80f, 0.80f, 0.80f, 0.35f); // Dim background for modal windows - - // Style adjustments - style.setWindowPadding(8.00f, 8.00f); - style.setFramePadding(5.00f, 2.00f); - style.setCellPadding(6.00f, 6.00f); - style.setItemSpacing(6.00f, 6.00f); - style.setItemInnerSpacing(6.00f, 6.00f); - style.setTouchExtraPadding(0.00f, 0.00f); - style.setIndentSpacing(25); - style.setScrollbarSize(11); - style.setGrabMinSize(10); - style.setChildBorderSize(1); - style.setPopupBorderSize(1); - style.setFrameBorderSize(1); - style.setTabBorderSize(1); - style.setWindowRounding(7); - style.setChildRounding(4); - style.setFrameRounding(3); - style.setPopupRounding(4); - style.setScrollbarRounding(8); - style.setGrabRounding(3); - style.setLogSliderDeadzone(4); - style.setTabRounding(4); - } -} \ No newline at end of file diff --git a/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java b/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java index 187fd01b..75b4a181 100644 --- a/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java +++ b/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java @@ -2,8 +2,6 @@ import java.util.*; -import imgui.*; -import lombok.SneakyThrows; import net.raphimc.thingl.resource.font.Font; import net.raphimc.thingl.resource.font.impl.FreeTypeFont; import oxy.bascenario.Base; @@ -17,8 +15,6 @@ public class FontUtils { public static Font DEFAULT, SEMI_BOLD; private static final Map NAME_TO_FONTS = new HashMap<>(); - public static ImFont IM_FONT_SEMI_BOLD, IM_FONT_REGULAR, CHILLGOTHIC_17; - public static Font font(FontStyle style, FontType type) { return NAME_TO_FONTS.get(type.toName(style)); } @@ -69,14 +65,6 @@ public static void loadFonts() { DEFAULT = NAME_TO_FONTS.get("ChillRoundRegular"); SEMI_BOLD = NAME_TO_FONTS.get("ChillRoundSemiBold"); - - ImGui.getIO().setFontDefault(loadImFont("/assets/base/fonts/global/NotoSans-Regular.ttf", 18, false)); - IM_FONT_REGULAR = loadImFont("/assets/base/fonts/global/NotoSans-Regular.ttf", 50, false); - IM_FONT_SEMI_BOLD = loadImFont("/assets/base/fonts/global/NotoSans-SemiBold.ttf", 50, false); - CHILLGOTHIC_17 = loadImFont("/assets/base/fonts/chinese/simplified/ChillRoundGothic_Regular.otf", 50, true); - - ImGui.getIO().setConfigDpiScaleFonts(true); - ImGui.getIO().setConfigDpiScaleViewports(true); } public static Font loadSpecificFont(Scenario scenario, FileInfo font) { @@ -92,27 +80,4 @@ private static void loadFont(String name, String font) { throw new RuntimeException(e); } } - - @SneakyThrows - private static ImFont loadImFont(String font, int size, boolean full) { - final byte[] fontData = FontUtils.class.getResourceAsStream(font).readAllBytes(); - - final ImFontGlyphRangesBuilder rangesBuilder = new ImFontGlyphRangesBuilder(); - rangesBuilder.addRanges(ImGui.getIO().getFonts().getGlyphRangesDefault()); - if (full) { - rangesBuilder.addRanges(ImGui.getIO().getFonts().getGlyphRangesJapanese()); - rangesBuilder.addRanges(ImGui.getIO().getFonts().getGlyphRangesCyrillic()); - rangesBuilder.addRanges(ImGui.getIO().getFonts().getGlyphRangesChineseFull()); - rangesBuilder.addRanges(ImGui.getIO().getFonts().getGlyphRangesChineseSimplifiedCommon()); - rangesBuilder.addRanges(ImGui.getIO().getFonts().getGlyphRangesKorean()); - - // Thanks ImGui :D, memory goin to be nice! and well imgui-java still on 1.90.0 so no dynamic render. - for (char c = '\u4e00'; c <= '\u9fff'; c++) { - rangesBuilder.addChar(c); - } - } - - final ImGuiIO data = ImGui.getIO(); - return data.getFonts().addFontFromMemoryTTF(fontData, size, new ImFontConfig(), rangesBuilder.buildRanges()); - } } \ No newline at end of file diff --git a/core/src/main/resources/assets/base/base.imgui b/core/src/main/resources/assets/base/base.imgui deleted file mode 100644 index 9964c90c..00000000 --- a/core/src/main/resources/assets/base/base.imgui +++ /dev/null @@ -1,73 +0,0 @@ -[Window][Debug##Default] -Pos=60,60 -Size=400,400 -Collapsed=0 - -[Window][WindowOverViewport_11111111] -Pos=0,23 -Size=1920,1004 -Collapsed=0 - -[Window][Actions] -Pos=1356,23 -Size=564,563 -Collapsed=0 -DockId=0x00000004,0 - -[Window][Timeline] -Pos=0,588 -Size=1920,439 -Collapsed=0 -DockId=0x00000002,0 - -[Window][Objects] -Pos=1356,23 -Size=564,563 -Collapsed=0 -DockId=0x00000004,1 - -[Window][Inspector] -Pos=0,23 -Size=375,563 -Collapsed=0 -DockId=0x00000005,0 - -[Window][Scenario View] -Pos=377,23 -Size=977,563 -Collapsed=0 -DockId=0x00000006,0 - -[Window][Assets] -Pos=0,23 -Size=375,563 -Collapsed=0 -DockId=0x00000005,1 - -[Window][Select asset] -Pos=775,362 -Size=370,303 -Collapsed=0 - -[Window][Dear ImGui Demo] -Pos=276,54 -Size=550,680 -Collapsed=0 - -[Table][0xF666A32D,2] -Column 0 Weight=1.3118 -Column 1 Weight=0.6882 - -[Table][0x412292FB,2] -Column 0 Weight=1.0000 -Column 1 Weight=1.0000 - -[Docking][Data] -DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,23 Size=1920,1004 Split=Y - DockNode ID=0x00000001 Parent=0x7C6B3D9B SizeRef=1920,563 Split=X - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1354,538 Split=X Selected=0xE7039252 - DockNode ID=0x00000005 Parent=0x00000003 SizeRef=375,587 Selected=0xE7039252 - DockNode ID=0x00000006 Parent=0x00000003 SizeRef=977,587 CentralNode=1 Selected=0x52AAAE52 - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=564,538 Selected=0x7D8AF184 - DockNode ID=0x00000002 Parent=0x7C6B3D9B SizeRef=1920,439 Selected=0x0F18B61B - diff --git a/editor/build.gradle.kts b/editor/build.gradle.kts index 470609a0..f36ab701 100644 --- a/editor/build.gradle.kts +++ b/editor/build.gradle.kts @@ -7,9 +7,6 @@ dependencies { implementation(project(":core")) implementation(libs.gdx.base) - implementation(libs.imgui.binding) - implementation(libs.imgui.lwjgl3) - implementation(libs.lwjgl.glfw) implementation(libs.lwjgl.nfd) diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/Inspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/Inspector.java deleted file mode 100644 index 0686a423..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/Inspector.java +++ /dev/null @@ -1,206 +0,0 @@ -package oxy.bascenario.editor.inspector; - -import imgui.ImGui; -import lombok.RequiredArgsConstructor; -import oxy.bascenario.api.effects.ScreenEffect; -import oxy.bascenario.api.effects.Weather; -import oxy.bascenario.api.event.*; -import oxy.bascenario.api.event.background.ClearBackgroundEvent; -import oxy.bascenario.api.event.background.SetBackgroundEvent; -import oxy.bascenario.api.event.color.ColorOverlayEvent; -import oxy.bascenario.api.event.animation.PlayAnimationEvent; -import oxy.bascenario.api.event.animation.SpriteAnimationEvent; -import oxy.bascenario.api.event.animation.StopAnimationEvent; -import oxy.bascenario.api.event.color.SetColorEvent; -import oxy.bascenario.api.event.dialogue.AddDialogueEvent; -import oxy.bascenario.api.event.dialogue.RedirectDialogueEvent; -import oxy.bascenario.api.event.dialogue.ShowOptionsEvent; -import oxy.bascenario.api.event.dialogue.StartDialogueEvent; -import oxy.bascenario.api.event.element.ElementEffectEvent; -import oxy.bascenario.api.event.element.focus.FocusElementEvent; -import oxy.bascenario.api.event.element.values.PositionElementEvent; -import oxy.bascenario.api.event.element.values.RotateElementEvent; -import oxy.bascenario.api.event.log.AddLogEvent; -import oxy.bascenario.api.event.sound.SoundVolumeEvent; -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.api.render.elements.LocationInfo; -import oxy.bascenario.api.render.elements.Preview; -import oxy.bascenario.api.render.elements.Sprite; -import oxy.bascenario.api.render.elements.emoticon.Emoticon; -import oxy.bascenario.api.render.elements.image.AnimatedImage; -import oxy.bascenario.api.render.elements.image.Image; -import oxy.bascenario.api.render.elements.shape.Circle; -import oxy.bascenario.api.render.elements.shape.Rectangle; -import oxy.bascenario.api.render.elements.text.AnimatedText; -import oxy.bascenario.api.render.elements.text.Text; -import oxy.bascenario.api.utils.math.Vec2; -import oxy.bascenario.editor.inspector.impl.events.*; -import oxy.bascenario.editor.inspector.impl.objects.*; -import oxy.bascenario.editor.timeline.ObjectOrEvent; -import oxy.bascenario.editor.screen.BaseScenarioEditorScreen; -import oxy.bascenario.editor.timeline.Timeline; -import oxy.bascenario.editor.utils.AudioUtils; -import oxy.bascenario.editor.utils.NameUtils; -import oxy.bascenario.editor.utils.SoundAsElement; -import oxy.bascenario.editor.utils.TimeCompiler; -import oxy.bascenario.utils.ImGuiUtils; - -import java.util.*; - -@RequiredArgsConstructor -public class Inspector { - private final BaseScenarioEditorScreen screen; - private final Timeline timeline; - - @SuppressWarnings("ALL") - public void render() { - ImGui.begin("Inspector"); - final ObjectOrEvent object = timeline.getSelectedObject(); - if (object == null) { - ImGui.end(); - return; - } - - ImGui.text(NameUtils.name(object.object)); - - // I seriously can't think of a proper way to implement this in the editor, so we going with the always wait approch. - boolean requireWait = true; // ImGuiUtils.checkbox("Wait For Dialogue", object.requireWait); - RenderLayer layer = null; - Vec2 vec2 = null; - if (object.layer != null) { - layer = RenderLayer.values()[ImGuiUtils.combo("Render Layer", object.layer.ordinal(), RenderLayer.getAlls())]; - - if (object.vec2 != null) { - float x = ImGuiUtils.sliderFloat("X", object.vec2.x(), -1920, 1920); - float y = ImGuiUtils.sliderFloat("Y", object.vec2.y(), -1080, 1080); - vec2 = new Vec2(x, y); - } - } - - ImGui.separator(); - - final Object old = object.object; - object.object = switch (object.object) { - case Preview preview -> PreviewInspector.render(preview); - case Emoticon emoticon -> EmoticonInspector.render(emoticon); - case LocationInfo info -> LocationInfoInspector.render(info); - case Text text -> TextInspector.render(screen.getScenario(), text); - case AnimatedText text -> TextInspector.render(screen.getScenario(), text); - case AnimatedImage image -> ImageInspector.render(image); - case Image image -> ImageInspector.render(image); - case Circle circle -> ShapeInspector.render(circle); - case Rectangle rectangle -> ShapeInspector.render(rectangle); - case Sprite sprite -> SpriteInspector.render(sprite); - - case StartDialogueEvent event -> DialogueInspector.render(screen.getScenario(), event); - case AddDialogueEvent event -> DialogueInspector.render(screen.getScenario(), event); - case RedirectDialogueEvent event -> DialogueInspector.render(event); - - case AddLogEvent event -> LogInspector.render(screen.getScenario(), event); - - case ShowOptionsEvent event -> OptionsInspector.render(event); - - case ColorOverlayEvent event -> ColorInspector.render(event); - case SetColorEvent event -> ColorInspector.render(event); - - case PlayAnimationEvent event -> AnimationInspector.render(event); - case StopAnimationEvent event -> AnimationInspector.render(event); - case SpriteAnimationEvent event -> AnimationInspector.render(event); - - case ElementEffectEvent event -> ElementEffectInspector.render(event); - -// case PlaySoundEvent event -> SoundInspector.render(screen.getScenario(), event); - case SoundAsElement sound -> SoundInspector.render(screen.getScenario(), sound); - case SoundVolumeEvent event -> SoundInspector.render(event); -// case StopSoundEvent event -> SoundInspector.render(event); - - case PositionElementEvent event -> PositionInspector.render(event); - case RotateElementEvent event -> PositionInspector.render(event); - - case SetBackgroundEvent event -> BackgroundInspector.render(event); - case ClearBackgroundEvent event -> BackgroundInspector.render(event); - - case FocusElementEvent event -> new FocusElementEvent(ImGuiUtils.inputInt("Track Id", event.id())); - - case ShowButtonsEvent event -> new ShowButtonsEvent(ImGuiUtils.checkbox("Show", event.show())); - - case SetWeatherEvent event -> new SetWeatherEvent(Weather.values()[ImGuiUtils.combo("Weather", event.weather().ordinal(), Weather.getAlls())]); - case ScreenEffectEvent event -> { - ScreenEffectEvent.Type type = ScreenEffectEvent.Type.values()[ImGuiUtils.combo("Type", event.type().ordinal(), ScreenEffectEvent.Type.getAlls())]; - if (type == ScreenEffectEvent.Type.CLEAR_ALL) { - yield new ScreenEffectEvent(ScreenEffectEvent.Type.CLEAR_ALL, ScreenEffect.GRAY_FILTER); // effect here is placeholder - } else { - yield new ScreenEffectEvent(type, - ScreenEffect.values()[ImGuiUtils.combo("Effect", event.effect().ordinal(), ScreenEffect.getAlls())]); - } - } - - case ScreenTransitionEvent event -> TransitionInspector.render(event); - case PopupEvent event -> ImageInspector.render(event); - default -> old; - }; - - boolean objectEquals = !old.equals(object.object); - if (objectEquals || requireWait != object.requireWait || object.layer != layer || !Objects.equals(vec2, object.vec2)) { - final int oldTrack = object.track; - long oldDuration = old instanceof SoundAsElement sound ? AudioUtils.toDuration(screen.getScenario().name(), sound) : TimeCompiler.compileTime(old); - if (objectEquals) { - long duration = object.object instanceof SoundAsElement sound ? AudioUtils.toDuration(screen.getScenario().name(), sound) : TimeCompiler.compileTime(object.object); - if (duration == Long.MAX_VALUE) { - duration = 0; - } - if (oldDuration == Long.MAX_VALUE) { - oldDuration = 0; - } - duration += Math.max(0, object.duration - oldDuration); - object.duration = duration; - - object.track = findNonOccupiedSlot(object.track, object.start, object.duration, object); - } - - final boolean oldWait = object.requireWait; - final RenderLayer oldLayer = object.layer; - final Vec2 oldVec2 = object.vec2; - final long oldTime = oldDuration == Long.MAX_VALUE ? 0 : oldDuration; - timeline.queueUndo(() -> { - object.object = old; - object.duration = oldTime; - object.requireWait = oldWait; - object.layer = oldLayer; - object.vec2 = oldVec2; - object.track = oldTrack; - }); - - object.requireWait = requireWait; - object.layer = layer; - object.vec2 = vec2; - } - - ImGui.end(); - } - - private int findNonOccupiedSlot(int initId, long time, long duration, ObjectOrEvent object1) { - final Map> map = new HashMap<>(); - timeline.getObjects().forEach(object -> map.computeIfAbsent(object.track, n -> new ArrayList<>()).add(object)); - - int i = initId; - List track; - while ((track = map.get(i)) != null) { - boolean occupied = false; - for (ObjectOrEvent object : track) { - final long maxTime = object.start + object.duration, minTime = object.start; - if (maxTime >= time && minTime <= time + duration && object1 != object) { - occupied = true; - break; - } - } - - if (!occupied) { - return i; - } - i++; - } - - return i; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/AnimationInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/AnimationInspector.java deleted file mode 100644 index 564697b1..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/AnimationInspector.java +++ /dev/null @@ -1,40 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import oxy.bascenario.Base; -import oxy.bascenario.api.event.animation.PlayAnimationEvent; -import oxy.bascenario.api.event.animation.SpriteAnimationEvent; -import oxy.bascenario.api.event.animation.StopAnimationEvent; -import oxy.bascenario.utils.ImGuiUtils; -import oxy.bascenario.utils.math.Pair; - -public class AnimationInspector { - public static PlayAnimationEvent render(PlayAnimationEvent event) { - PlayAnimationEvent.Builder builder = event.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Target Track", event.id()))); - - final Pair alls = Base.instance().animationManager().getAllAnimations(event.name()); - builder.name(alls.right()[ImGuiUtils.combo("Animation Name", alls.left(), alls.right())]); - - builder.loop(ImGuiUtils.checkbox("Loop", event.loop())); - return builder.build(); - } - - public static StopAnimationEvent render(StopAnimationEvent event) { - StopAnimationEvent.Builder builder = event.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Target Track", event.id()))); - - final Pair alls = Base.instance().animationManager().getAllAnimations(event.name()); - builder.name(alls.right()[ImGuiUtils.combo("Animation Name", alls.left(), alls.right())]); - return builder.build(); - } - - public static SpriteAnimationEvent render(SpriteAnimationEvent event) { - SpriteAnimationEvent.Builder builder = event.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Target Track", event.id()))); - builder.trackIndex(Math.abs(ImGuiUtils.inputInt("Layer", event.trackIndex()))); - builder.mixTime(Math.abs(ImGuiUtils.sliderFloat("Mix/Lerp Time", event.mixTime(), 0, 20))); - builder.animationName(ImGuiUtils.inputText("Animation Name", event.animationName())); - builder.loop(ImGuiUtils.checkbox("Loop", event.loop())); - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/BackgroundInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/BackgroundInspector.java deleted file mode 100644 index 68fdbadf..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/BackgroundInspector.java +++ /dev/null @@ -1,30 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import oxy.bascenario.api.event.background.ClearBackgroundEvent; -import oxy.bascenario.api.event.background.SetBackgroundEvent; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.editor.miniuis.AssetsUI; -import oxy.bascenario.utils.ImGuiUtils; - -public class BackgroundInspector { - private static FileInfo last; - - public static SetBackgroundEvent render(SetBackgroundEvent event) { - SetBackgroundEvent.Builder builder = event.toBuilder(); - builder.duration(Math.abs(ImGuiUtils.sliderInt("Fade Duration", event.duration(), 0, 10000))); - AssetsUI.pick("Pick Background!", file -> last = file, "png,jpg"); - - if (last != null) { - builder.background(last); - last = null; - } - - return builder.build(); - } - - public static ClearBackgroundEvent render(ClearBackgroundEvent event) { - ClearBackgroundEvent.Builder builder = event.toBuilder(); - builder.duration(Math.abs(ImGuiUtils.sliderInt("Fade Duration", event.duration(), 0, 10000))); - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/ColorInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/ColorInspector.java deleted file mode 100644 index 2b101aab..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/ColorInspector.java +++ /dev/null @@ -1,36 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import oxy.bascenario.api.event.color.ColorOverlayEvent; -import oxy.bascenario.api.event.color.SetColorEvent; -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.utils.ImGuiUtils; - -import java.util.Optional; - -public class ColorInspector { - public static SetColorEvent render(SetColorEvent event) { - SetColorEvent.Builder builder = event.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Target Track", event.id()))); - builder.duration(ImGuiUtils.sliderInt("Fade Duration", event.duration(), 0, 60000)); - - builder.color(ImGuiUtils.color("Color", event.color())); - return builder.build(); - } - - public static ColorOverlayEvent render(ColorOverlayEvent event) { - ColorOverlayEvent.Builder builder = event.toBuilder(); - if (event.id().isPresent()) { - builder.id(Optional.of(Math.abs(ImGuiUtils.inputInt("Target Track", event.id().get())))); - } - - builder.duration(ImGuiUtils.sliderInt("Fade Duration", event.duration(), 0, 60000)); - - builder.color(ImGuiUtils.color("Color", event.color())); - - if (event.id().isEmpty()) { - builder.renderLayer(RenderLayer.values()[ImGuiUtils.combo("Render Layer", event.renderLayer().ordinal(), RenderLayer.getAlls())]); - } - - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/DialogueInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/DialogueInspector.java deleted file mode 100644 index 4d2de83d..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/DialogueInspector.java +++ /dev/null @@ -1,96 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import imgui.ImGui; -import imgui.type.ImBoolean; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.event.dialogue.AddDialogueEvent; -import oxy.bascenario.api.event.dialogue.RedirectDialogueEvent; -import oxy.bascenario.api.event.dialogue.StartDialogueEvent; -import oxy.bascenario.api.event.dialogue.enums.OffsetType; -import oxy.bascenario.api.event.dialogue.enums.TextOffset; -import oxy.bascenario.api.render.elements.Dialogue; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.editor.inspector.impl.objects.TextInspector; -import oxy.bascenario.utils.ImGuiUtils; - -import java.util.ArrayList; -import java.util.List; - -public class DialogueInspector { - public static RedirectDialogueEvent render(RedirectDialogueEvent event) { - return new RedirectDialogueEvent(ImGuiUtils.inputInt("Dialogue Index", event.index())); - } - - public static AddDialogueEvent render(Scenario.Builder scenario, AddDialogueEvent event) { - AddDialogueEvent.Builder builder = event.toBuilder(); - - builder.index(ImGuiUtils.inputInt("Dialogue Index", event.index())); - builder.newLine(ImGuiUtils.checkbox("New Line", event.newLine())); - - final List list = new ArrayList<>(); - if (ImGui.button("New dialogue!##" + ImGuiUtils.COUNTER++)) { - list.add(Dialogue.builder().build()); - } - - for (Dialogue dialogue : event.dialogues()) { - final ImBoolean imBoolean = new ImBoolean(true); - if (ImGui.collapsingHeader("Dialogue##" + ImGuiUtils.COUNTER++, imBoolean)) { - dialogue = render(scenario, dialogue); - } - - if (imBoolean.get()) { - list.add(dialogue); - } - } - builder.dialogues(list.toArray(new Dialogue[0])); - - return builder.build(); - } - - public static StartDialogueEvent render(Scenario.Builder scenario, StartDialogueEvent event) { - StartDialogueEvent.Builder builder = event.toBuilder(); - - OffsetType offsetType = OffsetType.values()[ImGuiUtils.combo("Text Offset", event.offset().type().ordinal(), OffsetType.getAlls())]; - float offset = event.offset().offset(); - if (offsetType == OffsetType.Custom) { - offset = ImGuiUtils.sliderFloat("Offset X", offset, 0, 1920); - } - builder.offset(new TextOffset(offsetType, offset)); - - ImGui.separatorText(""); - - builder.type(FontType.values()[ImGuiUtils.combo("Font Type", event.type().ordinal(), FontType.getAlls())]); - builder.index(ImGuiUtils.inputInt("Dialogue Index", event.index())); - builder.name(ImGuiUtils.inputText("Name", event.name())); - builder.association(ImGuiUtils.inputText("Association", event.association())); - builder.background(ImGuiUtils.checkbox("Background", event.background())); - - final List list = new ArrayList<>(); - boolean add = ImGui.button("New dialogue!##" + ImGuiUtils.COUNTER++); - - for (Dialogue dialogue : event.dialogues()) { - final ImBoolean imBoolean = new ImBoolean(true); - if (ImGui.collapsingHeader("Dialogue##" + ImGuiUtils.COUNTER++, imBoolean)) { - dialogue = render(scenario, dialogue); - } - - if (imBoolean.get()) { - list.add(dialogue); - } - } - if (add) { - list.add(Dialogue.builder().add("This is a dialogue text!").build()); - } - builder.dialogues(list.toArray(new Dialogue[0])); - - return builder.build(); - } - - public static Dialogue render(Scenario.Builder scenario, Dialogue dialogue) { - Dialogue.Builder builder = dialogue.toBuilder(); - builder.offset(ImGuiUtils.sliderFloat("Text Offset", builder.offset(), -1920, 1920)); - builder.playSpeed(ImGuiUtils.sliderFloat("Play Speed", builder.playSpeed(), 0.01f, 50)); - builder.dialogue(TextInspector.render(scenario, dialogue.getDialogue())); - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/ElementEffectInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/ElementEffectInspector.java deleted file mode 100644 index fc1580e5..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/ElementEffectInspector.java +++ /dev/null @@ -1,68 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import oxy.bascenario.api.effects.Effect; -import oxy.bascenario.api.event.element.ElementEffectEvent; -import oxy.bascenario.api.utils.math.Axis; -import oxy.bascenario.utils.ImGuiUtils; - -public class ElementEffectInspector { - public static ElementEffectEvent render(ElementEffectEvent event) { - ElementEffectEvent.Builder builder = event.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Target Track", event.id()))); - Effect effect = Effect.values()[ImGuiUtils.combo("Effect Type", event.effect().ordinal(), Effect.getAlls())]; - builder.effect(effect); - Object[] values; - - if (event.type() == ElementEffectEvent.Type.ADD) { - switch (effect) { - case HOLOGRAM -> { - Axis old = Axis.X; - if (event.values().length == 1 && event.values()[0] instanceof Axis axis) { - old = axis; - } - - values = new Object[] {Axis.values()[ImGuiUtils.combo("Axis", old.ordinal(), Axis.getAlls())]}; - } - - case RAINBOW -> { - Axis old = Axis.X; - float oldFloat = 1.5f; - if (event.values().length == 2 && event.values()[0] instanceof Axis axis && event.values()[1] instanceof Float f) { - old = axis; - oldFloat = f; - } - - values = new Object[] { - Axis.values()[ImGuiUtils.combo("Axis", old.ordinal(), Axis.getAlls())], - ImGuiUtils.sliderFloat("Distance", oldFloat, 0.01f, 500) - }; - } - - case BLUR -> { - int old = 5; - if (event.values().length == 1 && event.values()[0] instanceof Integer integer) { - old = integer; - } - - values = new Object[] {ImGuiUtils.sliderInt("Radius", old, 0, 100)}; - } - - case OUTLINE -> { - int old = 1; - if (event.values().length == 2 && event.values()[0] instanceof Integer integer) { - old = integer; - } - - values = new Object[] {ImGuiUtils.sliderInt("Radius", old, 0, 100), 1 << 1}; - } - default -> throw new IllegalStateException("Unexpected value: " + event.effect()); - } - } else { - values = new Object[] {}; - } - - builder.values(values); - - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/LogInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/LogInspector.java deleted file mode 100644 index 92da02d5..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/LogInspector.java +++ /dev/null @@ -1,58 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import imgui.ImGui; -import imgui.type.ImBoolean; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.event.dialogue.AddDialogueEvent; -import oxy.bascenario.api.event.dialogue.RedirectDialogueEvent; -import oxy.bascenario.api.event.dialogue.StartDialogueEvent; -import oxy.bascenario.api.event.dialogue.enums.OffsetType; -import oxy.bascenario.api.event.dialogue.enums.TextOffset; -import oxy.bascenario.api.event.log.AddLogEvent; -import oxy.bascenario.api.render.elements.Dialogue; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.editor.inspector.impl.objects.TextInspector; -import oxy.bascenario.utils.ImGuiUtils; - -import java.util.ArrayList; -import java.util.List; - -public class LogInspector { - public static AddLogEvent render(Scenario.Builder scenario, AddLogEvent event) { - AddLogEvent.Builder builder = event.toBuilder(); - - AddLogEvent.Type type = AddLogEvent.Type.values()[ImGuiUtils.combo("Action", event.type().ordinal(), AddLogEvent.Type.getAlls())]; - ImGui.separatorText(""); - if (type == AddLogEvent.Type.CLEAR) { - OffsetType offsetType = OffsetType.values()[ImGuiUtils.combo("Text Offset", event.offset().type().ordinal(), OffsetType.getAlls())]; - float offset = event.offset().offset(); - if (offsetType == OffsetType.Custom) { - offset = ImGuiUtils.sliderFloat("Offset X", offset, 0, 1920); - } - builder.offset(new TextOffset(offsetType, offset)); - ImGui.separatorText(""); - } - builder.type(type); - - builder.index(ImGuiUtils.inputInt("Dialogue Index", event.index())); - - final List list = new ArrayList<>(); - if (ImGui.button("New log!##" + ImGuiUtils.COUNTER++)) { - list.add(Dialogue.builder().build()); - } - - for (Dialogue dialogue : event.dialogues()) { - final ImBoolean imBoolean = new ImBoolean(true); - if (ImGui.collapsingHeader("Log##" + ImGuiUtils.COUNTER++, imBoolean)) { - dialogue = DialogueInspector.render(scenario, dialogue); - } - - if (imBoolean.get()) { - list.add(dialogue); - } - } - builder.dialogues(list.toArray(new Dialogue[0])); - - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/OptionsInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/OptionsInspector.java deleted file mode 100644 index 6dff1a35..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/OptionsInspector.java +++ /dev/null @@ -1,39 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import imgui.ImGui; -import imgui.type.ImBoolean; -import oxy.bascenario.api.event.dialogue.ShowOptionsEvent; -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.utils.ImGuiUtils; - -import java.util.LinkedHashMap; -import java.util.Map; - -public class OptionsInspector { - public static ShowOptionsEvent render(ShowOptionsEvent event) { - final Map options = new LinkedHashMap<>(); - - FontType type = FontType.values()[ImGuiUtils.combo("Font Type", event.type().ordinal(), FontType.getAlls())]; - boolean add = ImGui.button("New option!"); - - for (Map.Entry entry : event.options().entrySet()) { - final ImBoolean imBoolean = new ImBoolean(true); - String option = entry.getKey(); - int redirect = entry.getValue(); - if (ImGui.collapsingHeader("Option##" + ImGuiUtils.COUNTER++, imBoolean)) { - option = ImGuiUtils.inputText("Option", entry.getKey()); - redirect = ImGuiUtils.inputInt("Redirect to", entry.getValue()); - } - if (imBoolean.get()) { - options.put(option, redirect); - } - } - - if (add) { - options.put("", 0); - } - - return new ShowOptionsEvent(type, options); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/PositionInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/PositionInspector.java deleted file mode 100644 index 1033f82c..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/PositionInspector.java +++ /dev/null @@ -1,43 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import oxy.bascenario.api.effects.Easing; -import oxy.bascenario.api.event.element.values.PositionElementEvent; -import oxy.bascenario.api.event.element.values.RotateElementEvent; -import oxy.bascenario.api.utils.math.Vec2; -import oxy.bascenario.api.utils.math.Vec3; -import oxy.bascenario.utils.ImGuiUtils; - -public class PositionInspector { - public static RotateElementEvent render(RotateElementEvent event) { - RotateElementEvent.Builder builder = event.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Target Track", event.id()))); - builder.duration(Math.abs(ImGuiUtils.sliderInt("Duration (ms)", (int) event.duration(), 0, 50000))); - - float x = ImGuiUtils.sliderFloat("X", event.rotation().x(), 0, 360); - float y = ImGuiUtils.sliderFloat("Y", event.rotation().y(), 0, 360); - float z = ImGuiUtils.sliderFloat("Z", event.rotation().z(), 0, 360); - - builder.rotation(new Vec3(x, y, z)); - builder.easing(Easing.values()[ImGuiUtils.combo("Easing Mode", event.easing().ordinal(), Easing.getAlls())]); - return builder.build(); - } - - public static PositionElementEvent render(PositionElementEvent event) { - PositionElementEvent.Builder builder = event.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Target Track", event.id()))); - builder.duration(Math.abs(ImGuiUtils.sliderInt("Duration (ms)", event.duration(), 0, 50000))); - - float MIN_X = event.type() == PositionElementEvent.Type.SCALE ? 0 : -1920; - float MAX_X = event.type() == PositionElementEvent.Type.SCALE ? 200 : 1920; - float MIN_Y = event.type() == PositionElementEvent.Type.SCALE ? 0 : -1080; - float MAX_Y = event.type() == PositionElementEvent.Type.SCALE ? 200 : 1080; - - float x = ImGuiUtils.sliderFloat("X", event.vec().x(), MIN_X, MAX_X); - float y = ImGuiUtils.sliderFloat("Y", event.vec().y(), MIN_Y, MAX_Y); - builder.vec(new Vec2(x, y)); - - builder.easing(Easing.values()[ImGuiUtils.combo("Easing Mode", event.easing().ordinal(), Easing.getAlls())]); - - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/SoundInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/SoundInspector.java deleted file mode 100644 index 7dd1883c..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/SoundInspector.java +++ /dev/null @@ -1,70 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import imgui.ImGui; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.effects.Easing; -import oxy.bascenario.api.effects.Sound; -import oxy.bascenario.api.event.sound.SoundVolumeEvent; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.editor.miniuis.AssetsUI; -import oxy.bascenario.editor.utils.AudioUtils; -import oxy.bascenario.editor.utils.SoundAsElement; -import oxy.bascenario.utils.ImGuiUtils; - -public class SoundInspector { - public static SoundAsElement render(Scenario.Builder scenario, SoundAsElement sound) { - SoundAsElement.Builder builder = sound.toBuilder(); - final FileInfo old = sound.sound().file(); - Sound newSound = render(sound.sound()); - builder.sound(newSound); - ImGui.separatorText(""); - builder.in(ImGuiUtils.sliderInt("Fade In (ms)", sound.in(), 0, (int) Math.min(10000, sound.max()))); - builder.out(ImGuiUtils.sliderInt("Fade Out (ms)", sound.out(), 0, (int) Math.min(10000, sound.max()))); - builder.start(ImGuiUtils.sliderFloat("Start Position (seconds)", sound.start(), 0, (float) sound.max() / 1000L)); - if ((old == null || !old.equals(newSound.file())) && newSound.file() != null) { - builder.max(AudioUtils.toDuration(scenario.name(), newSound.file())); - } - return builder.build(); - } -// -// public static StopSoundEvent render(StopSoundEvent event) { -// StopSoundEvent.Builder builder = event.toBuilder(); -// builder.id(Math.abs(ImGuiUtils.inputInt("Sound ID", 0))); -// builder.duration(ImGuiUtils.sliderInt("Fade Duration (ms)", event.getDuration(), 0, 10000)); -// return builder.build(); -// } -// - public static SoundVolumeEvent render(SoundVolumeEvent event) { - SoundVolumeEvent.Builder builder = event.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Sound ID", 0))); - builder.duration(ImGuiUtils.sliderInt("Fade Duration (ms)", (int) event.duration(), 0, 10000)); - builder.volume(Math.abs(ImGuiUtils.sliderFloat("Volume", event.volume(), 0, 1))); - builder.easing(Easing.values()[ImGuiUtils.combo("Easing Mode", event.easing().ordinal(), Easing.getAlls())]); - return builder.build(); - } -// -// public static PlaySoundEvent render(Scenario.Builder scenario, PlaySoundEvent event) { -// PlaySoundEvent.Builder builder = event.toBuilder(); -// builder.duration(ImGuiUtils.sliderInt("Fade Duration (ms)", (int) event.getDuration(), 0, 10000)); -// builder.start(ImGuiUtils.inputFloat("Start Position (seconds)", event.getStart())); -// ImGui.separatorText(""); -// builder.sound(render(scenario, event.getSound())); -// return builder.build(); -// } - - private static FileInfo last; - - public static Sound render(Sound sound) { - final Sound.Builder builder = sound.toBuilder(); - builder.id(Math.abs(ImGuiUtils.inputInt("Sound ID", 0))); - AssetsUI.pick("Pick Sound!", file -> last = file, "mp3,wav,ogg"); - builder.maxVolume(Math.abs(ImGuiUtils.sliderFloat("Max Volume", sound.maxVolume(), 0, 1))); -// builder.loop(ImGuiUtils.checkbox("Loop", sound.loop())); - - if (last != null) { - builder.file(last); - last = null; - } - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/TransitionInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/TransitionInspector.java deleted file mode 100644 index 70d840a1..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/events/TransitionInspector.java +++ /dev/null @@ -1,31 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.events; - -import oxy.bascenario.api.effects.TransitionType; -import oxy.bascenario.api.event.ScreenTransitionEvent; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.editor.miniuis.AssetsUI; -import oxy.bascenario.utils.ImGuiUtils; - -public class TransitionInspector { - private static FileInfo last; - - public static ScreenTransitionEvent render(ScreenTransitionEvent event) { - ScreenTransitionEvent.Builder builder = event.toBuilder(); - - AssetsUI.pick("Switch Background To", file -> last = file, "png,jpg"); - - if (last != null) { - builder.background(last); - last = null; - } - - TransitionType type = TransitionType.values()[ImGuiUtils.combo("Transition Type", event.type().ordinal(), TransitionType.getAlls())]; - builder.type(type); - - builder.waitDuration(Math.abs(ImGuiUtils.sliderInt("Wait Duration", event.waitDuration(), 0, 10000))); - builder.inDuration(Math.abs(ImGuiUtils.sliderInt("In Duration", event.inDuration(), 0, 10000))); - builder.outDuration(Math.abs(ImGuiUtils.sliderInt("Out Duration", event.outDuration(), 0, 10000))); - - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/EmoticonInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/EmoticonInspector.java deleted file mode 100644 index be70c558..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/EmoticonInspector.java +++ /dev/null @@ -1,14 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.objects; - -import oxy.bascenario.api.render.elements.emoticon.Emoticon; -import oxy.bascenario.api.render.elements.emoticon.EmoticonType; -import oxy.bascenario.utils.ImGuiUtils; - -public class EmoticonInspector { - public static Emoticon render(Emoticon emoticon) { - final Emoticon.Builder builder = emoticon.toBuilder(); - builder.type(EmoticonType.values()[ImGuiUtils.combo("Type", emoticon.type().ordinal(), EmoticonType.getAlls())]); - builder.sound(ImGuiUtils.checkbox("Play SFX", emoticon.sound())); - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/ImageInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/ImageInspector.java deleted file mode 100644 index 655c87b4..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/ImageInspector.java +++ /dev/null @@ -1,63 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.objects; - -import imgui.ImGui; -import oxy.bascenario.api.event.PopupEvent; -import oxy.bascenario.api.render.elements.emoticon.Emoticon; -import oxy.bascenario.api.render.elements.emoticon.EmoticonType; -import oxy.bascenario.api.render.elements.image.AnimatedImage; -import oxy.bascenario.api.render.elements.image.Image; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.editor.miniuis.AssetsUI; -import oxy.bascenario.utils.ImGuiUtils; - -public class ImageInspector { - private static FileInfo last, last1, last2; - - public static PopupEvent render(PopupEvent event) { - final PopupEvent.Builder builder = event.toBuilder(); - PopupEvent.Type type = PopupEvent.Type.values()[ImGuiUtils.combo("Type", event.type().ordinal(), PopupEvent.Type.getAlls())]; - builder.type(type); - if (type == PopupEvent.Type.SET) { - AssetsUI.pick("Pick Image!", file -> last2 = file, "png,jpg"); - if (last2 != null) { - builder.popup(last2); - last2 = null; - } - } - - return builder.build(); - } - - public static AnimatedImage render(AnimatedImage image) { - AnimatedImage.Builder builder = image.toBuilder(); - AssetsUI.pick("Pick Gif!", file -> last = file, "gif"); - if (last != null) { - builder.file(last); - last = null; - } - - builder.start(ImGuiUtils.sliderInt("Start Position (ms)", (int) image.start(), 0, 20000)); - builder.loop(ImGuiUtils.checkbox("Loop", image.loop())); - - ImGui.separatorText(""); - - builder.color(ImGuiUtils.color("Color", image.color())); - builder.width(ImGuiUtils.sliderInt("Width", image.width(), 0, 1920)); - builder.height(ImGuiUtils.sliderInt("Height", image.height(), 0, 1080)); - return builder.build(); - } - - public static Image render(Image image) { - Image.Builder builder = image.toBuilder(); - AssetsUI.pick("Pick Image!", file -> last1 = file, "png,jpg"); - if (last1 != null) { - builder.file(last1); - last1 = null; - } - - builder.color(ImGuiUtils.color("Color", image.color())); - builder.width(ImGuiUtils.sliderInt("Width", image.width(), 0, 1920)); - builder.height(ImGuiUtils.sliderInt("Height", image.height(), 0, 1080)); - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/LocationInfoInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/LocationInfoInspector.java deleted file mode 100644 index f23402fd..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/LocationInfoInspector.java +++ /dev/null @@ -1,15 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.objects; - -import oxy.bascenario.api.render.elements.LocationInfo; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.utils.ImGuiUtils; - -public class LocationInfoInspector { - public static LocationInfo render(LocationInfo info) { - final LocationInfo.Builder builder = info.toBuilder(); - builder.font(FontType.values()[ImGuiUtils.combo("Font Type", info.font().ordinal(), FontType.getAlls())]); - builder.location(ImGuiUtils.inputText("Location Name", info.location())); - builder.fade(ImGuiUtils.sliderInt("Fade Duration", info.fade(), 0, 5000)); - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/PreviewInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/PreviewInspector.java deleted file mode 100644 index bdcbd9d0..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/PreviewInspector.java +++ /dev/null @@ -1,35 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.objects; - -import imgui.ImGui; -import oxy.bascenario.api.render.elements.Preview; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.editor.miniuis.AssetsUI; -import oxy.bascenario.utils.ImGuiUtils; - -public class PreviewInspector { - private static FileInfo last; - - public static Preview render(Preview preview) { - final Preview.Builder builder = preview.toBuilder(); - builder.type(FontType.values()[ImGuiUtils.combo("Font Type", preview.type().ordinal(), FontType.getAlls())]); - builder.title(ImGuiUtils.inputText("Title", preview.title())); - builder.subtitle(ImGuiUtils.inputText("Subtitle", preview.subtitle())); - - AssetsUI.pick("Pick Image!", file -> last = file, "png,jpg"); - - if (preview.background() != null) { - ImGui.sameLine(); - if (ImGui.button("Remove background!")) { - builder.background(null); - } - } - - if (last != null) { - builder.background(last); - last = null; - } - - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/ShapeInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/ShapeInspector.java deleted file mode 100644 index f849f205..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/ShapeInspector.java +++ /dev/null @@ -1,24 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.objects; - -import oxy.bascenario.api.render.elements.shape.Circle; -import oxy.bascenario.api.render.elements.shape.Rectangle; -import oxy.bascenario.utils.ImGuiUtils; - -public class ShapeInspector { - public static Circle render(Circle circle) { - Circle.Builder builder = circle.toBuilder(); - builder.radius(ImGuiUtils.sliderFloat("Radius", circle.radius(), 0, 1000)); - builder.outlineOnly(ImGuiUtils.checkbox("Outline Only", circle.outlineOnly())); - builder.color(ImGuiUtils.color("Color", circle.color())); - return builder.build(); - } - - public static Rectangle render(Rectangle rectangle) { - Rectangle.Builder builder = rectangle.toBuilder(); - builder.width(ImGuiUtils.sliderInt("Width", rectangle.width(), 0, 1920)); - builder.height(ImGuiUtils.sliderInt("Height", rectangle.height(), 0, 1080)); - builder.outlineOnly(ImGuiUtils.checkbox("Outline Only", rectangle.outlineOnly())); - builder.color(ImGuiUtils.color("Color", rectangle.color())); - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/SpriteInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/SpriteInspector.java deleted file mode 100644 index a61d54b4..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/SpriteInspector.java +++ /dev/null @@ -1,35 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.objects; - -import imgui.ImGui; -import oxy.bascenario.api.render.elements.Sprite; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.editor.miniuis.AssetsUI; - -public class SpriteInspector { - private static FileInfo skeleton, atlas; - - public static Sprite render(Sprite sprite) { - Sprite.Builder builder = sprite.toBuilder(); - if (skeleton != null) { - builder.skeleton(skeleton); - skeleton = null; - } - - if (atlas != null) { - builder.atlas(atlas); - atlas = null; - } - - AssetsUI.pick("Pick Skeleton", file -> skeleton = file, "skel"); - AssetsUI.pick("Pick Atlas", file -> atlas = file, "atlas"); - - if (sprite.skeleton() != null) { - ImGui.textUnformatted("Skeleton: " + sprite.skeleton()); - } - if (sprite.atlas() != null) { - ImGui.textUnformatted("Atlas: " + sprite.atlas()); - } - - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/TextInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/TextInspector.java deleted file mode 100644 index f5dba903..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/objects/TextInspector.java +++ /dev/null @@ -1,34 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.objects; - -import imgui.ImGui; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.render.elements.text.AnimatedText; -import oxy.bascenario.api.render.elements.text.Text; -import oxy.bascenario.api.render.elements.text.TextSegment; -import oxy.bascenario.editor.inspector.impl.other.TextSegmentInspector; -import oxy.bascenario.utils.ImGuiUtils; - -public class TextInspector { - public static Text render(Scenario.Builder scenario, Text text) { - if (ImGui.button("New segment!")) { - text.segments().add(new TextSegment()); - } - - Text.Builder builder = text.toBuilder(); - builder.size(ImGuiUtils.sliderInt("Font Size", text.size(), 1, 150)); - builder.segments(TextSegmentInspector.render(scenario, text.segments())); - return builder.build(); - } - - public static AnimatedText render(Scenario.Builder scenario, AnimatedText text) { - if (ImGui.button("New segment!")) { - text.segments().add(new TextSegment()); - } - - AnimatedText.Builder builder = text.toBuilder(); - builder.playSpeed(ImGuiUtils.sliderFloat("Play Speed", text.playSpeed(), 0.01f, 50)); - builder.size(ImGuiUtils.sliderInt("Font Size", text.size(), 1, 150)); - builder.segments(TextSegmentInspector.render(scenario, text.segments())); - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/other/TextSegmentInspector.java b/editor/src/main/java/oxy/bascenario/editor/inspector/impl/other/TextSegmentInspector.java deleted file mode 100644 index d55308ec..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/inspector/impl/other/TextSegmentInspector.java +++ /dev/null @@ -1,90 +0,0 @@ -package oxy.bascenario.editor.inspector.impl.other; - -import imgui.ImGui; -import imgui.type.ImBoolean; -import net.lenni0451.commons.color.Color; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.render.elements.text.font.FontStyle; -import oxy.bascenario.api.render.elements.text.TextSegment; -import oxy.bascenario.api.render.elements.text.TextStyle; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.editor.miniuis.AssetsUI; -import oxy.bascenario.utils.ImGuiUtils; - -import java.util.*; - -public class TextSegmentInspector { - public static List render(Scenario.Builder scenario, List segments) { - final List list = new ArrayList<>(); - for (TextSegment segment : segments) { - final ImBoolean imBoolean = new ImBoolean(true); - if (ImGui.collapsingHeader("Text Segment##" + ImGuiUtils.COUNTER++, imBoolean)) { - segment = render(scenario, segment); - } - - if (imBoolean.get()) { - list.add(segment); - } - } - - return list; - } - - private static FileInfo last; - public static TextSegment render(Scenario.Builder scenario, TextSegment segment) { - TextSegment.Builder builder = segment.toBuilder(); - builder.text(ImGuiUtils.inputMultiLineText("Text", segment.text())); - - if (ImGuiUtils.checkbox("Custom Font", segment.font().file() != null)) { - if (segment.font().file() == null) { - // We kinda need to set this to something first. - builder.font(new FileInfo("assets/base/fonts/global/NotoSans-Regular.ttf", false, true)); - } - - AssetsUI.pick("Pick Font!", file -> last = file, "ttf"); - ImGui.sameLine(); - ImGui.textUnformatted(segment.font().file() == null ? "" : segment.font().file().path()); - } else { - builder.font((FileInfo) null); - - int type = segment.font().type() == null ? 0 : segment.font().type().ordinal(); - int style = segment.font().style() == null ? 0 : segment.font().style().ordinal(); - builder.type(FontType.values()[ImGuiUtils.combo("Font Type", type, FontType.getAlls())]); - builder.style(FontStyle.values()[ImGuiUtils.combo("Font Style", style, FontStyle.getAlls())]); - } - - if (last != null) { - builder.font(last); - last = null; - } - - ImGui.separatorText(""); - - builder.color(ImGuiUtils.color("Text Color", segment.color())); - - if (ImGuiUtils.checkbox("Outline", segment.outline().isPresent())) { - builder.outline(ImGuiUtils.color("Outline Color", segment.outline().isEmpty() ? Color.WHITE : segment.outline().get())); - } else { - builder.outline(null); - } - - ImGui.separatorText(""); - - boolean shadow = ImGuiUtils.checkbox("Shadow", segment.styles().contains(TextStyle.SHADOW)); - boolean bold = ImGuiUtils.checkbox("Bold", segment.styles().contains(TextStyle.BOLD)); - boolean italic = ImGuiUtils.checkbox("Italic", segment.styles().contains(TextStyle.ITALIC)); - boolean underline = ImGuiUtils.checkbox("Underline", segment.styles().contains(TextStyle.UNDERLINE)); - boolean strikethrough = ImGuiUtils.checkbox("Strikethrough", segment.styles().contains(TextStyle.STRIKETHROUGH)); - final Set styles = EnumSet.noneOf(TextStyle.class); - if (shadow) styles.add(TextStyle.SHADOW); - if (bold) styles.add(TextStyle.BOLD); - if (italic) styles.add(TextStyle.ITALIC); - if (underline) styles.add(TextStyle.UNDERLINE); - if (strikethrough) styles.add(TextStyle.STRIKETHROUGH); - builder.styles().clear(); - builder.styles().addAll(styles); - - return builder.build(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/miniuis/ActionsUI.java b/editor/src/main/java/oxy/bascenario/editor/miniuis/ActionsUI.java deleted file mode 100644 index e91d0685..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/miniuis/ActionsUI.java +++ /dev/null @@ -1,286 +0,0 @@ -package oxy.bascenario.editor.miniuis; - -import imgui.ImGui; -import imgui.ImVec2; -import imgui.flag.ImGuiHoveredFlags; -import lombok.RequiredArgsConstructor; -import net.lenni0451.commons.color.Color; -import oxy.bascenario.api.effects.*; -import oxy.bascenario.api.event.*; -import oxy.bascenario.api.event.LockClickEvent; -import oxy.bascenario.api.event.background.ClearBackgroundEvent; -import oxy.bascenario.api.event.background.SetBackgroundEvent; -import oxy.bascenario.api.event.color.ColorOverlayEvent; -import oxy.bascenario.api.event.animation.PlayAnimationEvent; -import oxy.bascenario.api.event.animation.SpriteAnimationEvent; -import oxy.bascenario.api.event.animation.StopAnimationEvent; -import oxy.bascenario.api.event.api.Event; -import oxy.bascenario.api.event.color.SetColorEvent; -import oxy.bascenario.api.event.dialogue.*; -import oxy.bascenario.api.event.dialogue.enums.TextOffset; -import oxy.bascenario.api.event.element.ClearLogEvent; -import oxy.bascenario.api.event.element.ElementEffectEvent; -import oxy.bascenario.api.event.element.focus.FocusElementEvent; -import oxy.bascenario.api.event.element.focus.UnfocusElementEvent; -import oxy.bascenario.api.event.element.values.PositionElementEvent; -import oxy.bascenario.api.event.element.values.RotateElementEvent; -import oxy.bascenario.api.event.log.AddLogEvent; -import oxy.bascenario.api.event.sound.SoundVolumeEvent; -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.api.render.elements.Dialogue; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.api.utils.math.Axis; -import oxy.bascenario.api.utils.math.Vec2; -import oxy.bascenario.api.utils.math.Vec3; -import oxy.bascenario.editor.timeline.ObjectOrEvent; -import oxy.bascenario.editor.timeline.Timeline; -import oxy.bascenario.editor.screen.BaseScenarioEditorScreen; -import oxy.bascenario.editor.utils.TimeCompiler; - -import java.util.*; - -@RequiredArgsConstructor -public class ActionsUI { - private static final Dialogue DUMMY_DIALOGUE = Dialogue.builder().add("Hello World!").build(); - - private final BaseScenarioEditorScreen screen; - private final Timeline timeline; - - // TODO... - public void render() { - ImGui.begin("Actions"); - - ImGui.beginTabBar("actions_tab_bar"); - - if (ImGui.beginTabItem("All")){ - animationTab(); - dialogueTab(); - objectTab(); - backgroundTab(); - soundTab(); - otherTab(); - ImGui.endTabItem(); - } - - if (ImGui.beginTabItem("Animations")){ - animationTab(); - ImGui.endTabItem(); - } - - if (ImGui.beginTabItem("Dialogues")){ - dialogueTab(); - ImGui.endTabItem(); - } - - if (ImGui.beginTabItem("Logs")){ - logTab(); - ImGui.endTabItem(); - } - - if (ImGui.beginTabItem("Objects")){ - objectTab(); - ImGui.endTabItem(); - } - - if (ImGui.beginTabItem("Background")){ - backgroundTab(); - ImGui.endTabItem(); - } - - if (ImGui.beginTabItem("Sounds")){ - soundTab(); - ImGui.endTabItem(); - } - - if (ImGui.beginTabItem("Others")){ - otherTab(); - ImGui.endTabItem(); - } - - ImGui.endTabBar(); - - ImGui.end(); - } - - private void otherTab() { - add("Screen Color", - "This will cover the entire screen with a color you choose on a render layer that you also choose.", - new ColorOverlayEvent(RenderLayer.TOP, 500, Color.WHITE)); - - add("Lock Click", - "This lock or unlock the mouse click button to interact with the options or dialogue.", - new LockClickEvent(false)); - - add("Show Buttons", - "Choose to hide/show the auto/menu button at the top right of the screen.", - new ShowButtonsEvent(true)); - - add("Set Weather", - "Set the current weather", - new SetWeatherEvent(Weather.RAIN)); - - add("Screen Effect", - "Add or remove an effect on the screen.", - new ScreenEffectEvent(ScreenEffectEvent.Type.ADD, ScreenEffect.GRAY_FILTER)); - - add("Screen Transition", - "Transition the screen (to another background if wanted)", - new ScreenTransitionEvent(null, TransitionType.HORIZONTAL_SWIPE_LR, 1000, 700, 700)); - - add("Image Popup", - "Show an image popup on the screen", - new PopupEvent(PopupEvent.Type.SET, null)); - } - - private void soundTab() { - add("Sound Volume", - "Change a playing sound volume.", - new SoundVolumeEvent(0, 1000, 1, Easing.LINEAR)); - } - - private void backgroundTab() { - add("Set Background", - "A simple way to change the background with fade duration.", - new SetBackgroundEvent(new FileInfo("assets/base/black.png", false, true), 500)); - - add("Clear Background", - "Clear the current background and fade it to black!", - new ClearBackgroundEvent(500)); - } - - private void objectTab() { - add("Move Object", - "This move an object.", - new PositionElementEvent(0, 1000, new Vec2(0, 0), Easing.LINEAR, PositionElementEvent.Type.POSITION)); - - add("Offset Object", - "Offset an object by a certain value, so that no matter where the object is the position will always be offset by that value.", - new PositionElementEvent(0, 1000, new Vec2(0, 0), Easing.LINEAR, PositionElementEvent.Type.OFFSET)); - - add("Scale Object", - "This scale an object.", - new PositionElementEvent(0, 1000, new Vec2(1, 1), Easing.LINEAR, PositionElementEvent.Type.SCALE)); - - add("Pivot Object", - "Define a \"pivot\" point, this will be the point used when rotating and the object will be rotate around this point.", - new PositionElementEvent(0, 1000, new Vec2(0, 0), Easing.LINEAR, PositionElementEvent.Type.PIVOT)); - - add("Rotate Object", - "This rotate an object.", - new RotateElementEvent(0, 1000, new Vec3(0, 0, 0), Easing.LINEAR)); - - add("Apply Effect", - "Apply an effect on top of any element on the selected track, eg: hologram, blur, rainbow.", - new ElementEffectEvent(0, Effect.HOLOGRAM, Axis.Y)); - - add("Remove Effect", - "Remove an effect that being applied on top of an element on the selected track.", - new ElementEffectEvent(0, Effect.HOLOGRAM, ElementEffectEvent.Type.REMOVE)); - - add("Color Overlay", - "Apply a color overlay, this will put a color overlay on top of the object.", - new ColorOverlayEvent(0, 500, Color.WHITE)); - - add("Set Color", - "Like color overlay, but instead of overlaying color it change the object color itself.", - new SetColorEvent(0, 500, Color.WHITE)); - - add("Focus Object", - "Focus on a object and un-focus everything else.", - new FocusElementEvent(0)); - - add("Unfocus Objects", - "Un-focus any currently focused object.", - new UnfocusElementEvent()); - } - - private void logTab() { - add("Add Log", - "Start a log which is similar to a dialogue, but will play on the screen instead of bottom of the screen.", - new AddLogEvent(AddLogEvent.Type.CLEAR, 0, TextOffset.LEFT, DUMMY_DIALOGUE)); - - add("Clear Log", - "Clear any kind of log that is on the screen.", - new ClearLogEvent()); - } - - private void dialogueTab() { - add("Start Dialogue", - "Start a dialogue like in game, with values like name, association or should this render background, and of course dialogues.", - new StartDialogueEvent(FontType.NotoSans, 0, "", "", true, DUMMY_DIALOGUE)); - - add("Add Dialogue", - "Add a new dialogue (text) onto any existing playing dialogue, allows for playing a certain dialogue text a bit late.", - new AddDialogueEvent(0, DUMMY_DIALOGUE)); - - add("Redirect Dialogue", - "Set the dialogue index to a whole number you choose.", - new RedirectDialogueEvent(0)); - - add("Close Dialogue", "Close any currently present dialogue", new CloseDialogueEvent()); - { - final LinkedHashMap map = new LinkedHashMap<>(); - map.put("Yes", 0); - map.put("No", 0); - add("Show Options", - "Show options that the user could choose, result in a dialogue index that determine which dialogue to play.", - new ShowOptionsEvent(map)); - } - add("Close Options", "Close any currently present options", new CloseOptionsEvent()); - } - - private void animationTab() { - add("Play Animation", - "Play an defined animation, default or custom with an element on the selected track.", - new PlayAnimationEvent(0, "bascenarioengine:default-shake", false)); - - add("Play Sprite Animation", - "Play an animation that is defined in the spine (.skel) file you imported to a sprite character on the selected track.", - new SpriteAnimationEvent(0, 0.2f, "Idle_01", 1)); - - add("Stop Animation", - "Stop any defined animation, (note) not sprite animation.", - new StopAnimationEvent(0, "bascenarioengine:default-shake")); - } - - private void add(String label, String tooltip, Event event) { - if (!ImGui.button(label, new ImVec2(ImGui.getWindowSize().x - 20, 50))) { - if (ImGui.isItemHovered(ImGuiHoveredFlags.DelayNormal)) { - ImGui.setTooltip(tooltip); - } - return; - } - timeline.setSelectedObject(null); - - long duration = TimeCompiler.compileTime(event); - final int track = findNonOccupiedSlot(timeline.getTimestamp(), duration); - - timeline.put(track, timeline.getTimestamp(), duration, event, null, true, null); - } - - private int findNonOccupiedSlot(long time, long duration) { - final Map> map = new HashMap<>(); - timeline.getObjects().forEach(object -> map.computeIfAbsent(object.track, n -> new ArrayList<>()).add(object)); - - int i = 0; - List track; - while ((track = map.get(i)) != null) { - boolean occupied = false; - for (ObjectOrEvent object : track) { - final long maxTime = object.start + object.duration, minTime = object.start; - if (maxTime >= time && minTime <= time + duration) { - occupied = true; - break; - } - } - - if (!occupied) { - return i; - } - i++; - } - - return i; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/miniuis/AssetsUI.java b/editor/src/main/java/oxy/bascenario/editor/miniuis/AssetsUI.java deleted file mode 100644 index 23d95af7..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/miniuis/AssetsUI.java +++ /dev/null @@ -1,152 +0,0 @@ -package oxy.bascenario.editor.miniuis; - -import imgui.ImColor; -import imgui.ImGui; -import imgui.flag.ImGuiTableColumnFlags; -import imgui.flag.ImGuiTableFlags; -import imgui.flag.ImGuiTreeNodeFlags; -import oxy.bascenario.Base; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.editor.timeline.Timeline; -import oxy.bascenario.managers.ScenarioManager; -import oxy.bascenario.utils.ImGuiUtils; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Stream; - -public class AssetsUI { - public static boolean popup; - public static Consumer consumer; - public static String filter; - - private static Object object; - public static void pick(String name, Consumer consumer, String filter) { - if (ImGui.button(name + "##" + ImGuiUtils.COUNTER++)) { - AssetsUI.filter = filter; - AssetsUI.consumer = consumer; - AssetsUI.popup = true; - object = null; - } - } - - public static void render(Timeline timeline, Scenario.Builder scenario) { - ImGui.begin("Assets"); - render(timeline, scenario, null); - ImGui.end(); - - if (popup) { - ImGui.openPopup("Select asset"); - popup = false; - } - - if (ImGui.beginPopupModal("Select asset")) { - render(timeline, scenario, consumer); - if (ImGui.button("Nevermine!")) { - ImGui.closeCurrentPopup(); - filter = null; - consumer = null; - } - ImGui.endPopup(); - } - } - - public static void render(Timeline timeline, Scenario.Builder scenario, Consumer consumer) { - if (object == null) { - object = timeline.getSelectedObject(); - } - - ImGuiUtils.pick(f -> {}, scenario, "Load File", filter != null ? filter : "*"); - ImGui.sameLine(); - ImGuiUtils.pickFolder(f -> {}, scenario, "Load Folder"); - - final List noFolders = new ArrayList<>(); - final Map> folders = new HashMap<>(); - - final Path path = new File(new File(ScenarioManager.SAVE_DIR, scenario.name()), "files").toPath(); - try (final Stream stream = Files.walk(path)) { - List files = stream.filter(Files::isRegularFile).map(Path::toFile).toList(); - - for (File file : files) { - FileInfo info = new FileInfo(file.getAbsolutePath().replace(path.toFile().getAbsolutePath() + "\\", ""), false, false); - if (filter != null && !filter.equals("*")) { - boolean valid = false; - for (String ext : filter.replace(" ", "").split(",")) { - if (info.path().toLowerCase(Locale.ROOT).endsWith("." + ext)) { - valid = true; - break; - } - } - - if (!valid) { - continue; - } - } - - if (!info.path().contains("\\")) { - noFolders.add(info); - continue; - } - - String[] split = info.path().split("\\\\"); - if (split.length != 2) { - noFolders.add(info); - continue; // We don't support stacked folder, if they do that, their fault. - } - - folders.computeIfAbsent(split[0], p -> new ArrayList<>()).add(info); - } - } catch (IOException ignored) { - } - - if (ImGui.beginTable("1ways", 1, ImGuiTableFlags.BordersV | ImGuiTableFlags.BordersOuterH | ImGuiTableFlags.Resizable | ImGuiTableFlags.RowBg | ImGuiTableFlags.NoBordersInBody)) { - ImGui.tableSetupColumn("Name", ImGuiTableColumnFlags.NoHide); - ImGui.tableHeadersRow(); - - for (Map.Entry> folder : folders.entrySet()) { - ImGui.tableNextRow(); - ImGui.tableNextColumn(); - if (ImGui.treeNodeEx(folder.getKey(), ImGuiTreeNodeFlags.SpanAllColumns | ImGuiTreeNodeFlags.DefaultOpen)) { - renderAssets(scenario.name(), timeline, folder.getValue(), consumer, true); - ImGui.treePop(); - } - } - - renderAssets(scenario.name(), timeline, noFolders, consumer, false); - - ImGui.endTable(); - } - } - - private static void renderAssets(String scenario, Timeline timeline, List files, Consumer consumer, boolean split) { - for (FileInfo file : files) { - ImGui.tableNextRow(); - ImGui.tableNextColumn(); - - if (consumer == null && filter == null) { - ImGui.text(split ? file.path().split("\\\\")[1] : file.path()); - ImGui.sameLine(); - if (ImGui.smallButton("Delete##" + ImGuiUtils.COUNTER++)) { - Base.instance().scenarioManager().file(scenario, file).delete(); - } - } else { - if (ImGui.selectable(split ? file.path().split("\\\\")[1] : file.path())) { - if (consumer != null) { - if (object == timeline.getSelectedObject()) { - consumer.accept(file); - } - AssetsUI.consumer = null; - } - - ImGui.closeCurrentPopup(); - filter = null; - } - } - } - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/miniuis/ObjectsUI.java b/editor/src/main/java/oxy/bascenario/editor/miniuis/ObjectsUI.java deleted file mode 100644 index 8f5e38c0..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/miniuis/ObjectsUI.java +++ /dev/null @@ -1,135 +0,0 @@ -package oxy.bascenario.editor.miniuis; - -import imgui.ImGui; -import imgui.ImVec2; -import imgui.flag.ImGuiHoveredFlags; -import lombok.RequiredArgsConstructor; -import net.lenni0451.commons.color.Color; -import oxy.bascenario.api.effects.Sound; -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.api.render.elements.*; -import oxy.bascenario.api.render.elements.emoticon.Emoticon; -import oxy.bascenario.api.render.elements.emoticon.EmoticonType; -import oxy.bascenario.api.render.elements.image.AnimatedImage; -import oxy.bascenario.api.render.elements.image.Image; -import oxy.bascenario.api.render.elements.shape.Circle; -import oxy.bascenario.api.render.elements.shape.Rectangle; -import oxy.bascenario.api.render.elements.text.AnimatedText; -import oxy.bascenario.api.render.elements.text.Text; -import oxy.bascenario.api.render.elements.text.TextSegment; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.api.utils.math.Vec2; -import oxy.bascenario.editor.timeline.ObjectOrEvent; -import oxy.bascenario.editor.timeline.Timeline; -import oxy.bascenario.editor.utils.TimeCompiler; -import oxy.bascenario.editor.screen.BaseScenarioEditorScreen; -import oxy.bascenario.editor.utils.SoundAsElement; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@RequiredArgsConstructor -public class ObjectsUI { - private final BaseScenarioEditorScreen screen; - private final Timeline timeline; - - public void render() { - ImGui.begin("Objects"); - - add("Preview", - "Show up the \"preview\" of the scenario like when you first play the story like in the game.\nWith title and (optional) subtitle and optional background.", - new Preview(FontType.NotoSans, "Title", "Subtitle", null)); - - add("Emoticon", - "Popup an emoji that show emotions like in game, eg: Angry, Thinking, etc.", - new Emoticon(1000, EmoticonType.NOTE, true)); - - add("Sprite", - "A character sprite (spine), in binary format (.skel), that allows for dynamic animations and other niche stuff.", - new Sprite(null, null)); - - add("Image", - "An image... It's an image.", - new Image(null, Color.WHITE, 100, 100)); - - add("Gif", - "A GIF... It's a GIF.", - new AnimatedImage(null, 0, true, Color.WHITE, 100, 100)); - - add("Text", - "A text with defined size with multiple segments, each segments with customizable font, color, etc.", - new Text(new ArrayList<>(List.of(TextSegment.builder().text("Hello World!").build())), 42)); - - add("Typing Text", - "Same as text but with typing animation.", - new AnimatedText(1, new ArrayList<>(List.of(TextSegment.builder().text("Hello World!").build())), 42)); - - add("Circle", - "round round we go.", - new Circle(20, Color.WHITE, false)); - - add("Rectangle", - """ - Why was the rectangle in love with a triangle? - She has acute angle. - """, - new Rectangle(100, 100, Color.WHITE, false)); - -// add("Triangle", null); - add("Location Info", - "Popup a box that show the current location name like in game.", - new LocationInfo(FontType.NotoSans, "Location Name", 2500, 500)); - - add("Sound", - "It's a sound, this will play a sound.", - new SoundAsElement(new Sound(0, null, 1, false), 0, 0, 0, 0)); - - ImGui.end(); - } - - private void add(String label, String tooltip, Object element) { - if (!ImGui.button(label, new ImVec2(ImGui.getWindowSize().x - 20, 50))) { - if (ImGui.isItemHovered(ImGuiHoveredFlags.DelayNormal)) { - ImGui.setTooltip(tooltip); - } - return; - } - - timeline.setSelectedObject(null); - - long duration = TimeCompiler.compileTime(element); - if (duration == Long.MAX_VALUE) { - duration = 1000L; - } - - final int track = findNonOccupiedSlot(timeline.getTimestamp(), duration); - timeline.put(track, timeline.getTimestamp(), duration, element, element instanceof SoundAsElement ? null : RenderLayer.ABOVE_DIALOGUE, true, element instanceof SoundAsElement ? null : new Vec2(0, 0)); - } - - private int findNonOccupiedSlot(long time, long duration) { - final Map> map = new HashMap<>(); - timeline.getObjects().forEach(object -> map.computeIfAbsent(object.track, n -> new ArrayList<>()).add(object)); - - int i = 0; - List track; - while ((track = map.get(i)) != null) { - boolean occupied = false; - for (ObjectOrEvent object : track) { - final long maxTime = object.start + object.duration, minTime = object.start; - if (maxTime >= time && minTime <= time + duration) { - occupied = true; - break; - } - } - - if (!occupied) { - return i; - } - i++; - } - - return i; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/screen/BaseScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/screen/BaseScenarioEditorScreen.java deleted file mode 100644 index 3a0e7236..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/screen/BaseScenarioEditorScreen.java +++ /dev/null @@ -1,166 +0,0 @@ -package oxy.bascenario.editor.screen; - -import com.badlogic.gdx.Screen; -import imgui.ImGui; -import imgui.ImGuiViewport; -import imgui.flag.ImGuiCol; -import imgui.flag.ImGuiDockNodeFlags; -import imgui.flag.ImGuiKey; -import imgui.flag.ImGuiWindowFlags; -import lombok.Getter; -import net.raphimc.thingl.ThinGL; -import oxy.bascenario.Base; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.editor.miniuis.AssetsUI; -import oxy.bascenario.editor.miniuis.ObjectsUI; -import oxy.bascenario.editor.miniuis.ActionsUI; -import oxy.bascenario.editor.timeline.Timeline; -import oxy.bascenario.editor.inspector.Inspector; - -import oxy.bascenario.editor.utils.TrackParser; -import oxy.bascenario.utils.ExtendableScreen; -import oxy.bascenario.utils.Launcher; -import oxy.bascenario.utils.thingl.ThinGLUtils; - -public class BaseScenarioEditorScreen extends ExtendableScreen { - private final Screen prevScreen; - - @Getter - protected final Scenario.Builder scenario; - protected final Timeline timeline; - protected final ObjectsUI objectsUI; - protected final ActionsUI eventAdder; - protected final Inspector inspector; - - public BaseScenarioEditorScreen(Screen prevScreen, Scenario.Builder scenario) { - this.prevScreen = prevScreen; - this.scenario = scenario; - this.timeline = new Timeline(this, scenario); - this.objectsUI = new ObjectsUI(this, this.timeline); - this.eventAdder = new ActionsUI(this, this.timeline); - this.inspector = new Inspector(this, this.timeline); - } - - @Override - public void render(float delta) { - ImGui.dockSpaceOverViewport(0, new ImGuiViewport(0), ImGuiDockNodeFlags.PassthruCentralNode); - - ImGui.getStyle().setColor(ImGuiCol.WindowBg, 0.098f, 0.098f, 0.098f, 1f); - - renderMenuBar(); - timeline.render(); - objectsUI.render(); - eventAdder.render(); - inspector.render(); - AssetsUI.render(timeline, scenario); - - ImGui.getStyle().setColor(ImGuiCol.WindowBg, 0, 0, 0, 0); - ImGui.begin("Scenario View", ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.MenuBar); - ImGui.beginMenuBar(); - if (ImGui.menuItem("Play", false, !timeline.isPlaying())) { - setPlaying(true); - } - if (ImGui.menuItem("Pause", false, timeline.isPlaying())) { - setPlaying(false); - } - ImGui.endMenuBar(); - renderScenarioWindow(); - ImGui.end(); - - boolean control = ImGui.isKeyDown(ImGuiKey.RightCtrl) || ImGui.isKeyDown(ImGuiKey.LeftCtrl); - if ((control) && ImGui.isKeyReleased(ImGuiKey.Z)) { - timeline.undo(); - } - } - - public void update() { - } - - protected final void renderScenarioWindow() { - float x = ThinGL.windowInterface().getFramebufferWidth() / 1920F; - ThinGLUtils.GLOBAL_RENDER_STACK.pushMatrix(); - ThinGLUtils.GLOBAL_RENDER_STACK.scale(1 / x, 1080F / ThinGL.windowInterface().getFramebufferHeight(), x); - ThinGLUtils.GLOBAL_RENDER_STACK.translate(ImGui.getWindowPosX(), ImGui.getWindowPosY() + 46, 0); - ThinGLUtils.GLOBAL_RENDER_STACK.scale(ImGui.getWindowSizeX() / 1920f, (ImGui.getWindowSizeY() - 46) / 1080f, ImGui.getWindowSizeX() / 1920f); - renderScenario(); - ThinGLUtils.GLOBAL_RENDER_STACK.popMatrix(); - } - - public void renderScenario() { - } - - // TODO. - private void renderMenuBar() { - ImGui.beginMainMenuBar(); - - if (ImGui.beginMenu("Timeline")) { - if (ImGui.menuItem("Play", false, !timeline.isPlaying())) { - setPlaying(true); - } - if (ImGui.menuItem("Pause", false, timeline.isPlaying())) { - setPlaying(false); - } - ImGui.endMenu(); - } - - if (ImGui.beginMenu("Edit")) { - if (ImGui.menuItem("Undo", "Ctrl+Z", false, timeline.canUndo())) { - timeline.undo(); - } - ImGui.endMenu(); - } - - if (ImGui.menuItem("Save")) { - save(); - } - - if (ImGui.beginMenu("Exit")) { - if (ImGui.menuItem("Exit and Save")) { - save(); - Launcher.WINDOW.setScreen(prevScreen); - } - if (ImGui.menuItem("Exit and Discard")) { - confirmExitAndDiscard = true; - } - ImGui.endMenu(); - } - - ImGui.endMainMenuBar(); - - if (confirmExitAndDiscard) { - ImGui.openPopup("AreYouSure"); - confirmExitAndDiscard = false; - } - - if (ImGui.beginPopupModal("AreYouSure", ImGuiWindowFlags.NoResize)) { - ImGui.text("You really want to discard all that hard works? Are you sure?"); - if (ImGui.button("Never mind let's save and quit!")) { - save(); - Launcher.WINDOW.setScreen(prevScreen); - } - ImGui.sameLine(); - if (ImGui.button("Yep!")) { - Launcher.WINDOW.setScreen(prevScreen); - } - ImGui.endPopup(); - } - } - - private boolean confirmExitAndDiscard; - - public void setPlaying(boolean playing) { - timeline.setPlaying(playing); - } - - private void save() { - scenario.timestamps().clear(); - scenario.timestamps().addAll(TrackParser.parse(this.timeline.getObjects())); - - Base.instance().scenarioManager().put(scenario.name(), scenario.build()); - try { - Base.instance().scenarioManager().saveToPath(scenario.build()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/screen/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/screen/ScenarioEditorScreen.java deleted file mode 100644 index 6898e159..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/screen/ScenarioEditorScreen.java +++ /dev/null @@ -1,144 +0,0 @@ -package oxy.bascenario.editor.screen; - -import com.badlogic.gdx.Screen; -import imgui.ImGui; -import imgui.ImVec2; -import net.raphimc.thingl.ThinGL; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.event.dialogue.AddDialogueEvent; -import oxy.bascenario.api.event.dialogue.ShowOptionsEvent; -import oxy.bascenario.api.event.dialogue.StartDialogueEvent; -import oxy.bascenario.editor.timeline.ObjectOrEvent; -import oxy.bascenario.editor.utils.TrackParser; -import oxy.bascenario.screens.ScenarioScreen; -import oxy.bascenario.utils.TimeUtils; - -import java.util.*; - -public final class ScenarioEditorScreen extends BaseScenarioEditorScreen { - private ScenarioScreen screen; - - public ScenarioEditorScreen(Screen screen, Scenario scenario, Scenario.Builder builder) { - super(screen, builder); - this.screen = new ScenarioScreen(scenario, false); - this.screen.setPlaying(false); - } - - @Override - public void show() { - super.show(); - this.screen.show(); - } - - @Override - public void resize(int width, int height) { - super.resize(width, height); - screen.resize(width, height); - } - - @Override - public void dispose() { - super.dispose(); - screen.dispose(); - } - - @Override - public void render(float delta) { - boolean busyDialogue = screen.isBusyDialogue() && !screen.getDialogueRenderer().isBusy(); - boolean busyOptions = screen.isBusyOptions() && !(screen.getOptionsRenderer().getScale().isRunning() && screen.getOptionsRenderer().getScale().getTarget() == 1); - timeline.setTickTime(!busyDialogue && !busyOptions); - super.render(delta); - } - - ImVec2 windowSize = new ImVec2(0, 0), windowPos = new ImVec2(0, 0); - @Override - public void renderScenario() { - windowSize = ImGui.getWindowSize(); - windowPos = ImGui.getWindowPos(); - screen.render(0); - } - - @Override - public void mouseClicked(double mouseX, double mouseY, int button) { - if (!timeline.isPlaying()) { - return; - } - - mouseX *= (ThinGL.windowInterface().getFramebufferWidth() / 1920F); - mouseY *= (ThinGL.windowInterface().getFramebufferHeight() / 1080F); - mouseX -= windowPos.x; - mouseY -= windowPos.y + 46; - - mouseX /= (windowSize.x / 1920f); - mouseY /= ((windowSize.y - 46) / 1080f); - - screen.mouseClicked(mouseX, mouseY, button); - } - - @Override - public void mouseRelease() { - if (!timeline.isPlaying()) { - return; - } - - screen.mouseRelease(); - } - - @Override - public void update() { - screen = new ScenarioScreen(screen.getScenario(), false); - screen.show(); - screen.getTimestamps().clear(); - screen.getTimestamps().addAll(TrackParser.parse(timeline.getObjects())); - screen.setPlaying(false); - - long start = System.currentTimeMillis(); - TimeUtils.fakeTimeMillis = start - timeline.getTimestamp(); - long lastDuration = 0, last = 0, prev = 0; - for (ObjectOrEvent object : this.timeline.getObjects()) { - if (object.start > timeline.getTimestamp()) { - break; - } - - long duration = object.duration; - screen.sinceDialogue = screen.sincePoll = lastDuration + (object.start - last); - - TimeUtils.fakeTimeMillis += object.start - prev; - screen.pollEvents(true); -// renderScenarioWindow(); - - lastDuration = duration; - last = object.start + duration; - prev = object.start; - - if (timeline.getTimestamp() > object.start + duration) { - if (object.object instanceof StartDialogueEvent || object.object instanceof AddDialogueEvent) { - screen.setBusyDialogue(false); - } else if (object.object instanceof ShowOptionsEvent) { - screen.getOptionsRenderer().setOptions(null, null); - screen.setBusyOptions(false); - } - } - } - - TimeUtils.fakeTimeMillis = timeline.isPlaying() ? null : System.currentTimeMillis(); - renderScenarioWindow(); - - screen.setPlaying(timeline.isPlaying()); - screen.sinceDialogue = screen.sincePoll = timeline.getTimestamp() - (last - lastDuration); - } - - @Override - public void setPlaying(boolean playing) { - if (playing) { - if (!timeline.isPlaying()) { - update(); - } - TimeUtils.fakeTimeMillis = null; - } else { - TimeUtils.fakeTimeMillis = System.currentTimeMillis(); - } - super.setPlaying(playing); - this.screen.setPlaying(playing); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectDragDrop.java b/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectDragDrop.java deleted file mode 100644 index d408a5fb..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectDragDrop.java +++ /dev/null @@ -1,36 +0,0 @@ -package oxy.bascenario.editor.timeline; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -@ToString -@RequiredArgsConstructor -public abstract class ObjectDragDrop { - public final ObjectOrEvent object; - - public long nearestTime = Long.MAX_VALUE; - public void time(long time, long last) { - if (time < last && Math.abs(time - last) < Math.abs(time - nearestTime)) { - nearestTime = last; - } - } - - @Getter - private boolean waiting; - public boolean loop, second; - public void waitForResult() { - this.waiting = true; - } - - @Getter - private boolean rejected; - public void reject() { - this.rejected = true; - } - public void reset() { - this.rejected = false; - } - - public abstract void accept(); -} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectOrEvent.java b/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectOrEvent.java deleted file mode 100644 index 45953d67..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectOrEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package oxy.bascenario.editor.timeline; - -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.api.utils.math.Vec2; - -public class ObjectOrEvent { - public int track; - public long start, duration; - public ObjectRenderer renderer; - - public Object object; - public RenderLayer layer; - public boolean requireWait; - public Vec2 vec2; - - public ObjectOrEvent(Timeline timeline, int track, long start, long duration, Object object, RenderLayer layer, boolean requireWait, Vec2 vec2) { - this.track = track; - this.start = start; - this.duration = duration; - this.renderer = new ObjectRenderer(timeline, this); - this.object = object; - this.layer = layer; - this.requireWait = requireWait; - this.vec2 = vec2; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectRenderer.java b/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectRenderer.java deleted file mode 100644 index 5395b1ee..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/ObjectRenderer.java +++ /dev/null @@ -1,256 +0,0 @@ -package oxy.bascenario.editor.timeline; - -import com.badlogic.gdx.math.MathUtils; -import imgui.ImColor; -import imgui.ImDrawList; -import imgui.ImGui; -import imgui.ImVec2; -import lombok.RequiredArgsConstructor; -import net.raphimc.thingl.ThinGL; -import net.raphimc.thingl.implementation.window.GLFWWindowInterface; -import net.raphimc.thingl.text.TextRun; -import org.lwjgl.glfw.GLFW; -import org.spongepowered.configurate.yaml.internal.snakeyaml.external.com.google.gdata.util.common.base.Escaper; -import oxy.bascenario.editor.utils.NameUtils; -import oxy.bascenario.editor.utils.TimeCompiler; -import oxy.bascenario.utils.font.FontUtils; -import oxy.bascenario.utils.font.TextUtils; - -@RequiredArgsConstructor -public class ObjectRenderer { - private final Timeline timeline; - private final ObjectOrEvent object; - - public float x, y, width; - - public void render() { - if (object.track < timeline.getVerticalScroll()) { - return; - } - - // This part determine render position and allat. - final float maxTime = Timeline.DEFAULT_MAX_TIME * timeline.getScale(); - - final ImVec2 size = ImGui.getWindowSize(), pos = ImGui.getWindowPos(); - float trackX = pos.x + size.x / 4, trackWidth = size.x - (size.x / 4); - float trackY = pos.y + 80 * ImGui.getStyle().getFontScaleDpi() + ((object.track - timeline.getVerticalScroll()) * 50 * ImGui.getStyle().getFontScaleDpi()); - - this.width = (object.duration / maxTime) * trackWidth; - if (!timeline.isDragging(this.object)) { // Fixed the position if not dragging.... - this.x = trackX + (object.start / maxTime) * trackWidth; - this.y = trackY; - - if (timeline.getScroll() > 0) { - final float ratio = (Timeline.DEFAULT_MAX_TIME * timeline.getScroll() * timeline.getScale()) / (Timeline.DEFAULT_MAX_TIME * timeline.getScale()); - this.x -= ratio * (size.x - size.x / 4); - } - - float distance = x - (pos.x + size.x / 4); - x = Math.max(x, pos.x + size.x / 4); - if (distance < 0) { - this.width = Math.max(0, distance + width); - } - } - - if (this.width <= 0) { - return; - } - - handleDraggingResult(); - - if (ImGui.isWindowFocused()) { - handleMouse(); - } - - // This part actually draw the object, nothing crazy here... - final ImDrawList drawList = ImGui.getWindowDrawList(); - drawList.addRectFilled(new ImVec2(x, y), new ImVec2(x + width, y + 50 * ImGui.getStyle().getFontScaleDpi()), ImColor.rgb(0.24f, 0.34f, 0.44f), 5f); - if (timeline.getSelectedObject() == this.object) { - drawList.addRect(new ImVec2(x, y), new ImVec2(x + width, y + 50 * ImGui.getStyle().getFontScaleDpi()), ImColor.rgb(255, 255, 255), 5f); - } - - String s = NameUtils.name(object.object); - float width = TextUtils.getVisualWidth(17 * ImGui.getStyle().getFontScaleDpi(), TextRun.fromString(FontUtils.DEFAULT, s).shape()); - if (width == 0) { - return; - } - s = s.substring(0, MathUtils.floor(Math.min((this.width / width) * s.length(), s.length()))); - - drawList.addText(new ImVec2(x + 5, y + 5), ImColor.rgb(255, 255, 255), s); - } - - private void handleDraggingResult() { - // This handle the current dragging object see if it overlaps and other stuff. - if (timeline.isDragging(this.object) || !timeline.isDragging()) { - return; - } - - final ObjectOrEvent dragging = timeline.getDraggingObject().object; - final ImVec2 size = ImGui.getWindowSize(), pos = ImGui.getWindowPos(); - float x = Math.max(ImGui.getMousePosX() - dragging.renderer.draggingX, pos.x + size.x / 4); - float y = Math.max(ImGui.getMousePosY() - dragging.renderer.draggingY, pos.y + 80 * ImGui.getStyle().getFontScaleDpi()); - float width = dragging.renderer.width; - - // This part check for object snapping. - float endDis = Math.abs((this.x + this.width) - x), startDis = Math.abs(this.x - (x + width)); - if (endDis < 3) { - ImGui.getWindowDrawList().addRectFilled(new ImVec2(this.x + this.width, ImGui.getWindowPosY()), new ImVec2(this.x + this.width + 1, ImGui.getWindowPosY() + ImGui.getWindowSizeY()), ImColor.rgb(255, 255, 255)); - dragging.renderer.draggingX = -((this.x + this.width + 0.01f) - ImGui.getMousePosX()); - } else if (startDis < 3) { - ImGui.getWindowDrawList().addRectFilled(new ImVec2(this.x - 1, ImGui.getWindowPosY()), new ImVec2(this.x, ImGui.getWindowPosY() + ImGui.getWindowSizeY()), ImColor.rgb(255, 255, 255)); - dragging.renderer.draggingX = -((this.x - width - 0.01f) - ImGui.getMousePosX()); - } - - // This code part checks if the drag result is overlapping something else. - if (!timeline.getDraggingObject().isWaiting()) { - return; - } - - int track = trackFromY(timeline, y); - if (track != this.object.track) { - return; // Not the same track, skip! - } - - if (!timeline.getDraggingObject().second) { - final float ratio = (object.renderer.x - pos.x - size.x / 4) / (size.x - size.x / 4); - long time = (long) (Timeline.DEFAULT_MAX_TIME * timeline.getScale() * timeline.getScroll() + ratio * Timeline.DEFAULT_MAX_TIME * timeline.getScale()); - // Determine the close object time to snap to. - timeline.getDraggingObject().time(time, object.start + object.duration); - } else { - long min = timeline.getDraggingObject().nearestTime + 1, max = min + object.duration; - if (max >= object.start && min < object.start + object.duration) { - timeline.getDraggingObject().reject(); - } - return; - } - - // Overlap! This result should not be valid. - if (x + width >= this.x && x <= this.x + this.width) { - timeline.getDraggingObject().reject(); - } - } - - private float draggingX, draggingY; - private void handleMouse() { - if (handleDurationResize()) { - return; - } - - final boolean over = ImGui.isMouseHoveringRect(new ImVec2(x, y), new ImVec2(x + width, y + 50 * ImGui.getStyle().getFontScaleDpi())); - final ImVec2 mouse = ImGui.getMousePos(), pos = ImGui.getWindowPos(), size = ImGui.getWindowSize(); - - boolean dragging = timeline.isDragging(this.object); - if (dragging && !timeline.getDraggingObject().isWaiting()) { - this.x = Math.max(mouse.x - this.draggingX, pos.x + size.x / 4); - this.y = Math.max(mouse.y - this.draggingY, pos.y + 80 * ImGui.getStyle().getFontScaleDpi()); - } - - float length = ImGui.getMouseDragDelta().x * ImGui.getMouseDragDelta().x + ImGui.getMouseDragDelta().y * ImGui.getMouseDragDelta().y; - if (ImGui.isMouseClicked(0) && over) { - timeline.setSelectedObject(this.object); // Selected an element. - } else if (ImGui.isMouseDown(0) && !timeline.isDragging() && over && length > 1) { - // Start dragging an object if we can! - timeline.setDraggingObject(new ObjectDragDrop(this.object) { - @Override - public void accept() { - final ImVec2 pos = ImGui.getWindowPos(), size = ImGui.getWindowSize(); - - final long oldStart = object.start; - final int oldTrack = object.track; - if (second) { - object.start = nearestTime + 1; - } else { - final float ratio = (object.renderer.x - pos.x - size.x / 4) / (size.x - size.x / 4); - object.start = (long) (Timeline.DEFAULT_MAX_TIME * timeline.getScale() * timeline.getScroll() + ratio * Timeline.DEFAULT_MAX_TIME * timeline.getScale()); - } - object.track = trackFromY(timeline, object.renderer.y); - - timeline.queueUndo(() -> { - object.start = oldStart; - object.track = oldTrack; - }); - } - }); - - this.draggingX = mouse.x - this.x; - this.draggingY = mouse.y - this.y; - dragging = true; - } - - // Stop dragging, now we wait for result..... - if (dragging && !ImGui.isMouseDown(0)) { - timeline.getDraggingObject().waitForResult(); - } - } - - private boolean resizing; - private boolean handleDurationResize() { - if (timeline.isDragging()) { - this.resizing = false; - return false; - } - - final ImVec2 mouse = ImGui.getMousePos(), size = ImGui.getWindowSize(); - final boolean yMatch = mouse.y >= y && mouse.y <= y + (50 * ImGui.getStyle().getFontScaleDpi()); - if (resizing && yMatch) { // If we're already resizing. we can't check for x axis xD. - float ratio = (mouse.x - (x + width)) / (size.x - size.x / 4); - long duration = (long) (Timeline.DEFAULT_MAX_TIME * timeline.getScale() * ratio); - - // I wonder how fast this is.... - int next = timeline.getObjects().indexOf(this.object); - if (next != -1 && next + 1 != timeline.getObjects().size()) { - ObjectOrEvent nextObject = timeline.getObjects().get(next + 1); - if (nextObject.track == this.object.track) { - duration = Math.min(duration, nextObject.start - (object.start + object.duration)); - } - } - - if (TimeCompiler.canResize(object.object)) { - final long oldDuration = object.duration; - final Object oldObject = object; - final float oldWidth = this.width; - object.duration = Math.max(0, object.duration + duration); - object.object = TimeCompiler.addTime(object.object, (int) duration); - - this.width = ((float) object.duration / (Timeline.DEFAULT_MAX_TIME * timeline.getScale())) * (size.x - size.x / 4); - timeline.queueUndo(() -> { - this.width = oldWidth; - this.object.duration = oldDuration; - this.object.object = oldObject; - }); - } - - resizing = ImGui.isMouseDown(0); - return true; - } - - resizing = false; - float xDistanceToMax = Math.abs(mouse.x - (x + width)); - if (xDistanceToMax > 5 || !yMatch) { - return false; - } - - if (!ImGui.isMouseDown(0)) { - return false; - } -// GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_HRESIZE_CURSOR)); - - timeline.setDraggingObject(null); - resizing = true; - return true; - } - - private static int trackFromY(Timeline timeline, float y) { - float approximateTrack = (y - (ImGui.getWindowPosY() + 80)) / (50f * ImGui.getStyle().getFontScaleDpi()); - int ceil = MathUtils.ceil(approximateTrack), floor = MathUtils.floor(approximateTrack); - // See if floor or ceil is closer... - int trackId; - if (Math.abs(y - (ImGui.getWindowPosY() + 80 * ImGui.getStyle().getFontScaleDpi() + (50 * ImGui.getStyle().getFontScaleDpi() * ceil))) > Math.abs(y - (ImGui.getWindowPosY() + 80 * ImGui.getStyle().getFontScaleDpi() + (50 * ImGui.getStyle().getFontScaleDpi() * floor)))) { - trackId = floor; - } else { - trackId = ceil; - } - - return trackId + timeline.getVerticalScroll(); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/Timeline.java b/editor/src/main/java/oxy/bascenario/editor/timeline/Timeline.java deleted file mode 100644 index 00059cb5..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/Timeline.java +++ /dev/null @@ -1,338 +0,0 @@ -package oxy.bascenario.editor.timeline; - -import imgui.*; -import imgui.flag.ImGuiKey; -import lombok.Getter; -import lombok.Setter; -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.api.utils.math.Vec2; -import oxy.bascenario.editor.screen.BaseScenarioEditorScreen; -import oxy.bascenario.editor.utils.TrackParser; -import oxy.bascenario.utils.font.FontUtils; -import oxy.bascenario.utils.ImGuiUtils; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Deque; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedDeque; - -// Shit code but whatever. -public class Timeline { - @Getter - private final BaseScenarioEditorScreen screen; - - public static final long DEFAULT_MAX_TIME = 15000; // 15 seconds - - @Getter - private final List objects; - public void put(int track, long start, long duration, Object object, RenderLayer layer, boolean wait, Vec2 vec2) { - final ObjectOrEvent objectOrEvent = new ObjectOrEvent(this, track, start, duration, object, layer, wait, vec2); - this.objects.add(objectOrEvent); - this.objects.sort(Comparator.comparingInt(o -> o.track)); - this.objects.sort(Comparator.comparingLong(o -> o.start)); - - queueUndo(() -> this.objects.remove(objectOrEvent)); - this.setSelectedObject(objectOrEvent); - } - - // Undo and Redo - private final Deque undo = new ConcurrentLinkedDeque<>(); - public void queueUndo(Runnable run) { - this.queueUpdate = true; - undo.add(run); - if (undo.size() > 30) { - undo.poll(); - } - } - public boolean canUndo() { - return !undo.isEmpty(); - } - public void undo() { - if (undo.isEmpty()) { - return; - } - - undo.pollLast().run(); - this.queueUpdate = true; -// this.redo = - } -// private Runnable redo; - - @Getter @Setter - private ObjectOrEvent selectedObject; - - @Setter @Getter - private ObjectDragDrop draggingObject; - public boolean isDragging() { - return draggingObject != null; - } - public boolean isDragging(ObjectOrEvent object) { - return this.draggingObject != null && object == this.draggingObject.object; - } - - private boolean queueUpdate; - - public Timeline(BaseScenarioEditorScreen screen, Scenario.Builder scenario) { - this.screen = screen; - - if (scenario == null) { - this.objects = new ArrayList<>(); - } else { - this.objects = TrackParser.parse(this, scenario.build()); - } - } - - @Setter @Getter - private boolean playing, tickTime = true; - - @Getter - private float scroll = 0, scale = 1; - @Getter - private int verticalScroll = 0; - @Setter @Getter - private long timestamp; - private long since; - - public void render() { -// GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR)); - if (since == 0) { - since = System.currentTimeMillis(); - } - if (playing && tickTime) { - timestamp += System.currentTimeMillis() - since; - } - since = System.currentTimeMillis(); - - scroll = Math.max(0, scroll); - - ImGui.begin("Timeline"); - if (ImGui.getIO().getMouseDown(0) && ImGui.isWindowFocused() && draggingObject == null) { - onMouseDown(ImGui.getIO().getMousePos()); - } - if (playing || ImGui.getIO().getMouseDown(0) && ImGui.isWindowFocused()) { - if (timestamp != 0 && timestamp >= (scroll + 1) * DEFAULT_MAX_TIME * scale) { - long distance = (long) (timestamp - ((scroll + 1) * DEFAULT_MAX_TIME * scale)); - float ratio = (float) distance / DEFAULT_MAX_TIME; - scroll += ratio + 0.2f; - } else if (timestamp != 0 && timestamp <= DEFAULT_MAX_TIME * scroll * scale) { - long distance = (long) (DEFAULT_MAX_TIME * scroll * scale - timestamp); - float ratio = (float) distance / DEFAULT_MAX_TIME; - scroll -= ratio + 0.2f; - } - } - - - final ImVec2 size = ImGui.getWindowSize(), pos = ImGui.getWindowPos(); - final ImVec2 mouse = ImGui.getMousePos(); - if (mouse.x >= pos.x && mouse.x <= pos.x + size.x && mouse.y >= pos.y && mouse.y <= pos.y + size.y && ImGui.isWindowFocused()) { - final float scroll = ImGui.getIO().getMouseWheel(); - if (mouse.x < pos.x + size.x / 4) { - this.verticalScroll = Math.max(0, this.verticalScroll - (int)scroll); - } else if (ImGui.getIO().getKeyCtrl()) { - if (scroll > 0) { - this.scale += 0.05f * this.scale; - } else if (scroll < 0) { - this.scale = Math.max(0.001f, this.scale - 0.05f * this.scale); - } - } else { - this.scroll = Math.max(0, this.scroll + scroll); - } - } - - if (this.queueUpdate) { - this.objects.sort(Comparator.comparingInt(o -> o.track)); - this.objects.sort(Comparator.comparingLong(o -> o.start)); - this.screen.update(); - this.queueUpdate = false; - } - - ImGui.getWindowDrawList().addRectFilled(new ImVec2(pos.x, pos.y), new ImVec2(pos.x + (size.x / 4), pos.y + size.y), ImColor.rgb(25, 25, 25)); - drawElapsedTimeSegments(size.x / 4, pos, size); - drawTimelineSegments(size.x / 4, pos, size); - drawElapsedTime(size.x / 4, pos, size); - drawTimelineCursor(size.x / 4, pos, size); - - if (ImGui.isWindowFocused() && this.selectedObject != null && ImGui.isKeyPressed(ImGuiKey.Delete)) { - final ObjectOrEvent old = this.selectedObject; - queueUndo(() -> this.objects.add(old)); - this.objects.remove(this.selectedObject); - this.selectedObject = null; - } - - if (ImGui.isWindowFocused() && ImGui.isKeyPressed(ImGuiKey.Space)) { - this.screen.setPlaying(!this.isPlaying()); - } - - if (!this.isDragging()) { - ImGui.end(); - return; - } - - // Render dragging separately on top of everything... - if (this.draggingObject != null) { - this.draggingObject.object.renderer.render(); - } - - if (this.draggingObject == null || !this.draggingObject.isWaiting()) { - ImGui.end(); - return; - } else if (!this.draggingObject.loop) { - ImGui.end(); - this.draggingObject.loop = true; - return; - } - - if (!this.draggingObject.isRejected()) { - this.draggingObject.accept(); // Accept the result of dragging if not rejected... - this.draggingObject = null; - } else { - final float ratio = (this.draggingObject.object.renderer.x - pos.x - size.x / 4) / (size.x - size.x / 4); - long time = (long) (Timeline.DEFAULT_MAX_TIME * scale * scroll + ratio * Timeline.DEFAULT_MAX_TIME * scale); - - if (this.draggingObject.second || this.draggingObject.nearestTime - time > 350L) { - this.draggingObject = null; // Done! - } else { - this.draggingObject.reset(); - this.draggingObject.second = true; - } - } - ImGui.end(); - } - - private void drawTimelineSegments(float timelineManagerWidth, ImVec2 pos, ImVec2 size) { - final ImDrawList drawList = ImGui.getWindowDrawList(); - - float yScale = 80 * ImGui.getStyle().getFontScaleDpi(); - float y = pos.y + yScale; - -// GLFW.glfwSetCursor(windowHandle, GLFW.glfwCreateStandardCursor(GLFW.GLFW_VRESIZE_CURSOR)); - - float segmentY = 50 * ImGui.getStyle().getFontScaleDpi(); - for (int i = verticalScroll; i <= ((size.y - yScale) / segmentY) + verticalScroll; i++) { - drawList.addRectFilled(new ImVec2(pos.x, y), new ImVec2(pos.x + timelineManagerWidth, y + segmentY), ImColor.rgb(33, 33, 33)); - - ImGui.pushFont(FontUtils.IM_FONT_REGULAR, 35); - drawList.addText(new ImVec2(pos.x + 10, y), ImColor.rgb(255, 255, 255), "Track " + i); - ImGui.popFont(); - - drawList.addRect(new ImVec2(pos.x, y), new ImVec2(pos.x + size.x, y + 0.5f), ImColor.rgb(50, 50, 50)); - - y += segmentY; - } - - this.objects.forEach(object -> { - if (!this.isDragging(object)) { - object.renderer.render(); - } - }); - } - - private void drawElapsedTimeSegments(float timelineManagerWidth, ImVec2 pos, ImVec2 size) { - final ImDrawList drawList = ImGui.getWindowDrawList(); - - drawList.addRectFilled(new ImVec2(pos.x + timelineManagerWidth, pos.y), new ImVec2(pos.x + size.x, pos.y + size.y), ImColor.rgb(20, 19, 24)); - for (int i = 0; i <= 5; i++) { - ImGui.pushFont(FontUtils.IM_FONT_SEMI_BOLD, 20); - long time = (long) ((DEFAULT_MAX_TIME * scale * scroll) + (DEFAULT_MAX_TIME * scale * (i / 5f))); - float segmentX = timestampToPosition(time, timelineManagerWidth + pos.x, size.x - timelineManagerWidth); - - drawList.addRect(new ImVec2(segmentX, pos.y), new ImVec2(segmentX + 0.5f, pos.y + size.y), ImColor.rgb(50, 50, 50)); - drawList.addText(segmentX + 5, pos.y + 30 * ImGui.getStyle().getFontScaleDpi(), ImColor.rgb(255, 255, 255), format(time)); - - ImGui.popFont(); - } - } - - private void drawElapsedTime(float timelineManagerWidth, ImVec2 pos, ImVec2 size) { - final ImDrawList drawList = ImGui.getWindowDrawList(); - - float yScale = 80 * ImGui.getStyle().getFontScaleDpi(); - - drawList.addRectFilled(new ImVec2(pos.x, pos.y), new ImVec2(pos.x + timelineManagerWidth, - pos.y + yScale), ImColor.rgb(50, 50, 50)); - - drawList.addRect(new ImVec2(pos.x, pos.y), new ImVec2(pos.x + size.x, pos.y + yScale), ImColor.rgb(50, 50, 50)); - - ImGui.pushFont(FontUtils.IM_FONT_SEMI_BOLD, 30); - drawList.addText(pos.x + 20, pos.y + 21 * ImGui.getStyle().getFontScaleDpi(), ImColor.rgb(255, 255, 255), format(timestamp)); - ImGui.popFont(); - - long millis = timestamp % 1000; - long second = (timestamp / 1000) % 60; - long minute = (timestamp / (1000 * 60)) % 60; - long hour = (timestamp / (1000 * 60 * 60)) % 24; - ImGui.pushItemWidth(timelineManagerWidth - 15); - ImGui.setCursorPos(5, 55 * ImGui.getStyle().getFontScaleDpi()); - int[] time = new int[] {Math.toIntExact(hour), Math.toIntExact(minute), Math.toIntExact(second), Math.toIntExact(millis)}; - ImGuiUtils.inputInt4("", time); - ImGui.popItemWidth(); - - long lastTimestamp = timestamp; - timestamp = Math.min(time[0], 99) * (long)3.6e+6 + Math.min(69, time[1]) * 60000L + Math.min(59, time[2]) * 1000L + Math.min(999, time[3]); - if (timestamp != lastTimestamp) { - screen.update(); - } - } - - private void drawTimelineCursor(float timelineManagerWidth, ImVec2 pos, ImVec2 size) { - final ImDrawList drawList = ImGui.getWindowDrawList(); - - final float cursorX = timestampToPosition(timestamp, timelineManagerWidth + pos.x, size.x - timelineManagerWidth); - if (cursorX < timelineManagerWidth + pos.x) { - return; - } - - drawList.addRect(new ImVec2(cursorX, pos.y), new ImVec2(cursorX + ImGui.getStyle().getFontScaleDpi(), pos.y + size.y), ImColor.rgb(255, 255, 255)); - } - - private void onMouseDown(ImVec2 vec2) { - final ImVec2 size = ImGui.getWindowSize(), pos = ImGui.getWindowPos(); - - if (vec2.x < pos.x || vec2.x > pos.x + size.x || vec2.y < pos.y + 20 || vec2.y > pos.y + size.y) { - return; - } - - if (vec2.x > pos.x + size.x - 20) { - scroll += 0.01f * ((pos.x + size.x - vec2.x) / 20) * scale; - } - - if (vec2.x < pos.x + size.x / 4) { - if (vec2.y > pos.y + 80 * ImGui.getStyle().getFontScaleDpi()) { - final float distance = pos.x + size.x / 4 - vec2.x; - final float ratio = distance / (size.x - size.x / 4); - final long backtrackTime = (long) (ratio * (DEFAULT_MAX_TIME * scale * 0.1)); - timestamp = Math.max(0, timestamp - backtrackTime); - screen.update(); - this.selectedObject = null; - } - return; - } - - if (vec2.y > pos.y + 80 * ImGui.getStyle().getFontScaleDpi()) { - return; - } - - final float ratio = (vec2.x - pos.x - size.x / 4) / (size.x - size.x / 4); - long last = timestamp; - timestamp = (long) (DEFAULT_MAX_TIME * scale * scroll + ratio * DEFAULT_MAX_TIME * scale); - this.selectedObject = null; - if (last != timestamp) { - screen.update(); - } - } - - private float timestampToPosition(long timestamp, float offsetX, float size) { - return offsetX + ((timestamp - (Timeline.DEFAULT_MAX_TIME * scale * scroll)) / (Timeline.DEFAULT_MAX_TIME * scale)) * size; - } - - // Don't comment on this. - private static String format(long ms) { - long millis = ms % 1000; - long second = (ms / 1000) % 60; - long minute = (ms / (1000 * 60)) % 60; - long hour = (ms / (1000 * 60 * 60)) % 24; - return (hour < 10 ? "0" + hour : hour) + ":" + (minute < 10 ? "0" + minute : minute) + ":" + (second < 10 ? "0" + second : second) + ":" + (millis < 10 ? "0" + millis : millis); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/utils/AudioUtils.java b/editor/src/main/java/oxy/bascenario/editor/utils/AudioUtils.java deleted file mode 100644 index 988ca02a..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/utils/AudioUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package oxy.bascenario.editor.utils; - -import oxy.bascenario.Base; -import oxy.bascenario.api.managers.other.Asset; -import oxy.bascenario.api.utils.FileInfo; -import oxy.bascenario.managers.other.AudioAsset; - -public class AudioUtils { - public static long toDuration(String scenario, SoundAsElement sound) { - long duration = toDuration(scenario, sound.sound().file()); - duration -= (long) (sound.start() * 1000L); - return duration; - } - - public static long toDuration(String scenario, FileInfo info) { - try { - final Asset asset = Base.instance().assetsManager().assets(scenario, info); - if (asset.asset() instanceof AudioAsset audioAsset) { - return audioAsset.duration(); - } - } catch (Exception ignored) { - } - - return 0; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/utils/NameUtils.java b/editor/src/main/java/oxy/bascenario/editor/utils/NameUtils.java deleted file mode 100644 index 8a095be8..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/utils/NameUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -package oxy.bascenario.editor.utils; - -import oxy.bascenario.api.event.*; -import oxy.bascenario.api.event.animation.PlayAnimationEvent; -import oxy.bascenario.api.event.animation.SpriteAnimationEvent; -import oxy.bascenario.api.event.animation.StopAnimationEvent; -import oxy.bascenario.api.event.background.ClearBackgroundEvent; -import oxy.bascenario.api.event.background.SetBackgroundEvent; -import oxy.bascenario.api.event.color.ColorOverlayEvent; -import oxy.bascenario.api.event.color.SetColorEvent; -import oxy.bascenario.api.event.dialogue.*; -import oxy.bascenario.api.event.element.ClearLogEvent; -import oxy.bascenario.api.event.element.ElementEffectEvent; -import oxy.bascenario.api.event.element.focus.FocusElementEvent; -import oxy.bascenario.api.event.element.focus.UnfocusElementEvent; -import oxy.bascenario.api.event.element.values.PositionElementEvent; -import oxy.bascenario.api.event.element.values.RotateElementEvent; -import oxy.bascenario.api.event.log.AddLogEvent; -import oxy.bascenario.api.event.sound.SoundVolumeEvent; -import oxy.bascenario.api.render.elements.LocationInfo; -import oxy.bascenario.api.render.elements.image.AnimatedImage; -import oxy.bascenario.api.render.elements.text.AnimatedText; - -public class NameUtils { - public static String name(Object object) { - return switch (object) { - // Events part. - case PlayAnimationEvent ignored -> "Play Animation"; - case SpriteAnimationEvent ignored -> "Play Sprite Animation"; - case StopAnimationEvent ignored -> "Stop Animation"; - - case StartDialogueEvent ignored -> "Start Dialogue"; - case AddDialogueEvent ignored -> "Add Dialogue"; - case ShowOptionsEvent ignored -> "Show Options"; - - case CloseDialogueEvent ignored -> "Close Dialogue"; - case CloseOptionsEvent ignored -> "Close Options"; - - case PositionElementEvent event -> switch (event.type()) { - case POSITION -> "Move Object"; - case OFFSET -> "Offset Object"; - case SCALE -> "Scale Object"; - case PIVOT -> "Pivot Object"; - }; - case RotateElementEvent ignored -> "Rotate Object"; - - case ElementEffectEvent event -> switch (event.type()) { - case ADD -> "Apply Effect"; - case REMOVE -> "Remove Effect"; - }; - - case ColorOverlayEvent event -> { - if (event.id().isPresent()) { - yield "Color Overlay"; - } else { - yield "Screen Color"; - } - } - case SetColorEvent ignored -> "Set Color"; - - case SetBackgroundEvent ignored -> "Set Background"; - case ClearBackgroundEvent ignored -> "Clear Background"; - - case SoundVolumeEvent ignored -> "Sound Volume"; - case LockClickEvent ignored -> "Lock Click"; - - case LocationInfo ignored -> "Location Info"; - case AnimatedText ignored -> "Typing Text"; - - case SoundAsElement ignored -> "Sound"; - case AnimatedImage ignored -> "Gif"; - - case ShowButtonsEvent ignored -> "Show Buttons"; - case FocusElementEvent ignored -> "Focus Object"; - case UnfocusElementEvent ignored -> "Un-focus Object"; - - case SetWeatherEvent ignored -> "Set Weather"; - case PopupEvent ignored -> "Image Popup"; - case ScreenTransitionEvent ignored -> "Screen Transition"; - case ScreenEffectEvent ignored -> "Screen Effect"; - - case AddLogEvent ignored -> "Add Log"; - case ClearLogEvent ignored -> "Clear Log"; - - default -> object.getClass().getSimpleName(); - }; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/utils/SelectionUtils.java b/editor/src/main/java/oxy/bascenario/editor/utils/SelectionUtils.java deleted file mode 100644 index 74f5ab70..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/utils/SelectionUtils.java +++ /dev/null @@ -1,14 +0,0 @@ -package oxy.bascenario.editor.utils; - -import imgui.ImVec2; -import oxy.bascenario.api.render.elements.Sprite; -import oxy.bascenario.screens.renderer.element.SpriteRenderer; -import oxy.bascenario.screens.renderer.element.base.ElementRenderer; - -public class SelectionUtils { - public static ImVec2 selectionBox(ElementRenderer base) { - return switch (base) { - default -> null; - }; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/utils/SoundAsElement.java b/editor/src/main/java/oxy/bascenario/editor/utils/SoundAsElement.java deleted file mode 100644 index 26b8eb82..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/utils/SoundAsElement.java +++ /dev/null @@ -1,9 +0,0 @@ -package oxy.bascenario.editor.utils; - -import lombok.Builder; -import oxy.bascenario.api.effects.Sound; - -// Just a duplicate of PlaySoundEvent (kinda) to be less confusing. -@Builder(toBuilder = true, builderClassName = "Builder") -public record SoundAsElement(Sound sound, int in, int out, float start, long max) { -} diff --git a/editor/src/main/java/oxy/bascenario/editor/utils/TimeCompiler.java b/editor/src/main/java/oxy/bascenario/editor/utils/TimeCompiler.java deleted file mode 100644 index 9be2bc1b..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/utils/TimeCompiler.java +++ /dev/null @@ -1,158 +0,0 @@ -package oxy.bascenario.editor.utils; - -import oxy.bascenario.api.event.ScreenTransitionEvent; -import oxy.bascenario.api.event.background.ClearBackgroundEvent; -import oxy.bascenario.api.event.background.SetBackgroundEvent; -import oxy.bascenario.api.event.color.ColorOverlayEvent; -import oxy.bascenario.api.event.api.Event; -import oxy.bascenario.api.event.color.SetColorEvent; -import oxy.bascenario.api.event.dialogue.AddDialogueEvent; -import oxy.bascenario.api.event.dialogue.ShowOptionsEvent; -import oxy.bascenario.api.event.dialogue.StartDialogueEvent; -import oxy.bascenario.api.event.element.values.PositionElementEvent; -import oxy.bascenario.api.event.element.values.RotateElementEvent; -import oxy.bascenario.api.event.log.AddLogEvent; -import oxy.bascenario.api.event.sound.PlaySoundEvent; -import oxy.bascenario.api.event.sound.SoundVolumeEvent; -import oxy.bascenario.api.event.sound.StopSoundEvent; -import oxy.bascenario.api.render.elements.*; -import oxy.bascenario.api.render.elements.emoticon.Emoticon; -import oxy.bascenario.api.render.elements.text.TextSegment; - -public class TimeCompiler { - public static Object addTime(final Object object, int duration) { - if (compileTime(object) == Long.MAX_VALUE) { - return object; - } - - return switch (object) { - case LocationInfo info -> { - LocationInfo.Builder builder = info.toBuilder(); - builder.duration(info.duration() + duration); - yield builder.build(); - } - case Emoticon emoticon -> { - Emoticon.Builder builder = emoticon.toBuilder(); - builder.duration(emoticon.duration() + duration); - yield builder.build(); - } - case PositionElementEvent event -> { - PositionElementEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case RotateElementEvent event -> { - RotateElementEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case PlaySoundEvent event -> { - PlaySoundEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case SoundVolumeEvent event -> { - SoundVolumeEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case StopSoundEvent event -> { - StopSoundEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case ColorOverlayEvent event -> { - ColorOverlayEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case SetColorEvent event -> { - SetColorEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case SetBackgroundEvent event -> { - SetBackgroundEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case ClearBackgroundEvent event -> { - ClearBackgroundEvent.Builder builder = event.toBuilder(); - builder.duration(event.duration() + duration); - yield builder.build(); - } - case ScreenTransitionEvent event -> { - ScreenTransitionEvent.Builder builder = event.toBuilder(); - builder.waitDuration(event.waitDuration() + duration); - yield builder.build(); - } - default -> object; - }; - } - - public static boolean canResize(final Object object) { - return compileTime(object) == Long.MAX_VALUE || object instanceof LocationInfo || object instanceof Emoticon || - object instanceof PositionElementEvent || object instanceof RotateElementEvent || object instanceof ScreenTransitionEvent || - object instanceof PlaySoundEvent || object instanceof SoundVolumeEvent || object instanceof StopSoundEvent || - object instanceof ColorOverlayEvent || object instanceof SetColorEvent || object instanceof ClearBackgroundEvent || object instanceof SetBackgroundEvent; - } - - public static long compileTime(final Object object) { - return switch (object) { - case Preview ignored -> 3900; - case LocationInfo info -> info.duration() + info.fade(); - case Emoticon emoticon -> switch (emoticon.type()) { - case NOTE -> 2600 + emoticon.duration(); - case RESPOND, ANGRY, SWEAT, CHAT, EXCLAMATION_MARK, HEART, SHY, SURPRISED, QUESTION_MARK, ANXIETY, IDEA -> 800 + emoticon.duration(); - case TWINKLE -> emoticon.duration(); - case THINKING, HESITATED -> 600L * 3 + (600L / 3); - case SIGH -> 350 + 500L + emoticon.duration(); - }; - - case StartDialogueEvent event -> compileTime(event.dialogues()); - case AddDialogueEvent event -> compileTime(event.dialogues()); - case AddLogEvent event -> compileTime(event.dialogues()); - - case Dialogue[] dialogues -> compileTime(dialogues); - case Dialogue dialogue -> compileTime(dialogue); - case Event event -> compileTime(event); - default -> Long.MAX_VALUE; - }; - } - - private static long compileTime(final Event object) { - return switch (object) { - case PositionElementEvent event -> Math.max(100, event.duration()); - case RotateElementEvent event -> Math.max(100, event.duration()); - case ShowOptionsEvent ignored -> 250L; // eh. - case PlaySoundEvent event -> Math.max(100, event.duration()); - case SoundVolumeEvent event -> Math.max(100, event.duration()); - case StopSoundEvent event -> Math.max(100, event.duration()); - case ColorOverlayEvent event -> Math.max(100, event.duration()); - case SetColorEvent event -> Math.max(100, event.duration()); - case ClearBackgroundEvent event -> Math.max(100, event.duration()); - case SetBackgroundEvent event -> Math.max(100, event.duration()); - case ScreenTransitionEvent event -> Math.max(100, event.inDuration() + event.outDuration() + event.waitDuration()); - - default -> 100L; // has to be something for it to show up in the editor. - }; - } - - private static long compileTime(Dialogue[] dialogues) { - long duration = 0; - for (Dialogue dialogue : dialogues) { - duration += compileTime(dialogue); - } - return duration; - } - - private static long compileTime(Dialogue dialogue) { - final long msPerWord = (long) (Dialogue.MS_PER_WORD * (1 / dialogue.getPlaySpeed()) * 1); - - long duration = 0; - for (TextSegment segment : dialogue.getDialogue().segments()) { - duration += msPerWord * segment.text().replace("\n", "").length(); - } - return duration; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/utils/TrackParser.java b/editor/src/main/java/oxy/bascenario/editor/utils/TrackParser.java deleted file mode 100644 index 8564bc07..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/utils/TrackParser.java +++ /dev/null @@ -1,174 +0,0 @@ -package oxy.bascenario.editor.utils; - -import oxy.bascenario.api.Scenario; -import oxy.bascenario.api.Timestamp; -import oxy.bascenario.api.effects.Sound; -import oxy.bascenario.api.event.api.Event; -import oxy.bascenario.api.event.dialogue.AddDialogueEvent; -import oxy.bascenario.api.event.dialogue.ShowOptionsEvent; -import oxy.bascenario.api.event.dialogue.StartDialogueEvent; -import oxy.bascenario.api.event.element.AddElementEvent; -import oxy.bascenario.api.event.element.RemoveElementEvent; -import oxy.bascenario.api.event.sound.PlaySoundEvent; -import oxy.bascenario.api.event.sound.StopSoundEvent; -import oxy.bascenario.editor.timeline.ObjectOrEvent; -import oxy.bascenario.editor.timeline.Timeline; -import oxy.bascenario.utils.math.Pair; - -import java.util.*; - -public class TrackParser { - // TODO: Sub elements.... - public static List parse(List objects) { - final List timestamps = new ArrayList<>(); - - final Map>> events = new TreeMap<>(); - - objects.forEach((object) -> { - final Pair> list = events.computeIfAbsent(object.start, n -> new Pair<>(object.requireWait, new ArrayList<>())); - if (object.object instanceof Event event) { - list.right().add(event); - } else if (object.object instanceof SoundAsElement(Sound sound, int in, int out, float start, long max)) { - list.right().add(new PlaySoundEvent(sound, in, start)); - if (out != Integer.MIN_VALUE && sound != null) { - events.computeIfAbsent(object.start + object.duration, n -> new Pair<>(object.requireWait, new ArrayList<>())).right().add(new StopSoundEvent(sound.id(), out)); - } - } else { - list.right().add(new AddElementEvent(object.track, object.vec2, object.object, object.layer)); - events.computeIfAbsent(object.start + object.duration, n -> new Pair<>(object.requireWait, new ArrayList<>())).right().add(new RemoveElementEvent(object.track)); - } - }); - - long last = 0; - for (Map.Entry>> entry : events.entrySet()) { - final Long time = entry.getKey(); - long delay = time - last; - last = time; - - long max = 0; - for (Event event : entry.getValue().right()) { - if (event instanceof StartDialogueEvent || event instanceof AddDialogueEvent || event instanceof ShowOptionsEvent) { - max = Math.max(max, TimeCompiler.compileTime(event)); - } - } - last += max; - - timestamps.add(new Timestamp(entry.getValue().left(), (int) delay, entry.getValue().right())); - } - -// System.out.println(Arrays.toString(timestamps.toArray())); - - return timestamps; - } - - public static List parse(Timeline timeline, Scenario scenario) { - final List result = new ArrayList<>(); - final Map>> occupies = new HashMap<>(); - - final Map previous = new HashMap<>(); - - long time = 0; - for (Timestamp timestamp : scenario.getTimestamps()) { - time += timestamp.time(); - - for (Event other : timestamp.events()) { switch (other) { - case AddElementEvent event -> { - long duration = TimeCompiler.compileTime(event.element()); - - final ObjectOrEvent objectOrEvent = new ObjectOrEvent(timeline, event.id(), time, duration, event.element(), event.layer(), timestamp.waitForDialogue(), event.position()); - previous.put(event.id(), objectOrEvent); - result.add(objectOrEvent); - - occupies.computeIfAbsent(event.id(), i -> new HashMap<>()).put(time, new Pair<>(time, time + duration)); - } - case RemoveElementEvent event -> { - Object object = previous.get(event.id()); - if (!(object instanceof ObjectOrEvent oOE)) { - continue; - } - - oOE.duration = Math.min(time - oOE.start, oOE.duration); - - Map> track = occupies.get(oOE.track); - if (track != null) { - final Pair pair = track.get(oOE.start); - if (pair != null) { - pair.right(pair.left() + oOE.duration); - - } - } - } - case PlaySoundEvent event -> { - long duration = AudioUtils.toDuration(scenario.getName(), event.sound().file()); - int track = findNonOccupiedSlot(time, duration, occupies); - - final SoundAsElement element = new SoundAsElement(event.sound(), (int) event.duration(), 0, event.start(), duration); - final ObjectOrEvent objectOrEvent = new ObjectOrEvent(timeline, track, time, duration, element, null, timestamp.waitForDialogue(), null); - previous.put(track, objectOrEvent); - result.add(objectOrEvent); - - occupies.computeIfAbsent(track, i -> new HashMap<>()).put(time, new Pair<>(time, time + duration)); - } - case StopSoundEvent event -> { - Object object = previous.get(event.id()); - if (!(object instanceof ObjectOrEvent oOE) || !(oOE.object instanceof SoundAsElement sound)) { - continue; - } - - final SoundAsElement.Builder builder = sound.toBuilder(); - builder.out(event.duration()); - - oOE.object = builder.build(); - oOE.duration = Math.min(time - oOE.start, oOE.duration); - - Map> track = occupies.get(oOE.track); - if (track != null) { - final Pair pair = track.get(oOE.start); - if (pair != null) { - pair.right(pair.left() + oOE.duration); - } - } - } - default -> { - long duration = TimeCompiler.compileTime(other); - int track = findNonOccupiedSlot(time, duration, occupies); - - final ObjectOrEvent objectOrEvent = new ObjectOrEvent(timeline, track, time, duration, other, null, timestamp.waitForDialogue(), null); - previous.put(track, objectOrEvent); - result.add(objectOrEvent); - - occupies.computeIfAbsent(track, i -> new HashMap<>()).put(time, new Pair<>(time, time + duration)); - - if (other instanceof StartDialogueEvent || other instanceof AddDialogueEvent || other instanceof ShowOptionsEvent) { - time += TimeCompiler.compileTime(other); - } - } - }} - } - - return result; - } - - private static int findNonOccupiedSlot(long time, long duration, Map>> occupies) { - int i = 0; - - Map> map; - while ((map = occupies.get(i)) != null) { - boolean occupied = false; - for (Pair pair : map.values()) { - final long maxTime = Math.abs(pair.right()), minTime = pair.left(); - if (maxTime >= time && minTime <= time + duration) { - occupied = true; - break; - } - } - - if (!occupied) { - return i; - } - i++; - } - - return i; - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bcacc98c..4f974013 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,6 @@ rivet = "0.0.1-SNAPSHOT" commons = "1.9.2" mocha = "3.0.1" configurate = "4.2.0" -imgui = "1.92.0" audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" @@ -23,12 +22,6 @@ projectlombok-lombok = { module = "org.projectlombok:lombok", version.ref = "lom google-gson = { module = "com.google.code.gson:gson", version.ref = "gson" } configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } -imgui-binding = { module = "io.github.spair:imgui-java-binding", version.ref = "imgui" } -imgui-lwjgl3 = { module = "io.github.spair:imgui-java-lwjgl3", version.ref = "imgui" } -imgui-natives-windows = { module = "io.github.spair:imgui-java-natives-windows", version.ref = "imgui" } -imgui-natives-linux = { module = "io.github.spair:imgui-java-natives-linux", version.ref = "imgui" } -imgui-natives-macos = { module = "io.github.spair:imgui-java-natives-macos", version.ref = "imgui" } - lwjgl-base = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" } lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" } lwjgl-stb = { module = "org.lwjgl:lwjgl-stb", version.ref = "lwjgl" } diff --git a/launcher/build.gradle.kts b/launcher/build.gradle.kts index b6f16312..3d902189 100644 --- a/launcher/build.gradle.kts +++ b/launcher/build.gradle.kts @@ -12,9 +12,6 @@ dependencies { implementation(libs.gdx.base) implementation(libs.gdx.spine) - implementation(libs.imgui.binding) - implementation(libs.imgui.lwjgl3) - implementation(libs.lwjgl.glfw) implementation(libs.lwjgl.nfd) diff --git a/launcher/src/main/java/oxy/bascenario/screen/ScenarioListScreen.java b/launcher/src/main/java/oxy/bascenario/screen/ScenarioListScreen.java index 63f157f7..d54c4555 100644 --- a/launcher/src/main/java/oxy/bascenario/screen/ScenarioListScreen.java +++ b/launcher/src/main/java/oxy/bascenario/screen/ScenarioListScreen.java @@ -2,29 +2,22 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import imgui.ImColor; -import imgui.ImGui; -import imgui.flag.*; import org.zeroturnaround.zip.ZipUtil; import org.zeroturnaround.zip.commons.FileUtils; import oxy.bascenario.Base; import oxy.bascenario.api.Scenario; -import oxy.bascenario.editor.screen.ScenarioEditorScreen; import oxy.bascenario.managers.ScenarioManager; import oxy.bascenario.screen.title.TitleScreen; import oxy.bascenario.screens.ScenarioScreen; import oxy.bascenario.serializers.Types; import oxy.bascenario.utils.ExtendableScreen; -import oxy.bascenario.utils.ImGuiUtils; import oxy.bascenario.utils.Launcher; import oxy.bascenario.utils.files.NFDUtils; import java.awt.*; import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.util.Iterator; -import java.util.UUID; public class ScenarioListScreen extends ExtendableScreen { private boolean popupNewScenario, popupImportingFailed; @@ -33,160 +26,160 @@ public class ScenarioListScreen extends ExtendableScreen { @Override public void render(float delta) { - ImGui.getStyle().setColor(ImGuiCol.WindowBg, 0.098f, 0.098f, 0.098f, 1f); - - ImGui.beginMainMenuBar(); - if (ImGui.menuItem("Exit")) { - Launcher.WINDOW.setScreen(TitleScreen.INSTANCE); - } - if (ImGui.menuItem("New Scenario")) { - popupNewScenario = true; - } - if (ImGui.menuItem("Import Scenario")) { - String fileS = NFDUtils.pickFile("zip,rar"); - if (!fileS.isEmpty()) { - final File file = new File(fileS); - if (!ZipUtil.containsEntry(file, "scenario.base")) { - importingFailed = "Import scenario failed, is this the correct format?"; - popupImportingFailed = true; - } else { - Scenario scenario = null; - final byte[] bytes = ZipUtil.unpackEntry(file, "scenario.base"); - try { - JsonObject object = JsonParser.parseString(new String(bytes)).getAsJsonObject(); - scenario = Types.SCENARIO_TYPE.read(object); - } catch (Exception ignored) { - } - if (scenario == null) { - importingFailed = "Import scenario failed, failed to read scenario!"; - popupImportingFailed = true; - } else { - File path = new File(ScenarioManager.SAVE_DIR, scenario.getName()); - if (path.exists()) { - importingFailed = "File/Scenario Folder with this name already exist, please delete/rename it first"; - popupImportingFailed = true; - } else { - path.mkdirs(); - - ZipUtil.unpack(file, path); - Base.instance().scenarioManager().put(scenario.getName(), scenario); - } - } - } - } - } - ImGui.endMainMenuBar(); - - ImGui.setNextWindowPos(0, 23 * ImGui.getStyle().getFontScaleDpi()); - ImGui.setNextWindowSize(ImGui.getIO().getDisplaySize()); - ImGui.begin("Main", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoResize); - - if (ImGui.button("Open Saved Location##" + ImGuiUtils.COUNTER++)) { - try { - Desktop.getDesktop().open(ScenarioManager.SAVE_DIR); - } catch (IOException ignored) { - } - } - - if (ImGui.beginTable("1ways", 1, ImGuiTableFlags.BordersV | ImGuiTableFlags.BordersOuterH |ImGuiTableFlags.RowBg | ImGuiTableFlags.NoBordersInBody)) { - ImGui.tableSetupColumn("Name", ImGuiTableColumnFlags.NoHide); - ImGui.tableHeadersRow(); - - final Iterator iterator = Base.instance().scenarioManager().getScenarios().values().iterator(); - while (iterator.hasNext()) { - Scenario scenario = iterator.next(); - - ImGui.tableNextRow(); - ImGui.tableNextColumn(); - ImGui.text(scenario.getName()); - ImGui.sameLine(); - if (ImGui.smallButton("Edit##" + ImGuiUtils.COUNTER++)) { - Launcher.WINDOW.setScreen(new ScenarioEditorScreen(this, scenario, scenario.toBuilder())); - } - ImGui.sameLine(); - if (ImGui.smallButton("Play##" + ImGuiUtils.COUNTER++)) { - Launcher.WINDOW.setScreen(new ScenarioScreen(scenario) { - @Override - public void render(float delta) { - if (ImGui.isKeyReleased(ImGuiKey.Escape)) { - Launcher.WINDOW.setScreen(TitleScreen.INSTANCE); - } - super.render(delta); - } - }); - } - ImGui.sameLine(); - - // TODO: do me! -// if (ImGui.smallButton("Rename##" + ImGuiUtils.COUNTER++)) { +// ImGui.getStyle().setColor(ImGuiCol.WindowBg, 0.098f, 0.098f, 0.098f, 1f); +// +// ImGui.beginMainMenuBar(); +// if (ImGui.menuItem("Exit")) { +// Launcher.WINDOW.setScreen(TitleScreen.INSTANCE); +// } +// if (ImGui.menuItem("New Scenario")) { +// popupNewScenario = true; +// } +// if (ImGui.menuItem("Import Scenario")) { +// String fileS = NFDUtils.pickFile("zip,rar"); +// if (!fileS.isEmpty()) { +// final File file = new File(fileS); +// if (!ZipUtil.containsEntry(file, "scenario.base")) { +// importingFailed = "Import scenario failed, is this the correct format?"; +// popupImportingFailed = true; +// } else { +// Scenario scenario = null; +// final byte[] bytes = ZipUtil.unpackEntry(file, "scenario.base"); +// try { +// JsonObject object = JsonParser.parseString(new String(bytes)).getAsJsonObject(); +// scenario = Types.SCENARIO_TYPE.read(object); +// } catch (Exception ignored) { +// } +// if (scenario == null) { +// importingFailed = "Import scenario failed, failed to read scenario!"; +// popupImportingFailed = true; +// } else { +// File path = new File(ScenarioManager.SAVE_DIR, scenario.getName()); +// if (path.exists()) { +// importingFailed = "File/Scenario Folder with this name already exist, please delete/rename it first"; +// popupImportingFailed = true; +// } else { +// path.mkdirs(); +// +// ZipUtil.unpack(file, path); +// Base.instance().scenarioManager().put(scenario.getName(), scenario); +// } +// } // } - - if (ImGui.smallButton("Export##" + ImGuiUtils.COUNTER++)) { - String folderS = NFDUtils.pickFolder(); - if (!folderS.isEmpty()) { - final File folder = new File(folderS); - final File path = new File(folder, scenario.getName() + ".zip"); - ZipUtil.pack(new File(ScenarioManager.SAVE_DIR, scenario.getName()), path); - try { - Desktop.getDesktop().open(folder); - } catch (IOException ignored) { - } - } - } - - ImGui.sameLine(); - if (ImGui.smallButton("Delete##" + ImGuiUtils.COUNTER++)) { - final File path = new File(ScenarioManager.SAVE_DIR, scenario.getName()); - try { - FileUtils.deleteDirectory(path); - iterator.remove(); - } catch (IOException ignored) { - } - } - } - - ImGui.endTable(); - } - - ImGui.end(); - - if (popupNewScenario) { - popupNewScenario = false; - cacheName = ""; - ImGui.openPopup("New Scenario"); - } - - if (popupImportingFailed) { - popupImportingFailed = false; - ImGui.openPopup("Import Failed"); - } - - if (ImGui.beginPopupModal("Import Failed", ImGuiWindowFlags.AlwaysAutoResize)) { - ImGui.text(importingFailed); - if (ImGui.button("Ok")) { - ImGui.closeCurrentPopup(); - importingFailed = ""; - } - ImGui.endPopup(); - } - - if (ImGui.beginPopupModal("New Scenario")) { - cacheName = ImGuiUtils.inputText("Name", cacheName); - final File path = new File(ScenarioManager.SAVE_DIR, cacheName); - - if (path.isDirectory() || cacheName.isEmpty()) { - ImGui.text(cacheName.isEmpty() ? "Name can't be empty!" : "A directory with this name already exist!"); - } - if (ImGui.button("Create##" + ImGuiUtils.COUNTER++) && !path.isDirectory() && !cacheName.isEmpty()) { - Base.instance().scenarioManager().put(cacheName, Scenario.builder().name(cacheName).build()); - ImGui.closeCurrentPopup(); - } - ImGui.sameLine(); - if (ImGui.button("Ok nevermind..")) { - ImGui.closeCurrentPopup(); - } - - ImGui.endPopup(); - } +// } +// } +// ImGui.endMainMenuBar(); +// +// ImGui.setNextWindowPos(0, 23 * ImGui.getStyle().getFontScaleDpi()); +// ImGui.setNextWindowSize(ImGui.getIO().getDisplaySize()); +// ImGui.begin("Main", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoResize); +// +// if (ImGui.button("Open Saved Location##" + ImGuiUtils.COUNTER++)) { +// try { +// Desktop.getDesktop().open(ScenarioManager.SAVE_DIR); +// } catch (IOException ignored) { +// } +// } +// +// if (ImGui.beginTable("1ways", 1, ImGuiTableFlags.BordersV | ImGuiTableFlags.BordersOuterH |ImGuiTableFlags.RowBg | ImGuiTableFlags.NoBordersInBody)) { +// ImGui.tableSetupColumn("Name", ImGuiTableColumnFlags.NoHide); +// ImGui.tableHeadersRow(); +// +// final Iterator iterator = Base.instance().scenarioManager().getScenarios().values().iterator(); +// while (iterator.hasNext()) { +// Scenario scenario = iterator.next(); +// +// ImGui.tableNextRow(); +// ImGui.tableNextColumn(); +// ImGui.text(scenario.getName()); +// ImGui.sameLine(); +// if (ImGui.smallButton("Edit##" + ImGuiUtils.COUNTER++)) { +// Launcher.WINDOW.setScreen(new ScenarioEditorScreen(this, scenario, scenario.toBuilder())); +// } +// ImGui.sameLine(); +// if (ImGui.smallButton("Play##" + ImGuiUtils.COUNTER++)) { +// Launcher.WINDOW.setScreen(new ScenarioScreen(scenario) { +// @Override +// public void render(float delta) { +// if (ImGui.isKeyReleased(ImGuiKey.Escape)) { +// Launcher.WINDOW.setScreen(TitleScreen.INSTANCE); +// } +// super.render(delta); +// } +// }); +// } +// ImGui.sameLine(); +// +// // TODO: do me! +//// if (ImGui.smallButton("Rename##" + ImGuiUtils.COUNTER++)) { +//// } +// +// if (ImGui.smallButton("Export##" + ImGuiUtils.COUNTER++)) { +// String folderS = NFDUtils.pickFolder(); +// if (!folderS.isEmpty()) { +// final File folder = new File(folderS); +// final File path = new File(folder, scenario.getName() + ".zip"); +// ZipUtil.pack(new File(ScenarioManager.SAVE_DIR, scenario.getName()), path); +// try { +// Desktop.getDesktop().open(folder); +// } catch (IOException ignored) { +// } +// } +// } +// +// ImGui.sameLine(); +// if (ImGui.smallButton("Delete##" + ImGuiUtils.COUNTER++)) { +// final File path = new File(ScenarioManager.SAVE_DIR, scenario.getName()); +// try { +// FileUtils.deleteDirectory(path); +// iterator.remove(); +// } catch (IOException ignored) { +// } +// } +// } +// +// ImGui.endTable(); +// } +// +// ImGui.end(); +// +// if (popupNewScenario) { +// popupNewScenario = false; +// cacheName = ""; +// ImGui.openPopup("New Scenario"); +// } +// +// if (popupImportingFailed) { +// popupImportingFailed = false; +// ImGui.openPopup("Import Failed"); +// } +// +// if (ImGui.beginPopupModal("Import Failed", ImGuiWindowFlags.AlwaysAutoResize)) { +// ImGui.text(importingFailed); +// if (ImGui.button("Ok")) { +// ImGui.closeCurrentPopup(); +// importingFailed = ""; +// } +// ImGui.endPopup(); +// } +// +// if (ImGui.beginPopupModal("New Scenario")) { +// cacheName = ImGuiUtils.inputText("Name", cacheName); +// final File path = new File(ScenarioManager.SAVE_DIR, cacheName); +// +// if (path.isDirectory() || cacheName.isEmpty()) { +// ImGui.text(cacheName.isEmpty() ? "Name can't be empty!" : "A directory with this name already exist!"); +// } +// if (ImGui.button("Create##" + ImGuiUtils.COUNTER++) && !path.isDirectory() && !cacheName.isEmpty()) { +// Base.instance().scenarioManager().put(cacheName, Scenario.builder().name(cacheName).build()); +// ImGui.closeCurrentPopup(); +// } +// ImGui.sameLine(); +// if (ImGui.button("Ok nevermind..")) { +// ImGui.closeCurrentPopup(); +// } +// +// ImGui.endPopup(); +// } } } From 2e3d85eb8b599250e1fea730cdc99abc5393a3b9 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Thu, 28 May 2026 02:20:13 +0700 Subject: [PATCH 04/71] stuff. --- .../java/oxy/bascenario/EngineRenderer.java | 17 ++- .../bascenario/utils/ExtendableScreen.java | 3 + .../oxy/bascenario/utils/font/FontUtils.java | 3 + core/src/test/java/RivetTest.java | 10 +- .../editor/ScenarioEditorScreen.java | 138 ++++++++++++++++++ .../java/oxy/bascenario/util/RivetUtil.java | 43 ++++++ editor/src/test/java/ScenarioEditorTest.java | 4 +- 7 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java create mode 100644 editor/src/main/java/oxy/bascenario/util/RivetUtil.java diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index c0720dc7..ac5f3a1e 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -18,6 +18,7 @@ import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.input.mouse.MouseScrollEvent; import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.layout.fullsize.FullSizeLayout; import net.lenni0451.rivet.math.Size; import net.raphimc.thingl.ThinGL; import net.raphimc.thingl.resource.font.impl.FreeTypeFont; @@ -26,7 +27,6 @@ import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWFramebufferSizeCallback; import oxy.bascenario.managers.AudioManager; -import oxy.bascenario.screens.ScenarioScreen; import oxy.bascenario.utils.*; import oxy.bascenario.utils.font.FontUtils; import oxy.bascenario.utils.thingl.ThinGLExtended; @@ -80,7 +80,7 @@ public void create() { setupRivetCallbacks(); this.backend = new ThinGLBackend(windowHandle, new FontSet(new FreeTypeFont(EngineRenderer.class.getResourceAsStream("/assets/base/fonts/rivet/SFUIText-Regular.ttf").readAllBytes(), 40))); - this.rivet = new Rivet(this.backend, AnchorLayout.INSTANCE, new Size(ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight())); + this.rivet = new Rivet(this.backend, FullSizeLayout.INSTANCE, new Size(ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight())); FontUtils.loadFonts(); @@ -112,17 +112,22 @@ public void render() { ThinGLUtils.GLOBAL_RENDER_STACK = new Matrix4fStack(8); - float x = ThinGL.windowInterface().getFramebufferWidth() / 1920F; - ThinGLUtils.GLOBAL_RENDER_STACK.scale(x, ThinGL.windowInterface().getFramebufferHeight() / 1080F, x); - ThinGLUtils.start(); + if (screen != null && screen instanceof ExtendableScreen extendableScreen) { + extendableScreen.renderBehindRivet(); + } + ThinGL.programs().getMsaa().bindInput(); - ThinGLRenderer.renderList(new Matrix4fStack(8), this.rivet.render()); + ThinGLRenderer.renderList(ThinGLUtils.GLOBAL_RENDER_STACK, this.rivet.render()); ThinGL.programs().getMsaa().unbindInput(); ThinGL.programs().getMsaa().renderFullscreen(); ThinGL.programs().getMsaa().clearInput(); ThinGLUtils.end(); + ThinGLUtils.GLOBAL_RENDER_STACK = new Matrix4fStack(8); + float x = ThinGL.windowInterface().getFramebufferWidth() / 1920F; + ThinGLUtils.GLOBAL_RENDER_STACK.scale(x, ThinGL.windowInterface().getFramebufferHeight() / 1080F, x); + super.render(); if (/*ImGui.isKeyReleased(ImGuiKey.F11) || */ someTempHackyBool) { diff --git a/core/src/main/java/oxy/bascenario/utils/ExtendableScreen.java b/core/src/main/java/oxy/bascenario/utils/ExtendableScreen.java index 074782c4..60f9a0fa 100644 --- a/core/src/main/java/oxy/bascenario/utils/ExtendableScreen.java +++ b/core/src/main/java/oxy/bascenario/utils/ExtendableScreen.java @@ -11,6 +11,9 @@ public void show() { public void init(Rivet rivet) { } + public void renderBehindRivet() { + } + @Override public void render(float delta) { } diff --git a/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java b/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java index 75b4a181..e7d83bca 100644 --- a/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java +++ b/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java @@ -38,6 +38,9 @@ public static Font toFont(Scenario scenario, TextSegment segment) { } public static void loadFonts() { + // Editor UI + loadFont("SFUIRegular", "/assets/base/fonts/rivet/SFUIText-Regular.ttf"); + // Global loadFont("NotoSansRegular", "/assets/base/fonts/global/NotoSans-Regular.ttf"); loadFont("NotoSansSemiBold", "/assets/base/fonts/global/NotoSans-SemiBold.ttf"); diff --git a/core/src/test/java/RivetTest.java b/core/src/test/java/RivetTest.java index 8404ad8d..8e1d6344 100644 --- a/core/src/test/java/RivetTest.java +++ b/core/src/test/java/RivetTest.java @@ -73,13 +73,15 @@ protected void addValues(final Rivet rivet, final Values values) { container.addChild(new Button(rivet, new Label(rivet, "Quit Game"), _ -> {}), button -> { button.layoutOptions(new GridLayoutOptions(1, 4).withAnchor(GridAnchor.EAST).withWeightX(1).withFill(GridFill.HORIZONTAL).withPadding(Padding.EMPTY.withTop(20))); }); - container.addChild(new ComboBox(rivet, "Testing since 2k26", new DecoratedContainer(rivet, new SolidColor(rivet, solidColor -> { - solidColor.color(Color.GREEN); - }), new ScrollContainer(rivet, new Container(rivet, new VerticalListLayout(5, true)), comboBoxContainer -> { + container.addChild(new ComboBox(rivet, "Testing since 2k26", + new DecoratedContainer(rivet, new SolidColor(rivet, solidColor -> {solidColor.color(Color.GREEN); + }), new ScrollContainer(rivet, new Container(rivet, new VerticalListLayout(5, true)), + comboBoxContainer -> { for (int i = 0; i < 10; i++) { comboBoxContainer.addChild(new Button(rivet, new Label(rivet, "Test " + i), _ -> {})); } - }))), comboBox -> comboBox.layoutOptions(new GridLayoutOptions(0, 5).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2))); + }))), comboBox -> comboBox.layoutOptions(new GridLayoutOptions(0, 5). + withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2))); container.addChild(new TextField(rivet), textField -> { textField.layoutOptions(new GridLayoutOptions(0, 6).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); textField.keyDownListener().add(event -> { diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java new file mode 100644 index 00000000..f12a0617 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -0,0 +1,138 @@ +package oxy.bascenario.editor; + +import lombok.RequiredArgsConstructor; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.Rivet; +import net.lenni0451.rivet.backend.Renderer; +import net.lenni0451.rivet.component.Container; +import net.lenni0451.rivet.component.base.Button; +import net.lenni0451.rivet.component.base.ComboBox; +import net.lenni0451.rivet.component.base.ScrollContainer; +import net.lenni0451.rivet.component.impl.FormattedLabel; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.component.impl.graphics.SolidColor; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Rectangle; +import net.raphimc.thingl.ThinGL; +import net.raphimc.thingl.text.TextRun; +import oxy.bascenario.api.Scenario; +import oxy.bascenario.util.RivetUtil; +import oxy.bascenario.utils.ExtendableScreen; +import oxy.bascenario.utils.font.TextUtils; +import oxy.bascenario.utils.math.Pair; +import oxy.bascenario.utils.thingl.ThinGLUtils; + +import java.util.List; + +@RequiredArgsConstructor +public class ScenarioEditorScreen extends ExtendableScreen { + private final Scenario scenario; + + @Override + public void renderBehindRivet() { + ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, + ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(50, 50, 50)); + } + + @Override + public void init(Rivet rivet) { + Container container = new Container(rivet, AnchorLayout.INSTANCE); + +// final FormattedLabel projectName = new FormattedLabel(rivet, scenario.getName()) { +// @Override +// public void render(final Renderer renderer, final Rectangle bounds) { +// renderer.scale(0.5f, () -> super.render(renderer, bounds)); +// } +// }; +// projectName.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(1 - +// (TextUtils.getVisualWidth(20, TextRun.fromString())) 0.015f).withAnchorMinY(0.015f)); +// container.addChild(projectName); + + RivetUtil.menuList(rivet, container, "Timeline", AnchorLayoutOptions.EMPTY.withAnchorMinX(0.015f).withAnchorMinY(0.01f), + List.of( + new Pair<>("Play", () -> {}), + new Pair<>("Pause", () -> {}) + ) + ); + + container.addChild(new SolidColor(rivet), c -> { + c.color(Color.fromRGB(35, 35, 35)); +// c.cornerRadius(5); + c.layoutOptions(new AnchorLayoutOptions( + 0.01f, 0.2f, 0.06f, 0.6f, + 0, 0, 0, 0, + 0, 0 + )); + }); + + container.addChild(new SolidColor(rivet), c -> { + c.color(Color.fromRGB(35, 35, 35)); +// c.cornerRadius(5); + c.layoutOptions(new AnchorLayoutOptions( + 0.205f, 0.8f, 0.06f, 0.6f, + 0, 0, 0, 0, + 0, 0 + )); + }); + + container.addChild(new SolidColor(rivet), c -> { + c.color(Color.fromRGB(35, 35, 35)); +// c.cornerRadius(5); + c.layoutOptions(new AnchorLayoutOptions( + 0.805f, 0.99f, 0.06f, 0.6f, + 0, 0, 0, 0, + 0, 0 + )); + }); + + container.addChild(new SolidColor(rivet), c -> { + c.color(Color.fromRGB(35, 35, 35)); +// c.cornerRadius(5); + c.layoutOptions(new AnchorLayoutOptions( + 0.01f, 0.99f, 0.605f, 0.99f, + 0, 0, 0, 0, + 0, 0 + )); + }); + +// container.addChild(new SolidColor(rivet), c -> { +// c.color(Color.RED); +// c.layoutOptions(new AnchorLayoutOptions( +// 0, 1, 0.7F, 1F, +// 0, 0, 0, 0, +// 0, 0 +// )); +// }); +// +// container.addChild(new SolidColor(rivet), c -> { +// c.color(Color.GREEN); +// c.layoutOptions(new AnchorLayoutOptions( +// 0, 0.2F, 0, 0.7F, +// 0, 0, 0, 0, +// 0, 0 +// )); +// }); +// container.addChild(new ScrollContainer(rivet, new Container(rivet, new VerticalListLayout(5, true)), c -> { +// for (int i = 0; i < 10; i++) { +// c.addChild(new Button(rivet, new Label(rivet, "Button " + i), e -> {})); +// } +// }), c -> { +// c.layoutOptions(new AnchorLayoutOptions( +// 0.7F, 1, 0, 0.7F, +// 0, 0, 0, 0, +// 0, 0 +// )); +// }); +// container.addChild(new SolidColor(rivet), c -> { +// c.color(Color.BLUE); +// c.layoutOptions(new AnchorLayoutOptions( +// 0.2F, 0.7F, 0, 0.7F, +// 0, 0, 0, 0, +// 0, 0 +// )); +// }); + rivet.root().addChild(container); + } +} diff --git a/editor/src/main/java/oxy/bascenario/util/RivetUtil.java b/editor/src/main/java/oxy/bascenario/util/RivetUtil.java new file mode 100644 index 00000000..38582307 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/util/RivetUtil.java @@ -0,0 +1,43 @@ +package oxy.bascenario.util; + +import net.lenni0451.rivet.Rivet; +import net.lenni0451.rivet.backend.Renderer; +import net.lenni0451.rivet.component.Container; +import net.lenni0451.rivet.component.base.Button; +import net.lenni0451.rivet.component.base.ComboBox; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Rectangle; +import oxy.bascenario.utils.math.Pair; + +import java.util.List; + +public class RivetUtil { + public static void menuList(Rivet rivet, Container container, String name, AnchorLayoutOptions layout, List> lists) { + final Container buttons = new Container(rivet, new VerticalListLayout(5, true)) { + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.scale(0.45f, () -> super.render(renderer, bounds)); + } + }; + + final ComboBox box = new ComboBox(rivet, name, buttons) { + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.scale(0.45f, () -> super.render(renderer, bounds)); + } + }; + box.arrowSize().set(0f); + box.layoutOptions(layout); + + for (Pair button : lists) { + buttons.addChild(new Button(rivet, new Label(rivet, button.left()), c -> { + box.close(); + button.right().run(); + })); + } + + container.addChild(box); + } +} diff --git a/editor/src/test/java/ScenarioEditorTest.java b/editor/src/test/java/ScenarioEditorTest.java index 8fca07da..9223035f 100644 --- a/editor/src/test/java/ScenarioEditorTest.java +++ b/editor/src/test/java/ScenarioEditorTest.java @@ -1,5 +1,5 @@ import oxy.bascenario.api.Scenario; -import oxy.bascenario.editor.screen.ScenarioEditorScreen; +import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.utils.Launcher; public class ScenarioEditorTest { @@ -7,6 +7,6 @@ public static void main(String[] args) { final Scenario.Builder scenario = new Scenario.Builder(); scenario.name("Scenario Editor Test"); - Launcher.launch(new ScenarioEditorScreen(null, scenario.build(), scenario), false); + Launcher.launch(new ScenarioEditorScreen(scenario.build()), false); } } From a40e374490bdac265d887fa9bab3eed2a8ccdda9 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sat, 30 May 2026 23:31:38 +0700 Subject: [PATCH 05/71] stuff. --- .../editor/ScenarioEditorScreen.java | 119 ++++-------------- .../java/oxy/bascenario/util/RivetUtil.java | 36 ++---- gradle/libs.versions.toml | 2 +- 3 files changed, 37 insertions(+), 120 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index f12a0617..c8738c75 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -3,24 +3,14 @@ import lombok.RequiredArgsConstructor; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; -import net.lenni0451.rivet.backend.Renderer; -import net.lenni0451.rivet.component.Container; -import net.lenni0451.rivet.component.base.Button; -import net.lenni0451.rivet.component.base.ComboBox; -import net.lenni0451.rivet.component.base.ScrollContainer; -import net.lenni0451.rivet.component.impl.FormattedLabel; -import net.lenni0451.rivet.component.impl.Label; -import net.lenni0451.rivet.component.impl.graphics.SolidColor; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; -import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Rectangle; import net.raphimc.thingl.ThinGL; -import net.raphimc.thingl.text.TextRun; import oxy.bascenario.api.Scenario; import oxy.bascenario.util.RivetUtil; import oxy.bascenario.utils.ExtendableScreen; -import oxy.bascenario.utils.font.TextUtils; import oxy.bascenario.utils.math.Pair; import oxy.bascenario.utils.thingl.ThinGLUtils; @@ -32,107 +22,44 @@ public class ScenarioEditorScreen extends ExtendableScreen { @Override public void renderBehindRivet() { - ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, - ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(50, 50, 50)); + ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(50, 50, 50)); } @Override public void init(Rivet rivet) { - Container container = new Container(rivet, AnchorLayout.INSTANCE); + Container container = new Container(AnchorLayout.INSTANCE); -// final FormattedLabel projectName = new FormattedLabel(rivet, scenario.getName()) { -// @Override -// public void render(final Renderer renderer, final Rectangle bounds) { -// renderer.scale(0.5f, () -> super.render(renderer, bounds)); -// } -// }; -// projectName.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(1 - -// (TextUtils.getVisualWidth(20, TextRun.fromString())) 0.015f).withAnchorMinY(0.015f)); -// container.addChild(projectName); - - RivetUtil.menuList(rivet, container, "Timeline", AnchorLayoutOptions.EMPTY.withAnchorMinX(0.015f).withAnchorMinY(0.01f), - List.of( - new Pair<>("Play", () -> {}), - new Pair<>("Pause", () -> {}) - ) - ); - - container.addChild(new SolidColor(rivet), c -> { + container.addChild(new SolidColor(), c -> { c.color(Color.fromRGB(35, 35, 35)); -// c.cornerRadius(5); - c.layoutOptions(new AnchorLayoutOptions( - 0.01f, 0.2f, 0.06f, 0.6f, - 0, 0, 0, 0, - 0, 0 - )); + c.cornerRadius(5); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.06f).withAnchorMaxX(0.2f).withAnchorMaxY(0.6f)); }); - container.addChild(new SolidColor(rivet), c -> { + container.addChild(new SolidColor(), c -> { c.color(Color.fromRGB(35, 35, 35)); -// c.cornerRadius(5); - c.layoutOptions(new AnchorLayoutOptions( - 0.205f, 0.8f, 0.06f, 0.6f, - 0, 0, 0, 0, - 0, 0 - )); + c.cornerRadius(5); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.06f).withAnchorMaxX(0.8f).withAnchorMaxY(0.6f)); }); - container.addChild(new SolidColor(rivet), c -> { + container.addChild(new SolidColor(), c -> { c.color(Color.fromRGB(35, 35, 35)); -// c.cornerRadius(5); - c.layoutOptions(new AnchorLayoutOptions( - 0.805f, 0.99f, 0.06f, 0.6f, - 0, 0, 0, 0, - 0, 0 - )); + c.cornerRadius(5); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); - container.addChild(new SolidColor(rivet), c -> { + container.addChild(new SolidColor(), c -> { c.color(Color.fromRGB(35, 35, 35)); -// c.cornerRadius(5); - c.layoutOptions(new AnchorLayoutOptions( - 0.01f, 0.99f, 0.605f, 0.99f, - 0, 0, 0, 0, - 0, 0 - )); + c.cornerRadius(5); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.605f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); }); -// container.addChild(new SolidColor(rivet), c -> { -// c.color(Color.RED); -// c.layoutOptions(new AnchorLayoutOptions( -// 0, 1, 0.7F, 1F, -// 0, 0, 0, 0, -// 0, 0 -// )); -// }); -// -// container.addChild(new SolidColor(rivet), c -> { -// c.color(Color.GREEN); -// c.layoutOptions(new AnchorLayoutOptions( -// 0, 0.2F, 0, 0.7F, -// 0, 0, 0, 0, -// 0, 0 -// )); -// }); -// container.addChild(new ScrollContainer(rivet, new Container(rivet, new VerticalListLayout(5, true)), c -> { -// for (int i = 0; i < 10; i++) { -// c.addChild(new Button(rivet, new Label(rivet, "Button " + i), e -> {})); -// } -// }), c -> { -// c.layoutOptions(new AnchorLayoutOptions( -// 0.7F, 1, 0, 0.7F, -// 0, 0, 0, 0, -// 0, 0 -// )); -// }); -// container.addChild(new SolidColor(rivet), c -> { -// c.color(Color.BLUE); -// c.layoutOptions(new AnchorLayoutOptions( -// 0.2F, 0.7F, 0, 0.7F, -// 0, 0, 0, 0, -// 0, 0 -// )); -// }); + + RivetUtil.dropDownList(container, "Timeline", AnchorLayoutOptions.EMPTY.withAnchorMinX(0.015f).withAnchorMinY(0.01f), + List.of( + new Pair<>("Play", () -> {}), + new Pair<>("Pause", () -> {}) + ) + ); rivet.root().addChild(container); } } diff --git a/editor/src/main/java/oxy/bascenario/util/RivetUtil.java b/editor/src/main/java/oxy/bascenario/util/RivetUtil.java index 38582307..fa31f876 100644 --- a/editor/src/main/java/oxy/bascenario/util/RivetUtil.java +++ b/editor/src/main/java/oxy/bascenario/util/RivetUtil.java @@ -1,41 +1,31 @@ package oxy.bascenario.util; -import net.lenni0451.rivet.Rivet; -import net.lenni0451.rivet.backend.Renderer; -import net.lenni0451.rivet.component.Container; -import net.lenni0451.rivet.component.base.Button; -import net.lenni0451.rivet.component.base.ComboBox; +import net.lenni0451.rivet.component.container.Button; +import net.lenni0451.rivet.component.container.ComboBox; +import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.utils.math.Pair; import java.util.List; public class RivetUtil { - public static void menuList(Rivet rivet, Container container, String name, AnchorLayoutOptions layout, List> lists) { - final Container buttons = new Container(rivet, new VerticalListLayout(5, true)) { - @Override - public void render(Renderer renderer, Rectangle bounds) { - renderer.scale(0.45f, () -> super.render(renderer, bounds)); - } - }; + public static void dropDownList(Container container, String name, AnchorLayoutOptions layout, List> lists) { + final Container buttons = new Container(new VerticalListLayout(1, true)); - final ComboBox box = new ComboBox(rivet, name, buttons) { - @Override - public void render(Renderer renderer, Rectangle bounds) { - renderer.scale(0.45f, () -> super.render(renderer, bounds)); - } - }; + final ComboBox box = new ComboBox(new Label(name).horizontalOrigin(TextOrigin.Horizontal.LOGICAL_LEFT).scale(0.45f), buttons); box.arrowSize().set(0f); box.layoutOptions(layout); - for (Pair button : lists) { - buttons.addChild(new Button(rivet, new Label(rivet, button.left()), c -> { + for (Pair pair : lists) { + Button button = new Button(new Label(pair.left()).scale(0.45f), c -> { box.close(); - button.right().run(); - })); + pair.right().run(); + }); + button.cornerRadius().set(0f); + buttons.addChild(button); } container.addChild(box); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f974013..7b84a30e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ lwjgl-sdl = "3.4.0" libgdx = "1.13.1" spine = "4.2.5" thingl = "0.1.1-SNAPSHOT" -rivet = "0.0.1-SNAPSHOT" +rivet = "0.0.1-20260530.162954-9" commons = "1.9.2" mocha = "3.0.1" configurate = "4.2.0" From a2525a27008d09adaf8ed0064d5f91089ec15e7e Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 31 May 2026 01:34:52 +0700 Subject: [PATCH 06/71] stuff. --- .../editor/ScenarioEditorScreen.java | 38 ++++++++++--------- .../editor/timeline/TimelineComponent.java | 22 +++++++++++ .../editor/timeline/TrackListComponent.java | 18 +++++++++ .../java/oxy/bascenario/util/RivetUtil.java | 4 +- 4 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/TimelineComponent.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/TrackListComponent.java diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index c8738c75..a36f4ce7 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -1,6 +1,5 @@ package oxy.bascenario.editor; -import lombok.RequiredArgsConstructor; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; import net.lenni0451.rivet.component.container.Container; @@ -9,17 +8,19 @@ import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.Scenario; -import oxy.bascenario.util.RivetUtil; +import oxy.bascenario.editor.timeline.TimelineComponent; import oxy.bascenario.utils.ExtendableScreen; -import oxy.bascenario.utils.math.Pair; +import oxy.bascenario.utils.TimeUtils; import oxy.bascenario.utils.thingl.ThinGLUtils; -import java.util.List; - -@RequiredArgsConstructor public class ScenarioEditorScreen extends ExtendableScreen { private final Scenario scenario; + public ScenarioEditorScreen(Scenario scenario) { + this.scenario = scenario; + TimeUtils.fakeTimeMillis = System.currentTimeMillis(); + } + @Override public void renderBehindRivet() { ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(50, 50, 50)); @@ -31,35 +32,38 @@ public void init(Rivet rivet) { container.addChild(new SolidColor(), c -> { c.color(Color.fromRGB(35, 35, 35)); - c.cornerRadius(5); c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.06f).withAnchorMaxX(0.2f).withAnchorMaxY(0.6f)); }); container.addChild(new SolidColor(), c -> { c.color(Color.fromRGB(35, 35, 35)); - c.cornerRadius(5); c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.06f).withAnchorMaxX(0.8f).withAnchorMaxY(0.6f)); }); container.addChild(new SolidColor(), c -> { c.color(Color.fromRGB(35, 35, 35)); - c.cornerRadius(5); c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); container.addChild(new SolidColor(), c -> { c.color(Color.fromRGB(35, 35, 35)); - c.cornerRadius(5); - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.605f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.605f).withAnchorMaxX(0.99f).withAnchorMaxY(0.65f)); }); + container.addChild(new TimelineComponent(), c -> { + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); + }); - RivetUtil.dropDownList(container, "Timeline", AnchorLayoutOptions.EMPTY.withAnchorMinX(0.015f).withAnchorMinY(0.01f), - List.of( - new Pair<>("Play", () -> {}), - new Pair<>("Pause", () -> {}) - ) - ); +// RivetUtil.dropDownList(container, "Timeline", AnchorLayoutOptions.EMPTY.withAnchorMinX(0.015f).withAnchorMinY(0.01f), +// List.of( +// new Pair<>("Play", () -> {}), +// new Pair<>("Pause", () -> {}) +// ) +// ); rivet.root().addChild(container); } + + @Override + public void render(float delta) { + } } diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineComponent.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineComponent.java new file mode 100644 index 00000000..f29d7666 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineComponent.java @@ -0,0 +1,22 @@ +package oxy.bascenario.editor.timeline; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; + +public class TimelineComponent extends Component { + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); + renderer.fillRect(0, 0, bounds.width() / 5, bounds.height(), Color.fromRGB(35, 35, 35)); + + + } + + @Override + public Size computeIdealSize(Size constraints) { + return null; // eh + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TrackListComponent.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TrackListComponent.java new file mode 100644 index 00000000..1894eec8 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/timeline/TrackListComponent.java @@ -0,0 +1,18 @@ +package oxy.bascenario.editor.timeline; + +import net.lenni0451.rivet.backend.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; + +public class TrackListComponent extends Component { + @Override + public void render(Renderer renderer, Rectangle bounds) { + + } + + @Override + public Size computeIdealSize(Size constraints) { + return null; + } +} diff --git a/editor/src/main/java/oxy/bascenario/util/RivetUtil.java b/editor/src/main/java/oxy/bascenario/util/RivetUtil.java index fa31f876..50ecde4e 100644 --- a/editor/src/main/java/oxy/bascenario/util/RivetUtil.java +++ b/editor/src/main/java/oxy/bascenario/util/RivetUtil.java @@ -6,7 +6,6 @@ import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.utils.math.Pair; import java.util.List; @@ -15,7 +14,8 @@ public class RivetUtil { public static void dropDownList(Container container, String name, AnchorLayoutOptions layout, List> lists) { final Container buttons = new Container(new VerticalListLayout(1, true)); - final ComboBox box = new ComboBox(new Label(name).horizontalOrigin(TextOrigin.Horizontal.LOGICAL_LEFT).scale(0.45f), buttons); + final ComboBox box = new ComboBox(new Label(name).scale(0.45f), buttons); + box.button().cornerRadius().set(0f); box.arrowSize().set(0f); box.layoutOptions(layout); From d0775823bce4f5bc2c8ea4b4e92cd05e89dc53de Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Tue, 2 Jun 2026 20:09:50 +0700 Subject: [PATCH 07/71] stuff. --- build.gradle.kts | 14 +--- core/build.gradle.kts | 8 +- editor/build.gradle.kts | 8 +- .../editor/ScenarioEditorScreen.java | 22 +++-- .../editor/timeline/TimelineContainer.java | 80 +++++++++++++++++++ .../editor/timeline/TimelineTabContainer.java | 53 ++++++++++++ ...lineComponent.java => TrackComponent.java} | 13 ++- .../editor/timeline/TrackListComponent.java | 18 ----- gradle/libs.versions.toml | 4 - 9 files changed, 169 insertions(+), 51 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java rename editor/src/main/java/oxy/bascenario/editor/timeline/{TimelineComponent.java => TrackComponent.java} (53%) delete mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/TrackListComponent.java diff --git a/build.gradle.kts b/build.gradle.kts index 6e6c897a..aae91d2f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,7 @@ version = "1.0" allprojects { repositories { mavenCentral() + maven("https://jitpack.io") maven("https://maven.lenni0451.net/snapshots") maven("https://central.sonatype.com/repository/maven-snapshots") maven("https://oss.sonatype.org/content/repositories/snapshots/") @@ -27,15 +28,4 @@ subprojects { implementation(rootProject.libs.google.gson) implementation(rootProject.libs.configurate.yaml) } -} -// -//dependencies { -// implementation(project(":editor")) -// implementation(project(":core")) -// implementation(project(":api")) -// -// implementation(libs.gdx.base) -// implementation(libs.commons.core) -// -// implementation(libs.imgui.binding) -//} \ No newline at end of file +} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 254fc16c..28d8d35e 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,6 +6,10 @@ var lwjgl = "3.3.6" var lwjglStd = "3.4.0" var libGdx = "1.13.1" +repositories { + maven("https://jitpack.io") +} + dependencies { implementation(project(":api")) implementation(project(":serializer")) @@ -51,6 +55,6 @@ dependencies { implementation("com.badlogicgames.gdx:gdx-platform:$libGdx:natives-desktop") - implementation(libs.rivet.core) - implementation(libs.rivet.thingl.backend) + implementation("com.github.Lenni0451.rivet:core:40c6f6c3a7") + implementation("com.github.Lenni0451.rivet:backend-thingl-glfw:40c6f6c3a7") } \ No newline at end of file diff --git a/editor/build.gradle.kts b/editor/build.gradle.kts index f36ab701..4eeed480 100644 --- a/editor/build.gradle.kts +++ b/editor/build.gradle.kts @@ -2,6 +2,10 @@ plugins { id("java") } +repositories { + maven("https://jitpack.io") +} + dependencies { implementation(project(":api")) implementation(project(":core")) @@ -16,6 +20,6 @@ dependencies { implementation(libs.jcraft.jorbis) implementation(libs.javazoom.jlayer) - implementation(libs.rivet.core) - implementation(libs.rivet.thingl.backend) + implementation("com.github.Lenni0451.rivet:core:40c6f6c3a7") + implementation("com.github.Lenni0451.rivet:backend-thingl-glfw:40c6f6c3a7") } \ No newline at end of file diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index a36f4ce7..8c1155ef 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -1,5 +1,8 @@ package oxy.bascenario.editor; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; import net.lenni0451.rivet.component.container.Container; @@ -8,14 +11,22 @@ import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.Scenario; -import oxy.bascenario.editor.timeline.TimelineComponent; +import oxy.bascenario.editor.timeline.TimelineContainer; +import oxy.bascenario.editor.timeline.TimelineTabContainer; import oxy.bascenario.utils.ExtendableScreen; import oxy.bascenario.utils.TimeUtils; import oxy.bascenario.utils.thingl.ThinGLUtils; +@Accessors(fluent = true) +@Getter +@Setter public class ScenarioEditorScreen extends ExtendableScreen { private final Scenario scenario; + public static final long DEFAULT_MAX_TIME = 15000; + private long timestamp; + private float scale = 1, scroll = 0; + public ScenarioEditorScreen(Scenario scenario) { this.scenario = scenario; TimeUtils.fakeTimeMillis = System.currentTimeMillis(); @@ -45,13 +56,12 @@ public void init(Rivet rivet) { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); - container.addChild(new SolidColor(), c -> { - c.color(Color.fromRGB(35, 35, 35)); - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.605f).withAnchorMaxX(0.99f).withAnchorMaxY(0.65f)); + container.addChild(new TimelineContainer(this), c -> { + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); }); - container.addChild(new TimelineComponent(), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); + container.addChild(new TimelineTabContainer(this), c -> { + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.605f).withAnchorMaxX(0.99f).withAnchorMaxY(0.65f)); }); // RivetUtil.dropDownList(container, "Timeline", AnchorLayoutOptions.EMPTY.withAnchorMinX(0.015f).withAnchorMinY(0.01f), diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java new file mode 100644 index 00000000..63c9b691 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java @@ -0,0 +1,80 @@ +package oxy.bascenario.editor.timeline; + +import lombok.RequiredArgsConstructor; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.ScenarioEditorScreen; + +import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; + +public class TimelineContainer extends Container { + private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(202, 74, 92); + + private float trackListSizeRatio = 1/5f; + + private final ScenarioEditorScreen screen; + public TimelineContainer(ScenarioEditorScreen screen) { + super(AbsoluteLayout.INSTANCE); + this.screen = screen; + } + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); + + drawTimeSection(renderer, bounds); + drawTimelineCursor(renderer, bounds); + + super.render(renderer, bounds); + } + + @Override + protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { + final float timelineX = bounds.width() * trackListSizeRatio; + if (event.x() > timelineX && event.x() < timelineX + (bounds.width() - timelineX) && event.y() < 35) { + final float ratio = (event.x() - timelineX) / (bounds.width() - timelineX); + long newTimestamp = (long) (DEFAULT_MAX_TIME * screen.scale() * screen.scroll() + ratio * DEFAULT_MAX_TIME * screen.scale()); + screen.timestamp(newTimestamp); + } + + return super.onComponentMouseDown(event, bounds); + } + + private void drawTimeSection(Renderer renderer, Rectangle bounds) { + renderer.fillRect(bounds.width() * trackListSizeRatio, 0, bounds.width() - (bounds.width() * trackListSizeRatio), 35, Color.fromRGB(35, 35, 35)); + + for (int i = 0; i <= 5; i++) { + long time = (long) ((DEFAULT_MAX_TIME * screen.scale() * screen.scroll()) + (DEFAULT_MAX_TIME * screen.scale() * (i / 5f))); + float segmentX = timestampToPosition(time, bounds.width() * trackListSizeRatio, bounds.width() - (bounds.width() * trackListSizeRatio)); + + float seconds = time / 1000f; + ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); + + renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); + renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, 25, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + } + } + + private void drawTimelineCursor(Renderer renderer, Rectangle bounds) { + final float cursorX = timestampToPosition(screen.timestamp(), bounds.width() * trackListSizeRatio, bounds.width() - (bounds.width() * trackListSizeRatio)); + + renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); + renderer.fillRect(cursorX, 0, 2, bounds.height(), TIMELINE_CURSOR_COLOR.withAlphaF(0.8f)); + } + + @Override + public Size computeIdealSize(Size constraints) { + return Size.EMPTY; + } + + private float timestampToPosition(long timestamp, float offsetX, float size) { + return offsetX + ((timestamp - (DEFAULT_MAX_TIME * screen.scale() * screen.scroll())) / (DEFAULT_MAX_TIME * screen.scale())) * size; + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java new file mode 100644 index 00000000..d96b4bfb --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java @@ -0,0 +1,53 @@ +package oxy.bascenario.editor.timeline; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.component.impl.slider.Slider; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; +import net.lenni0451.rivet.math.Rectangle; +import oxy.bascenario.editor.ScenarioEditorScreen; + +public class TimelineTabContainer extends Container { + private final ScenarioEditorScreen screen; + + private final Label timeLabel; + public TimelineTabContainer(final ScenarioEditorScreen screen) { + super(AnchorLayout.INSTANCE); + + final Slider slider = new Slider(0.01, 5, 0.01, 1); + slider.barHeight().set(5f); + slider.thumbWidth().set(20f); + slider.thumbHeight().set(20f); + slider.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.75f).withAnchorMaxX(0.95f).withAnchorMinY(0.5f).withPivotY(0.5f)); + + slider.valueChangeListener().add(d -> screen.scale(d.floatValue())); + + this.addChild(slider); + + timeLabel = new Label(""); + timeLabel.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.5F).withAnchorMinY(0.5f).pivot(0.5f, 0.5f)); + timeLabel.scale(.5f); + + this.addChild(timeLabel); + + this.screen = screen; + } + + @Override + public void render(Renderer renderer, Rectangle bounds) { + timeLabel.text(format(screen.timestamp())); + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); + super.render(renderer, bounds); + } + + private static String format(long ms) { + long millis = ms % 1000; + long second = (ms / 1000) % 60; + long minute = (ms / (1000 * 60)) % 60; + long hour = (ms / (1000 * 60 * 60)) % 24; + return (hour < 10 ? "0" + hour : hour) + ":" + (minute < 10 ? "0" + minute : minute) + ":" + (second < 10 ? "0" + second : second) + ":" + (millis < 10 ? "0" + millis : millis); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineComponent.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TrackComponent.java similarity index 53% rename from editor/src/main/java/oxy/bascenario/editor/timeline/TimelineComponent.java rename to editor/src/main/java/oxy/bascenario/editor/timeline/TrackComponent.java index f29d7666..210ab203 100644 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/timeline/TrackComponent.java @@ -1,22 +1,21 @@ package oxy.bascenario.editor.timeline; import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.Renderer; +import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; -public class TimelineComponent extends Component { +public class TrackComponent extends Component { + public float heightRatio = 1/4f; + @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); - renderer.fillRect(0, 0, bounds.width() / 5, bounds.height(), Color.fromRGB(35, 35, 35)); - - + renderer.fillRoundedRect(0, bounds.y(), bounds.width(), bounds.height() * heightRatio, 5, Color.fromRGB(35, 35, 35).brighter()); } @Override public Size computeIdealSize(Size constraints) { - return null; // eh + return Size.EMPTY; } } diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TrackListComponent.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TrackListComponent.java deleted file mode 100644 index 1894eec8..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/TrackListComponent.java +++ /dev/null @@ -1,18 +0,0 @@ -package oxy.bascenario.editor.timeline; - -import net.lenni0451.rivet.backend.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.math.Rectangle; -import net.lenni0451.rivet.math.Size; - -public class TrackListComponent extends Component { - @Override - public void render(Renderer renderer, Rectangle bounds) { - - } - - @Override - public Size computeIdealSize(Size constraints) { - return null; - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7b84a30e..50696dc0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,6 @@ lwjgl-sdl = "3.4.0" libgdx = "1.13.1" spine = "4.2.5" thingl = "0.1.1-SNAPSHOT" -rivet = "0.0.1-20260530.162954-9" commons = "1.9.2" mocha = "3.0.1" configurate = "4.2.0" @@ -42,9 +41,6 @@ gdx-spine = { module = "com.esotericsoftware.spine:spine-libgdx", version.ref = raphimc-thingl = { module = "net.raphimc:thingl", version.ref = "thingl" } raphimc-audiomixer = { module = "net.raphimc:audio-mixer", version.ref = "audiomixer" } -rivet-core = { module = "net.lenni0451.rivet:core", version.ref = "rivet" } -rivet-thingl-backend = { module = "net.lenni0451.rivet:backend-thingl-glfw", version.ref = "rivet" } - commons-animations = { module = "net.lenni0451.commons:animation", version.ref = "commons" } commons-core = { module = "net.lenni0451.commons:core", version.ref = "commons" } From 36f214ec5bb14d6cd03b8ee34adf378daeb7f04b Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Wed, 3 Jun 2026 02:56:07 +0700 Subject: [PATCH 08/71] Update Rivet. --- build.gradle.kts | 1 - core/build.gradle.kts | 4 ++-- .../main/java/oxy/bascenario/EngineRenderer.java | 10 +++++++++- editor/build.gradle.kts | 4 ++-- .../editor/timeline/TimelineContainer.java | 15 ++++++++++++++- .../editor/timeline/TimelineTabContainer.java | 1 + gradle/libs.versions.toml | 4 ++++ 7 files changed, 32 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index aae91d2f..4b13acd4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,6 @@ version = "1.0" allprojects { repositories { mavenCentral() - maven("https://jitpack.io") maven("https://maven.lenni0451.net/snapshots") maven("https://central.sonatype.com/repository/maven-snapshots") maven("https://oss.sonatype.org/content/repositories/snapshots/") diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 28d8d35e..9feda136 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -55,6 +55,6 @@ dependencies { implementation("com.badlogicgames.gdx:gdx-platform:$libGdx:natives-desktop") - implementation("com.github.Lenni0451.rivet:core:40c6f6c3a7") - implementation("com.github.Lenni0451.rivet:backend-thingl-glfw:40c6f6c3a7") + implementation(libs.rivet.core) + implementation(libs.rivet.thingl.backend) } \ No newline at end of file diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index ac5f3a1e..4ec15006 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -14,6 +14,7 @@ import net.lenni0451.rivet.backend.thingl.util.GLFWMapper; import net.lenni0451.rivet.input.keyboard.CharEvent; import net.lenni0451.rivet.input.keyboard.KeyEvent; +import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.input.mouse.MouseScrollEvent; @@ -32,6 +33,9 @@ import oxy.bascenario.utils.thingl.ThinGLExtended; import oxy.bascenario.utils.thingl.ThinGLUtils; +import java.util.EnumSet; +import java.util.Set; + @RequiredArgsConstructor public final class EngineRenderer extends Game { private ThinGLBackend backend; @@ -144,12 +148,13 @@ public void render() { } } + private final Set heldMouseButtons = EnumSet.noneOf(MouseButton.class); private void setupRivetCallbacks() { long windowHandle = ((Lwjgl3Graphics) Gdx.graphics).getWindow().getWindowHandle(); GLFW.glfwSetCursorPosCallback(windowHandle, (_, xpos, ypos) -> { float[] mouseScale = this.getMouseScale(); - this.rivet.onMouseMove(new MouseMoveEvent((float) xpos * mouseScale[0], (float) ypos * mouseScale[1])); + this.rivet.onMouseMove(new MouseMoveEvent((float) xpos * mouseScale[0], (float) ypos * mouseScale[1], this.heldMouseButtons)); }); GLFW.glfwSetMouseButtonCallback(windowHandle, (window, button, action, mods) -> { float[] mouseScale = this.getMouseScale(); @@ -160,9 +165,11 @@ private void setupRivetCallbacks() { MouseButtonEvent event = GLFWMapper.mapMouseButton((float) xpos[0] * mouseScale[0], (float) ypos[0] * mouseScale[1], button, mods); if (event != null) { if (action == GLFW.GLFW_PRESS) { + this.heldMouseButtons.add(event.button()); this.rivet.onMouseDown(event); } else if (action == GLFW.GLFW_RELEASE) { this.rivet.onMouseUp(event); + this.heldMouseButtons.add(event.button()); } } }); @@ -200,6 +207,7 @@ private void setupRivetCallbacks() { }); GLFW.glfwSetWindowFocusCallback(windowHandle, (window, focused) -> { if (!focused) { + this.heldMouseButtons.clear(); this.rivet.unfocus(); } }); diff --git a/editor/build.gradle.kts b/editor/build.gradle.kts index 4eeed480..c281b44f 100644 --- a/editor/build.gradle.kts +++ b/editor/build.gradle.kts @@ -20,6 +20,6 @@ dependencies { implementation(libs.jcraft.jorbis) implementation(libs.javazoom.jlayer) - implementation("com.github.Lenni0451.rivet:core:40c6f6c3a7") - implementation("com.github.Lenni0451.rivet:backend-thingl-glfw:40c6f6c3a7") + implementation(libs.rivet.core) + implementation(libs.rivet.thingl.backend) } \ No newline at end of file diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java index 63c9b691..1c59b996 100644 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java @@ -1,11 +1,12 @@ package oxy.bascenario.editor.timeline; -import lombok.RequiredArgsConstructor; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; @@ -35,6 +36,18 @@ public void render(Renderer renderer, Rectangle bounds) { super.render(renderer, bounds); } + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { + final float timelineX = bounds.width() * trackListSizeRatio; + if (event.x() > timelineX && event.x() < timelineX + (bounds.width() - timelineX) && event.y() < 35 && event.buttons().contains(MouseButton.LEFT)) { + final float ratio = (event.x() - timelineX) / (bounds.width() - timelineX); + long newTimestamp = (long) (DEFAULT_MAX_TIME * screen.scale() * screen.scroll() + ratio * DEFAULT_MAX_TIME * screen.scale()); + screen.timestamp(newTimestamp); + } + + return super.onComponentMouseMove(event, bounds); + } + @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { final float timelineX = bounds.width() * trackListSizeRatio; diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java index d96b4bfb..8ac11260 100644 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java @@ -22,6 +22,7 @@ public TimelineTabContainer(final ScenarioEditorScreen screen) { slider.thumbWidth().set(20f); slider.thumbHeight().set(20f); slider.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.75f).withAnchorMaxX(0.95f).withAnchorMinY(0.5f).withPivotY(0.5f)); + slider.showTooltip().set(false); slider.valueChangeListener().add(d -> screen.scale(d.floatValue())); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 50696dc0..45bdde51 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" +rivet = "5b1f772329" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } @@ -41,6 +42,9 @@ gdx-spine = { module = "com.esotericsoftware.spine:spine-libgdx", version.ref = raphimc-thingl = { module = "net.raphimc:thingl", version.ref = "thingl" } raphimc-audiomixer = { module = "net.raphimc:audio-mixer", version.ref = "audiomixer" } +rivet-core = { module = "com.github.Lenni0451.rivet:core", version.ref = "rivet"} +rivet-thingl-backend = { module = "com.github.Lenni0451.rivet:backend-thingl-glfw", version.ref = "rivet" } + commons-animations = { module = "net.lenni0451.commons:animation", version.ref = "commons" } commons-core = { module = "net.lenni0451.commons:core", version.ref = "commons" } From 44f1cff7a76bd3fa9fd3397c24f47d0146f7a3a8 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Wed, 3 Jun 2026 12:42:09 +0700 Subject: [PATCH 09/71] improve. --- .../java/oxy/bascenario/EngineRenderer.java | 40 ++++----- core/src/test/java/ImGuiTest.java | 4 +- core/src/test/java/RivetTest.java | 88 ------------------- 3 files changed, 21 insertions(+), 111 deletions(-) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index 4ec15006..88f282d5 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -59,6 +59,9 @@ public void create() { // GLFW.glfwSetWindowAspectRatio(windowHandle, 16, 9); GLFW.glfwSetCursorPosCallback(windowHandle, (window, x, y) -> { + float[] mouseScale = this.getMouseScale(); + this.rivet.onMouseMove(new MouseMoveEvent((float) x * mouseScale[0], (float) y * mouseScale[1], this.heldMouseButtons)); + if (window != windowHandle) { return; } @@ -67,6 +70,22 @@ public void create() { this.mouseY = y; }); GLFW.glfwSetMouseButtonCallback(windowHandle, (window, button, action, mode) -> { + float[] mouseScale = this.getMouseScale(); + final double[] xpos = new double[1]; + final double[] ypos = new double[1]; + GLFW.glfwGetCursorPos(window, xpos, ypos); + + MouseButtonEvent event = GLFWMapper.mapMouseButton((float) xpos[0] * mouseScale[0], (float) ypos[0] * mouseScale[1], button, mode); + if (event != null) { + if (action == GLFW.GLFW_PRESS) { + this.heldMouseButtons.add(event.button()); + this.rivet.onMouseDown(event); + } else if (action == GLFW.GLFW_RELEASE) { + this.rivet.onMouseUp(event); + this.heldMouseButtons.add(event.button()); + } + } + if (window != windowHandle || !(this.screen instanceof ExtendableScreen extendableScreen)) { return; } @@ -151,28 +170,7 @@ public void render() { private final Set heldMouseButtons = EnumSet.noneOf(MouseButton.class); private void setupRivetCallbacks() { long windowHandle = ((Lwjgl3Graphics) Gdx.graphics).getWindow().getWindowHandle(); - - GLFW.glfwSetCursorPosCallback(windowHandle, (_, xpos, ypos) -> { - float[] mouseScale = this.getMouseScale(); - this.rivet.onMouseMove(new MouseMoveEvent((float) xpos * mouseScale[0], (float) ypos * mouseScale[1], this.heldMouseButtons)); - }); - GLFW.glfwSetMouseButtonCallback(windowHandle, (window, button, action, mods) -> { - float[] mouseScale = this.getMouseScale(); - final double[] xpos = new double[1]; - final double[] ypos = new double[1]; - GLFW.glfwGetCursorPos(window, xpos, ypos); - MouseButtonEvent event = GLFWMapper.mapMouseButton((float) xpos[0] * mouseScale[0], (float) ypos[0] * mouseScale[1], button, mods); - if (event != null) { - if (action == GLFW.GLFW_PRESS) { - this.heldMouseButtons.add(event.button()); - this.rivet.onMouseDown(event); - } else if (action == GLFW.GLFW_RELEASE) { - this.rivet.onMouseUp(event); - this.heldMouseButtons.add(event.button()); - } - } - }); GLFW.glfwSetScrollCallback(windowHandle, (window, xoffset, yoffset) -> { float[] mouseScale = this.getMouseScale(); final double[] xpos = new double[1]; diff --git a/core/src/test/java/ImGuiTest.java b/core/src/test/java/ImGuiTest.java index d026e820..067666b5 100644 --- a/core/src/test/java/ImGuiTest.java +++ b/core/src/test/java/ImGuiTest.java @@ -1,4 +1,4 @@ -import imgui.ImGui; +//import imgui.ImGui; import oxy.bascenario.utils.ExtendableScreen; import oxy.bascenario.utils.Launcher; @@ -7,7 +7,7 @@ public static void main(String[] args) { Launcher.launch(new ExtendableScreen() { @Override public void render(float delta) { - ImGui.showDemoWindow(); +// ImGui.showDemoWindow(); } }, false); } diff --git a/core/src/test/java/RivetTest.java b/core/src/test/java/RivetTest.java index 8e1d6344..1d365d2e 100644 --- a/core/src/test/java/RivetTest.java +++ b/core/src/test/java/RivetTest.java @@ -1,12 +1,5 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; -import net.lenni0451.rivet.component.Container; -import net.lenni0451.rivet.component.base.Button; -import net.lenni0451.rivet.component.base.ComboBox; -import net.lenni0451.rivet.component.base.DecoratedContainer; -import net.lenni0451.rivet.component.base.ScrollContainer; -import net.lenni0451.rivet.component.impl.*; -import net.lenni0451.rivet.component.impl.graphics.SolidColor; import net.lenni0451.rivet.component.impl.slider.Slider; import net.lenni0451.rivet.input.keyboard.Key; import net.lenni0451.rivet.layout.grid.GridAnchor; @@ -23,86 +16,5 @@ public class RivetTest { public static void main(String[] args) { - Launcher.launch(new ExtendableScreen() { - @Override - public void init(Rivet rivet) { - rivet.theme(new DefaultDark() { - @Override - protected void addValues(final Rivet rivet, final Values values) { - super.addValues(rivet, values); - values.put(Theme.BUTTON_INACTIVE_COLOR, Color.GRAY.withAlpha(50)); - values.put(Theme.BUTTON_INACTIVE_OUTLINE_COLOR, Color.BLACK); - values.put(Theme.BUTTON_ACTIVE_COLOR, Color.GRAY.withAlpha(150)); - values.put(Theme.BUTTON_ACTIVE_OUTLINE_COLOR, Color.fromRGB(116, 165, 229)); - values.put(Theme.BUTTON_CLICK_COLOR, Color.GRAY.withAlpha(150).darker()); - values.put(Theme.BUTTON_CLICK_OUTLINE_COLOR, Color.fromRGB(116, 165, 229).darker()); - values.put(Theme.BUTTON_CORNER_RADIUS, 0F); - values.put(Theme.BUTTON_OUTLINE_WIDTH, 3F); - values.put(Theme.SCROLL_BAR_TYPE, ScrollContainer.ScrollBarType.NORMAL); - } - }); - - - Container container = new Container(rivet, new GridLayout(10, 10).withHomogeneousColumns(true).withShrinkColumns(true)); - rivet.root().addChild(new DecoratedContainer(rivet, new SolidColor(rivet, solidColor -> { - solidColor.color(Color.fromARGB(Integer.MIN_VALUE)); - solidColor.cornerRadius(20F); - solidColor.outlineColor(Color.GREEN); - }), new ScrollContainer(rivet, container, false, true)), decoratedContainer -> { - decoratedContainer.innerPadding(new Padding(20, 20, 20, 20)); - }); - container.addChild(new FormattedLabel(rivet, "Hello this is a really cool test string how are you doing lol"), label -> { - label.horizontalOrigin(TextOrigin.Horizontal.VISUAL_LEFT); - label.layoutOptions(new GridLayoutOptions(0, 0).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); - }); - container.addChild(new Button(rivet, new Label(rivet, "Singleplayer"), _ -> {}), button -> { - button.layoutOptions(new GridLayoutOptions(0, 1).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); - }); - container.addChild(new Button(rivet, new Label(rivet, "Minecraft Realms"), _ -> {}), button -> { - button.layoutOptions(new GridLayoutOptions(0, 2).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL)); - }); - container.addChild(new Button(rivet, new Label(rivet, "Multiplayer"), _ -> {}), button -> { - button.layoutOptions(new GridLayoutOptions(1, 2).withAnchor(GridAnchor.EAST).withWeightX(1).withFill(GridFill.HORIZONTAL)); - }); - container.addChild(new Button(rivet, new Label(rivet, "Whatever"), _ -> {}), button -> { - button.layoutOptions(new GridLayoutOptions(0, 3).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); - }); - container.addChild(new Button(rivet, new Label(rivet, "Options..."), _ -> {}), button -> { - button.layoutOptions(new GridLayoutOptions(0, 4).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withPadding(Padding.EMPTY.withTop(20))); - }); - container.addChild(new Button(rivet, new Label(rivet, "Quit Game"), _ -> {}), button -> { - button.layoutOptions(new GridLayoutOptions(1, 4).withAnchor(GridAnchor.EAST).withWeightX(1).withFill(GridFill.HORIZONTAL).withPadding(Padding.EMPTY.withTop(20))); - }); - container.addChild(new ComboBox(rivet, "Testing since 2k26", - new DecoratedContainer(rivet, new SolidColor(rivet, solidColor -> {solidColor.color(Color.GREEN); - }), new ScrollContainer(rivet, new Container(rivet, new VerticalListLayout(5, true)), - comboBoxContainer -> { - for (int i = 0; i < 10; i++) { - comboBoxContainer.addChild(new Button(rivet, new Label(rivet, "Test " + i), _ -> {})); - } - }))), comboBox -> comboBox.layoutOptions(new GridLayoutOptions(0, 5). - withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2))); - container.addChild(new TextField(rivet), textField -> { - textField.layoutOptions(new GridLayoutOptions(0, 6).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); - textField.keyDownListener().add(event -> { - if (event.key().isEquivalent(Key.ENTER)) { - System.out.println(textField.text()); - return true; - } - return false; - }); - }); - container.addChild(new Checkbox(rivet, "Test Test Test", true), checkbox -> { - checkbox.layoutOptions(new GridLayoutOptions(0, 7).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); - checkbox.cornerRadius().set(8F); - }); - container.addChild(new Slider(rivet, 0, 100, 1), slider -> { - slider.layoutOptions(new GridLayoutOptions(0, 8).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); - }); - container.addChild(new ColorPicker(rivet, Color.RED), colorPicker -> { - colorPicker.layoutOptions(new GridLayoutOptions(0, 9).withAnchor(GridAnchor.WEST).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); - }); - } - }, false); } } From f67ad8d36c081e70ca481160f7b632367d4a2154 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Thu, 4 Jun 2026 12:20:21 +0700 Subject: [PATCH 10/71] more work, separate render elements into components and containers. --- .../editor/ScenarioEditorScreen.java | 4 +- .../editor/containers/TimelineContainer.java | 59 ++++++++++++ .../TimelineTabContainer.java | 2 +- .../timeline/TimelineTimeSection.java | 65 +++++++++++++ .../timeline/TrackListContainer.java | 28 ++++++ .../containers/track/TrackComponent.java | 32 +++++++ .../editor/timeline/TimelineContainer.java | 93 ------------------- .../editor/timeline/TrackComponent.java | 21 ----- 8 files changed, 187 insertions(+), 117 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java rename editor/src/main/java/oxy/bascenario/editor/{timeline => containers}/TimelineTabContainer.java (98%) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/timeline/TrackComponent.java diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 8c1155ef..b97706a2 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -11,8 +11,8 @@ import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.Scenario; -import oxy.bascenario.editor.timeline.TimelineContainer; -import oxy.bascenario.editor.timeline.TimelineTabContainer; +import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.TimelineTabContainer; import oxy.bascenario.utils.ExtendableScreen; import oxy.bascenario.utils.TimeUtils; import oxy.bascenario.utils.thingl.ThinGLUtils; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java new file mode 100644 index 00000000..b1c82eb6 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -0,0 +1,59 @@ +package oxy.bascenario.editor.containers; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.editor.containers.timeline.TimelineTimeSection; +import oxy.bascenario.editor.containers.timeline.TrackListContainer; + +import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; + +public class TimelineContainer extends Container { + private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(202, 74, 92); + + private float trackListWidth = 200; + + private final ScenarioEditorScreen screen; + public TimelineContainer(ScenarioEditorScreen screen) { + super(AbsoluteLayout.INSTANCE); + this.screen = screen; + + this.addChild(new TrackListContainer(), c -> + c.layoutOptions(new AbsoluteLayoutOptions(0, 35, trackListWidth, null))); + this.addChild(new TimelineTimeSection(screen), c -> + c.layoutOptions(new AbsoluteLayoutOptions(trackListWidth, 0, null, 35f))); + } + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); + renderer.fillRect(0, 0, bounds.width(), 35, Color.fromRGB(35, 35, 35)); + + super.render(renderer, bounds); + + drawTimelineCursor(renderer, bounds); + } + + private void drawTimelineCursor(Renderer renderer, Rectangle bounds) { + final float cursorX = timestampToPosition(screen.timestamp(), trackListWidth, bounds.width() - trackListWidth, screen.scale(), screen.scroll()); + + renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); + renderer.fillRect(cursorX, 0, 2, bounds.height(), TIMELINE_CURSOR_COLOR.withAlphaF(0.8f)); + } + + @Override + public Size computeIdealSize(Size constraints) { + return Size.EMPTY; + } + + public static float timestampToPosition(long timestamp, float offsetX, float size, float scale, float scroll) { + return offsetX + ((timestamp - (DEFAULT_MAX_TIME * scale * scroll)) / (DEFAULT_MAX_TIME * scale)) * size; + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java similarity index 98% rename from editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java rename to editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java index 8ac11260..2a66db5f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java @@ -1,4 +1,4 @@ -package oxy.bascenario.editor.timeline; +package oxy.bascenario.editor.containers; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java new file mode 100644 index 00000000..c386fea3 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -0,0 +1,65 @@ +package oxy.bascenario.editor.containers.timeline; + +import lombok.RequiredArgsConstructor; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.editor.containers.TimelineContainer; + +import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; + +@RequiredArgsConstructor +public class TimelineTimeSection extends Component { + private final ScenarioEditorScreen screen; + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); + + for (int i = 0; i <= 5; i++) { + long time = (long) ((DEFAULT_MAX_TIME * screen.scale() * screen.scroll()) + (DEFAULT_MAX_TIME * screen.scale() * (i / 5f))); + float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), screen.scale(), screen.scroll()); + + float seconds = time / 1000f; + ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); + + renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); + renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + } + } + + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { + if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { + final float ratio = event.x() / (bounds.width() - bounds.x()); + long newTimestamp = (long) (DEFAULT_MAX_TIME * screen.scale() * screen.scroll() + ratio * DEFAULT_MAX_TIME * screen.scale()); + screen.timestamp(newTimestamp); + } + + return false; + } + + @Override + protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { + if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { + final float ratio = event.x() / (bounds.width() - bounds.x()); + long newTimestamp = (long) (DEFAULT_MAX_TIME * screen.scale() * screen.scroll() + ratio * DEFAULT_MAX_TIME * screen.scale()); + screen.timestamp(newTimestamp); + } + + return false; + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), 0); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java new file mode 100644 index 00000000..e19cfba3 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java @@ -0,0 +1,28 @@ +package oxy.bascenario.editor.containers.timeline; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.containers.track.TrackComponent; + +public class TrackListContainer extends Container { + public TrackListContainer() { + super(new VerticalListLayout()); + + this.addChild(new TrackComponent()); + } + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); + super.render(renderer, bounds); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(0, constraints.height()); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java new file mode 100644 index 00000000..19b4c615 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java @@ -0,0 +1,32 @@ +package oxy.bascenario.editor.containers.track; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; + +@Accessors(fluent = true) +public class TrackComponent extends Component { + @Setter @Getter + private int index; + private float heightRatio = 1/4f; + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.RED); // here for now... + + renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().shapeText("Track " + index, Color.WHITE), 10, 10, + TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), heightRatio * constraints.height()); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java deleted file mode 100644 index 1c59b996..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/TimelineContainer.java +++ /dev/null @@ -1,93 +0,0 @@ -package oxy.bascenario.editor.timeline; - -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.backend.text.ShapedText; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.input.mouse.MouseButtonEvent; -import net.lenni0451.rivet.input.mouse.MouseMoveEvent; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.math.Rectangle; -import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.ScenarioEditorScreen; - -import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; - -public class TimelineContainer extends Container { - private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(202, 74, 92); - - private float trackListSizeRatio = 1/5f; - - private final ScenarioEditorScreen screen; - public TimelineContainer(ScenarioEditorScreen screen) { - super(AbsoluteLayout.INSTANCE); - this.screen = screen; - } - - @Override - public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); - - drawTimeSection(renderer, bounds); - drawTimelineCursor(renderer, bounds); - - super.render(renderer, bounds); - } - - @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { - final float timelineX = bounds.width() * trackListSizeRatio; - if (event.x() > timelineX && event.x() < timelineX + (bounds.width() - timelineX) && event.y() < 35 && event.buttons().contains(MouseButton.LEFT)) { - final float ratio = (event.x() - timelineX) / (bounds.width() - timelineX); - long newTimestamp = (long) (DEFAULT_MAX_TIME * screen.scale() * screen.scroll() + ratio * DEFAULT_MAX_TIME * screen.scale()); - screen.timestamp(newTimestamp); - } - - return super.onComponentMouseMove(event, bounds); - } - - @Override - protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { - final float timelineX = bounds.width() * trackListSizeRatio; - if (event.x() > timelineX && event.x() < timelineX + (bounds.width() - timelineX) && event.y() < 35) { - final float ratio = (event.x() - timelineX) / (bounds.width() - timelineX); - long newTimestamp = (long) (DEFAULT_MAX_TIME * screen.scale() * screen.scroll() + ratio * DEFAULT_MAX_TIME * screen.scale()); - screen.timestamp(newTimestamp); - } - - return super.onComponentMouseDown(event, bounds); - } - - private void drawTimeSection(Renderer renderer, Rectangle bounds) { - renderer.fillRect(bounds.width() * trackListSizeRatio, 0, bounds.width() - (bounds.width() * trackListSizeRatio), 35, Color.fromRGB(35, 35, 35)); - - for (int i = 0; i <= 5; i++) { - long time = (long) ((DEFAULT_MAX_TIME * screen.scale() * screen.scroll()) + (DEFAULT_MAX_TIME * screen.scale() * (i / 5f))); - float segmentX = timestampToPosition(time, bounds.width() * trackListSizeRatio, bounds.width() - (bounds.width() * trackListSizeRatio)); - - float seconds = time / 1000f; - ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); - - renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); - renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, 25, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - } - } - - private void drawTimelineCursor(Renderer renderer, Rectangle bounds) { - final float cursorX = timestampToPosition(screen.timestamp(), bounds.width() * trackListSizeRatio, bounds.width() - (bounds.width() * trackListSizeRatio)); - - renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); - renderer.fillRect(cursorX, 0, 2, bounds.height(), TIMELINE_CURSOR_COLOR.withAlphaF(0.8f)); - } - - @Override - public Size computeIdealSize(Size constraints) { - return Size.EMPTY; - } - - private float timestampToPosition(long timestamp, float offsetX, float size) { - return offsetX + ((timestamp - (DEFAULT_MAX_TIME * screen.scale() * screen.scroll())) / (DEFAULT_MAX_TIME * screen.scale())) * size; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/timeline/TrackComponent.java b/editor/src/main/java/oxy/bascenario/editor/timeline/TrackComponent.java deleted file mode 100644 index 210ab203..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/timeline/TrackComponent.java +++ /dev/null @@ -1,21 +0,0 @@ -package oxy.bascenario.editor.timeline; - -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.math.Rectangle; -import net.lenni0451.rivet.math.Size; - -public class TrackComponent extends Component { - public float heightRatio = 1/4f; - - @Override - public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRoundedRect(0, bounds.y(), bounds.width(), bounds.height() * heightRatio, 5, Color.fromRGB(35, 35, 35).brighter()); - } - - @Override - public Size computeIdealSize(Size constraints) { - return Size.EMPTY; - } -} From 02303d27175f5ef1cedc5b5685108eb574733f5f Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Thu, 4 Jun 2026 13:28:05 +0700 Subject: [PATCH 11/71] more stuff. --- .../editor/containers/TimelineContainer.java | 8 ++++++-- .../containers/timeline/TrackListContainer.java | 15 ++++++++++----- .../editor/containers/track/TrackComponent.java | 11 ++++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index b1c82eb6..36f426e9 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -1,5 +1,7 @@ package oxy.bascenario.editor.containers; +import lombok.Getter; +import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; @@ -15,9 +17,11 @@ import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; +@Accessors(fluent = true) public class TimelineContainer extends Container { private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(202, 74, 92); + @Getter private float trackListWidth = 200; private final ScenarioEditorScreen screen; @@ -25,8 +29,8 @@ public TimelineContainer(ScenarioEditorScreen screen) { super(AbsoluteLayout.INSTANCE); this.screen = screen; - this.addChild(new TrackListContainer(), c -> - c.layoutOptions(new AbsoluteLayoutOptions(0, 35, trackListWidth, null))); + this.addChild(new TrackListContainer(this), c -> + c.layoutOptions(new AbsoluteLayoutOptions(0, 35, null, null))); this.addChild(new TimelineTimeSection(screen), c -> c.layoutOptions(new AbsoluteLayoutOptions(trackListWidth, 0, null, 35f))); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java index e19cfba3..9373781b 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java @@ -6,23 +6,28 @@ import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.track.TrackComponent; public class TrackListContainer extends Container { - public TrackListContainer() { - super(new VerticalListLayout()); + private final TimelineContainer parent; + public TrackListContainer(TimelineContainer parent) { + super(new VerticalListLayout(3, false)); + this.parent = parent; - this.addChild(new TrackComponent()); + this.addChild(new TrackComponent(parent)); + this.addChild(new TrackComponent(parent)); } @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); + renderer.fillRect(0, 0, parent.trackListWidth(), bounds.height(), Color.fromRGB(35, 35, 35)); + super.render(renderer, bounds); } @Override public Size computeIdealSize(Size constraints) { - return new Size(0, constraints.height()); + return new Size(constraints.width(), constraints.height()); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java index 19b4c615..759ebeaf 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java @@ -10,19 +10,24 @@ import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.containers.TimelineContainer; +@RequiredArgsConstructor @Accessors(fluent = true) public class TrackComponent extends Component { + private final TimelineContainer parent; + @Setter @Getter private int index; private float heightRatio = 1/4f; @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.RED); // here for now... +// renderer.fillRect(0, 0, parent.trackListWidth(), bounds.height(), Color.RED); // here for now... + + renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().shapeText("Track " + index, Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().shapeText("Track " + index, Color.WHITE), 10, 10, - TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + renderer.fillRoundedRect(parent.trackListWidth(), 0, bounds.width() + 100, bounds.height(), 5, Color.fromRGB(50, 50, 50)); } @Override From 61aa6e990363043ccaacf36882f334f818fd1197 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Thu, 4 Jun 2026 13:39:02 +0700 Subject: [PATCH 12/71] use scroll container. --- .../containers/timeline/TrackListContainer.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java index 9373781b..1dc0e5ab 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java @@ -3,20 +3,28 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.track.TrackComponent; -public class TrackListContainer extends Container { +public class TrackListContainer extends ScrollContainer { private final TimelineContainer parent; public TrackListContainer(TimelineContainer parent) { - super(new VerticalListLayout(3, false)); + final Container container = new Container(new VerticalListLayout(3, false)); + super(container); this.parent = parent; - this.addChild(new TrackComponent(parent)); - this.addChild(new TrackComponent(parent)); + // Test!~ + container.addChild(new TrackComponent(parent)); + container.addChild(new TrackComponent(parent)); + container.addChild(new TrackComponent(parent)); + container.addChild(new TrackComponent(parent)); + container.addChild(new TrackComponent(parent)); + container.addChild(new TrackComponent(parent)); + container.addChild(new TrackComponent(parent)); } @Override From 0f24afdb8dec098453881383ae7e6d1e55781534 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Thu, 4 Jun 2026 15:50:46 +0700 Subject: [PATCH 13/71] Fixed broken timestamp and allat. --- .../editor/ScenarioEditorScreen.java | 2 +- .../editor/containers/TimelineContainer.java | 7 +++-- .../timeline/TimelineTimeSection.java | 23 +++++++--------- .../TrackListContainer.java | 27 ++++++++++--------- 4 files changed, 28 insertions(+), 31 deletions(-) rename editor/src/main/java/oxy/bascenario/editor/containers/{timeline => track}/TrackListContainer.java (55%) diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index b97706a2..08021ba5 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -24,7 +24,7 @@ public class ScenarioEditorScreen extends ExtendableScreen { private final Scenario scenario; public static final long DEFAULT_MAX_TIME = 15000; - private long timestamp; + private long timestamp = 1000; private float scale = 1, scroll = 0; public ScenarioEditorScreen(Scenario scenario) { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 36f426e9..38f0337d 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -7,13 +7,11 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.layout.anchor.AnchorLayout; -import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.timeline.TimelineTimeSection; -import oxy.bascenario.editor.containers.timeline.TrackListContainer; +import oxy.bascenario.editor.containers.track.TrackListContainer; import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; @@ -24,6 +22,7 @@ public class TimelineContainer extends Container { @Getter private float trackListWidth = 200; + @Getter private final ScenarioEditorScreen screen; public TimelineContainer(ScenarioEditorScreen screen) { super(AbsoluteLayout.INSTANCE); @@ -31,7 +30,7 @@ public TimelineContainer(ScenarioEditorScreen screen) { this.addChild(new TrackListContainer(this), c -> c.layoutOptions(new AbsoluteLayoutOptions(0, 35, null, null))); - this.addChild(new TimelineTimeSection(screen), c -> + this.addChild(new TimelineTimeSection(this), c -> c.layoutOptions(new AbsoluteLayoutOptions(trackListWidth, 0, null, 35f))); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index c386fea3..753924e9 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -11,22 +11,19 @@ import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.TimelineContainer; import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; @RequiredArgsConstructor public class TimelineTimeSection extends Component { - private final ScenarioEditorScreen screen; + private final TimelineContainer container; @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); - for (int i = 0; i <= 5; i++) { - long time = (long) ((DEFAULT_MAX_TIME * screen.scale() * screen.scroll()) + (DEFAULT_MAX_TIME * screen.scale() * (i / 5f))); - float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), screen.scale(), screen.scroll()); + long time = (long) ((DEFAULT_MAX_TIME * container.screen().scale() * container.screen().scroll()) + (DEFAULT_MAX_TIME * container.screen().scale() * (i / 5f))); + float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), container.screen().scale(), container.screen().scroll()); float seconds = time / 1000f; ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); @@ -39,9 +36,9 @@ public void render(Renderer renderer, Rectangle bounds) { @Override protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { - final float ratio = event.x() / (bounds.width() - bounds.x()); - long newTimestamp = (long) (DEFAULT_MAX_TIME * screen.scale() * screen.scroll() + ratio * DEFAULT_MAX_TIME * screen.scale()); - screen.timestamp(newTimestamp); + final float ratio = Math.min((event.x() - 1.25f) / bounds.width(), 1); + long newTimestamp = (long) (DEFAULT_MAX_TIME * container.screen().scale() * container.screen().scroll() + ratio * DEFAULT_MAX_TIME * container.screen().scale()); + container.screen().timestamp(newTimestamp); } return false; @@ -50,9 +47,9 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { - final float ratio = event.x() / (bounds.width() - bounds.x()); - long newTimestamp = (long) (DEFAULT_MAX_TIME * screen.scale() * screen.scroll() + ratio * DEFAULT_MAX_TIME * screen.scale()); - screen.timestamp(newTimestamp); + final float ratio = Math.min((event.x() - 1.25f) / bounds.width(), 1); + long newTimestamp = (long) (DEFAULT_MAX_TIME * container.screen().scale() * container.screen().scroll() + ratio * DEFAULT_MAX_TIME * container.screen().scale()); + container.screen().timestamp(newTimestamp); } return false; @@ -60,6 +57,6 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), 0); + return new Size(constraints.width() - container.trackListWidth(), 0); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java similarity index 55% rename from editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java rename to editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 1dc0e5ab..e0bf65ff 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -1,36 +1,37 @@ -package oxy.bascenario.editor.containers.timeline; +package oxy.bascenario.editor.containers.track; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.editor.containers.track.TrackComponent; public class TrackListContainer extends ScrollContainer { private final TimelineContainer parent; + private final Container container; + public TrackListContainer(TimelineContainer parent) { - final Container container = new Container(new VerticalListLayout(3, false)); - super(container); + super(container = new Container(new VerticalListLayout(3, false))); this.parent = parent; - - // Test!~ - container.addChild(new TrackComponent(parent)); - container.addChild(new TrackComponent(parent)); - container.addChild(new TrackComponent(parent)); - container.addChild(new TrackComponent(parent)); - container.addChild(new TrackComponent(parent)); - container.addChild(new TrackComponent(parent)); - container.addChild(new TrackComponent(parent)); } @Override public void render(Renderer renderer, Rectangle bounds) { renderer.fillRect(0, 0, parent.trackListWidth(), bounds.height(), Color.fromRGB(35, 35, 35)); + if (container.children().isEmpty()) { + renderer.fillRoundedRect(parent.trackListWidth(), 0, 300, 1/4f * bounds.height(), 5, Color.fromRGB(50, 50, 50)); + renderer.scale(0.4f, () -> { + ShapedText text = this.rivet().backend().shapeText("Drop anything here to get started.", Color.WHITE); + renderer.text(text, parent.trackListWidth() + 350 ,1/4f * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP); + }); + } + super.render(renderer, bounds); } From 2540b5365b442bc45475ac8e7f452152d7164bf1 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Thu, 4 Jun 2026 17:42:35 +0700 Subject: [PATCH 14/71] more work or whatever. --- .../editor/ScenarioEditorScreen.java | 4 +- .../editor/containers/AOContainer.java | 36 +++++++ .../editor/containers/ao/AOTabContainer.java | 35 +++++++ .../editor/containers/ao/tab/AoTextTab.java | 47 ++++++++++ .../containers/track/TrackComponent.java | 4 +- .../containers/track/TrackListContainer.java | 19 +++- .../util/components/TypingLabel.java | 93 +++++++++++++++++++ gradle/libs.versions.toml | 2 +- 8 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java create mode 100644 editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 08021ba5..84109fdc 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -11,6 +11,7 @@ import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.Scenario; +import oxy.bascenario.editor.containers.AOContainer; import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.TimelineTabContainer; import oxy.bascenario.utils.ExtendableScreen; @@ -41,8 +42,7 @@ public void renderBehindRivet() { public void init(Rivet rivet) { Container container = new Container(AnchorLayout.INSTANCE); - container.addChild(new SolidColor(), c -> { - c.color(Color.fromRGB(35, 35, 35)); + container.addChild(new AOContainer(), c -> { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.06f).withAnchorMaxX(0.2f).withAnchorMaxY(0.6f)); }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java new file mode 100644 index 00000000..e015394c --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -0,0 +1,36 @@ +package oxy.bascenario.editor.containers; + +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.math.Rectangle; +import oxy.bascenario.editor.containers.ao.AOTabContainer; +import oxy.bascenario.editor.containers.ao.tab.AoTextTab; + +// Object & Actions container +@Accessors(fluent = true) +public class AOContainer extends Container { + @Setter + private Tab tab = Tab.Media; + + public AOContainer() { + super(AbsoluteLayout.INSTANCE); + + this.addChild(new AOTabContainer(this), c -> c.layoutOptions(new AbsoluteLayoutOptions(0, 0))); + this.addChild(new AoTextTab(), c -> c.layoutOptions(new AbsoluteLayoutOptions(10, 45))); + } + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); + super.render(renderer, bounds); + } + + public enum Tab { + Media, Text, Object, Transition, Actions + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java new file mode 100644 index 00000000..0f40af33 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java @@ -0,0 +1,35 @@ +package oxy.bascenario.editor.containers.ao; + +import net.lenni0451.rivet.component.container.Button; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.layout.list.HorizontalListLayout; +import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.containers.AOContainer; + +public class AOTabContainer extends ScrollContainer { + public AOTabContainer(AOContainer parent) { + final Container container = new Container(new HorizontalListLayout(2, false)); + super(container, true, false); + + for (AOContainer.Tab tab : AOContainer.Tab.values()) { + add(parent, container, tab); + } + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), 40); + } + + private void add(AOContainer parent, Container container, AOContainer.Tab targetTab) { + final Label label = new Label(targetTab.name()).scale(0.5f); + final Button button = new Button(label, ignored -> {}); + button.clickListener().add(ignored -> { + parent.tab(targetTab); + }); + + container.addChild(button); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java new file mode 100644 index 00000000..5d40cec9 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java @@ -0,0 +1,47 @@ +package oxy.bascenario.editor.containers.ao.tab; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Button; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.component.impl.SolidColor; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.layout.grid.GridLayoutOptions; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import oxy.bascenario.util.components.TypingLabel; + +public class AoTextTab extends ScrollContainer { + public AoTextTab() { + final Container container = new Container(new VerticalListLayout(10, false)); + super(container); + + final Button defaultText = new Button(new Label("Default Text").scale(0.7f), ignored -> {}); + defaultText.mouseDownListener().add((ignored, bounds) -> { + Button ghost = new Button(new Label("Default Text").scale(0.7f), _ -> {}); + ghost.interactive(false); + ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); + + // Start the drag with some payload + rivet().dragAndDropManager().startDrag( + "Default Text", + ghost, + new Size(bounds.width(), bounds.height()), + -bounds.width() / 2f, + -bounds.height() / 2f + ); + return true; + }); + + container.addChild(defaultText); + container.addChild(new Button(new TypingLabel("Typing Text").scale(0.7f), ignored -> {})); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), constraints.height() - 40); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java index 759ebeaf..f4a79c8a 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java @@ -19,7 +19,7 @@ public class TrackComponent extends Component { @Setter @Getter private int index; - private float heightRatio = 1/4f; + private float height = 60f; @Override public void render(Renderer renderer, Rectangle bounds) { @@ -32,6 +32,6 @@ public void render(Renderer renderer, Rectangle bounds) { @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), heightRatio * constraints.height()); + return new Size(constraints.width(), height); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index e0bf65ff..f2b95139 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -5,6 +5,8 @@ import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.dragdrop.DragOverEvent; +import net.lenni0451.rivet.dragdrop.DropEvent; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; @@ -25,16 +27,29 @@ public void render(Renderer renderer, Rectangle bounds) { renderer.fillRect(0, 0, parent.trackListWidth(), bounds.height(), Color.fromRGB(35, 35, 35)); if (container.children().isEmpty()) { - renderer.fillRoundedRect(parent.trackListWidth(), 0, 300, 1/4f * bounds.height(), 5, Color.fromRGB(50, 50, 50)); + renderer.fillRoundedRect(parent.trackListWidth(), 0, 300, 60f, 5, Color.fromRGB(50, 50, 50)); renderer.scale(0.4f, () -> { ShapedText text = this.rivet().backend().shapeText("Drop anything here to get started.", Color.WHITE); - renderer.text(text, parent.trackListWidth() + 350 ,1/4f * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP); + renderer.text(text, parent.trackListWidth() + 350 ,(60f / 2f - (this.rivet().backend().getTextHeight() * 0.4f) / 2f) / 0.4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP); }); } super.render(renderer, bounds); } + @Override + protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { + if (!container.children().isEmpty()) { + return super.onComponentDrop(event, bounds); + } + if (event.x() > parent.trackListWidth() && event.x() < parent.trackListWidth() + bounds.width() && event.y() > 0 && event.y() < 60f) { + final TrackComponent component = new TrackComponent(parent); + container.addChild(component); + } + + return super.onComponentDrop(event, bounds); + } + @Override public Size computeIdealSize(Size constraints) { return new Size(constraints.width(), constraints.height()); diff --git a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java b/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java new file mode 100644 index 00000000..33146d2d --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java @@ -0,0 +1,93 @@ +package oxy.bascenario.util.components; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.math.MathUtils; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import net.lenni0451.rivet.theme.Theme; + +@Accessors(fluent = true, chain = true, makeFinal = true) +public class TypingLabel extends Component { + + @Getter + private String text; + private ShapedText shapedText; + private boolean reshape; + @Getter + @Setter + private TextOrigin.Horizontal horizontalOrigin = TextOrigin.Horizontal.VISUAL_CENTER; + @Getter + @Setter + private TextOrigin.Vertical verticalOrigin = TextOrigin.Vertical.LOGICAL_CENTER; + @Getter + private float scale = 1F; + + public TypingLabel(final String text) { + this.text = text; + } + + public TypingLabel text(final String text) { + if (!this.text.equals(text)) { + this.text = text; + this.reshape = true; + if (this.parent() != null) { + this.parent().requestLayoutRecalculation(); + } + } + return this; + } + + public TypingLabel scale(final float scale) { + if (this.scale != scale) { + this.scale = scale; + if (this.parent() != null) { + this.parent().requestLayoutRecalculation(); + } + } + return this; + } + + private void shapeText() { + if (this.reshape) { + this.shapedText = this.rivet().backend().shapeText(this.text, this.rivet().theme().get(Theme.TEXT_COLOR)); + this.reshape = false; + } + } + + @Override + protected void onComponentAdded() { + this.reshape = true; + } + + @Override + public void onThemeChanged() { + this.reshape = true; + } + + @Override + public void render(final Renderer renderer, final Rectangle bounds) { + this.shapeText(); + float x = this.horizontalOrigin.offset(bounds.width() / this.scale); + float y = this.verticalOrigin.offset(bounds.height() / this.scale); + + float progress = (System.currentTimeMillis() % 3000) / 3000f; + net.lenni0451.rivet.backend.text.ShapedText text1 = this.rivet().backend().shapeText(this.text.substring(0, MathUtils.ceilInt(progress * this.text.length())), this.rivet().theme().get(Theme.TEXT_COLOR)); + renderer.scale(this.scale, () -> renderer.text(text1, x, y, this.horizontalOrigin, this.verticalOrigin)); + } + + @Override + public Size computeIdealSize(final Size constraints) { + this.shapeText(); + return new Size( + this.shapedText.visualBounds().width() * this.scale, + this.shapedText.logicalBounds().height() * this.scale + ); + } + +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 45bdde51..821297cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "5b1f772329" +rivet = "8d7bc03154" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } From b7302b7ab870280775c94a3921eb456753a4e33c Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Thu, 4 Jun 2026 19:25:52 +0700 Subject: [PATCH 15/71] whatever. --- .../oxy/bascenario/editor/containers/TimelineTabContainer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java index 2a66db5f..2d20f7da 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java @@ -21,7 +21,7 @@ public TimelineTabContainer(final ScenarioEditorScreen screen) { slider.barHeight().set(5f); slider.thumbWidth().set(20f); slider.thumbHeight().set(20f); - slider.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.75f).withAnchorMaxX(0.95f).withAnchorMinY(0.5f).withPivotY(0.5f)); + slider.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.78f).withAnchorMaxX(0.98f).withAnchorMinY(0.5f).withPivotY(0.5f)); slider.showTooltip().set(false); slider.valueChangeListener().add(d -> screen.scale(d.floatValue())); From f398d4d792b397c698798ee28fe8c81df476c87b Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 5 Jun 2026 02:07:10 +0700 Subject: [PATCH 16/71] Test --- ...{TrackComponent.java => TrackContainer.java} | 17 +++++++++++++---- .../containers/track/TrackListContainer.java | 5 ++--- 2 files changed, 15 insertions(+), 7 deletions(-) rename editor/src/main/java/oxy/bascenario/editor/containers/track/{TrackComponent.java => TrackContainer.java} (67%) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java similarity index 67% rename from editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java rename to editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index f4a79c8a..c9afce00 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -6,21 +6,28 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.containers.TimelineContainer; -@RequiredArgsConstructor @Accessors(fluent = true) -public class TrackComponent extends Component { +public class TrackContainer extends Container { private final TimelineContainer parent; @Setter @Getter private int index; private float height = 60f; + public TrackContainer(TimelineContainer parent) { + super(AbsoluteLayout.INSTANCE); + this.parent = parent; + +// this.addChild(new Button("Test", c -> {}).layoutOptions(new AbsoluteLayoutOptions(1200, 0))); + } + @Override public void render(Renderer renderer, Rectangle bounds) { // renderer.fillRect(0, 0, parent.trackListWidth(), bounds.height(), Color.RED); // here for now... @@ -28,10 +35,12 @@ public void render(Renderer renderer, Rectangle bounds) { renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().shapeText("Track " + index, Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); renderer.fillRoundedRect(parent.trackListWidth(), 0, bounds.width() + 100, bounds.height(), 5, Color.fromRGB(50, 50, 50)); + + super.render(renderer, bounds); } @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), height); + return children().isEmpty() ? new Size(constraints.width(), height) : super.computeIdealSize(new Size(constraints.width(), height)); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index f2b95139..a5b668be 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -5,7 +5,6 @@ import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.dragdrop.DragOverEvent; import net.lenni0451.rivet.dragdrop.DropEvent; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Rectangle; @@ -18,7 +17,7 @@ public class TrackListContainer extends ScrollContainer { private final Container container; public TrackListContainer(TimelineContainer parent) { - super(container = new Container(new VerticalListLayout(3, false))); + super(container = new Container(new VerticalListLayout(3, false)), true, true); this.parent = parent; } @@ -43,7 +42,7 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { return super.onComponentDrop(event, bounds); } if (event.x() > parent.trackListWidth() && event.x() < parent.trackListWidth() + bounds.width() && event.y() > 0 && event.y() < 60f) { - final TrackComponent component = new TrackComponent(parent); + final TrackContainer component = new TrackContainer(parent); container.addChild(component); } From 0de08e498b5a5351c360b1cfd8303f6a527a9b92 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 5 Jun 2026 02:48:21 +0700 Subject: [PATCH 17/71] stuff. --- .../bascenario/editor/containers/track/TrackContainer.java | 7 +++++-- .../editor/containers/track/TrackListContainer.java | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index c9afce00..6c1e9166 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -6,8 +6,10 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; @@ -25,7 +27,7 @@ public TrackContainer(TimelineContainer parent) { super(AbsoluteLayout.INSTANCE); this.parent = parent; -// this.addChild(new Button("Test", c -> {}).layoutOptions(new AbsoluteLayoutOptions(1200, 0))); + this.addChild(new Button("Test", c -> {}).layoutOptions(new AbsoluteLayoutOptions(2000, 0))); } @Override @@ -41,6 +43,7 @@ public void render(Renderer renderer, Rectangle bounds) { @Override public Size computeIdealSize(Size constraints) { - return children().isEmpty() ? new Size(constraints.width(), height) : super.computeIdealSize(new Size(constraints.width(), height)); + Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); + return new Size(Math.max(idealSize.width(), constraints.width()), height); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index a5b668be..551d4c9a 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -51,6 +51,6 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), constraints.height()); + return constraints; } } From 4dd5dc88206ca228c885418eeac6ef3881363847 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 5 Jun 2026 02:57:14 +0700 Subject: [PATCH 18/71] Fix gradlew. --- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 60756 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 Date: Fri, 5 Jun 2026 03:27:04 +0700 Subject: [PATCH 19/71] ok initially fix. --- .../editor/containers/TimelineContainer.java | 11 +++--- .../timeline/TimelineTimeSection.java | 38 ++++++++++--------- .../containers/track/TrackListContainer.java | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 38f0337d..e4137a29 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -7,6 +7,9 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.layout.border.BorderLayout; +import net.lenni0451.rivet.layout.border.BorderPosition; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; @@ -25,13 +28,11 @@ public class TimelineContainer extends Container { @Getter private final ScenarioEditorScreen screen; public TimelineContainer(ScenarioEditorScreen screen) { - super(AbsoluteLayout.INSTANCE); + super(BorderLayout.INSTANCE); this.screen = screen; - this.addChild(new TrackListContainer(this), c -> - c.layoutOptions(new AbsoluteLayoutOptions(0, 35, null, null))); - this.addChild(new TimelineTimeSection(this), c -> - c.layoutOptions(new AbsoluteLayoutOptions(trackListWidth, 0, null, 35f))); + this.addChild(new TimelineTimeSection(this), c -> c.layoutOptions(BorderPosition.TOP)); +// this.addChild(new TrackListContainer(this), c -> c.layoutOptions(BorderPosition.CENTER)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index 753924e9..5ae1dcaf 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -17,28 +17,30 @@ @RequiredArgsConstructor public class TimelineTimeSection extends Component { - private final TimelineContainer container; + private final TimelineContainer parent; @Override public void render(Renderer renderer, Rectangle bounds) { - for (int i = 0; i <= 5; i++) { - long time = (long) ((DEFAULT_MAX_TIME * container.screen().scale() * container.screen().scroll()) + (DEFAULT_MAX_TIME * container.screen().scale() * (i / 5f))); - float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), container.screen().scale(), container.screen().scroll()); + renderer.translate(parent.trackListWidth(), 0, () -> { + for (int i = 0; i <= 5; i++) { + long time = (long) ((DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll()) + (DEFAULT_MAX_TIME * parent.screen().scale() * (i / 5f))); + float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), parent.screen().scale(), parent.screen().scroll()); - float seconds = time / 1000f; - ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); + float seconds = time / 1000f; + ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); - renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); - renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - } + renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); + renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + } + }); } @Override protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { - if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { - final float ratio = Math.min((event.x() - 1.25f) / bounds.width(), 1); - long newTimestamp = (long) (DEFAULT_MAX_TIME * container.screen().scale() * container.screen().scroll() + ratio * DEFAULT_MAX_TIME * container.screen().scale()); - container.screen().timestamp(newTimestamp); + if (event.x() > parent.trackListWidth() && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { + final float ratio = Math.min((event.x() - parent.trackListWidth() - 1.25f) / (bounds.width() - parent.trackListWidth()), 1); + long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); + parent.screen().timestamp(newTimestamp); } return false; @@ -46,10 +48,10 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { - if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { - final float ratio = Math.min((event.x() - 1.25f) / bounds.width(), 1); - long newTimestamp = (long) (DEFAULT_MAX_TIME * container.screen().scale() * container.screen().scroll() + ratio * DEFAULT_MAX_TIME * container.screen().scale()); - container.screen().timestamp(newTimestamp); + if (event.x() > parent.trackListWidth() && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { + final float ratio = Math.min((event.x() - parent.trackListWidth() - 1.25f) / (bounds.width() - parent.trackListWidth()) , 1); + long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); + parent.screen().timestamp(newTimestamp); } return false; @@ -57,6 +59,6 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width() - container.trackListWidth(), 0); + return new Size(constraints.width(), 35); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 551d4c9a..e24889db 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -51,6 +51,6 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { @Override public Size computeIdealSize(Size constraints) { - return constraints; + return new Size(constraints.width() - parent.trackListWidth(), constraints.height()); } } From f246dda2508dd15b81cbd85bc93b3b2fa77efe6e Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 5 Jun 2026 11:50:25 +0700 Subject: [PATCH 20/71] Fix layout and proper scrolling. --- .../editor/ScenarioEditorScreen.java | 17 +++++++++--- .../editor/containers/TimelineContainer.java | 12 +++++---- .../timeline/TimelineTimeSection.java | 26 +++++++++---------- .../containers/track/TrackContainer.java | 6 +---- .../containers/track/TrackListContainer.java | 10 +++---- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 84109fdc..ebabcce4 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -26,7 +26,13 @@ public class ScenarioEditorScreen extends ExtendableScreen { public static final long DEFAULT_MAX_TIME = 15000; private long timestamp = 1000; - private float scale = 1, scroll = 0; + private float scale = 1; + + private TimelineContainer timelineContainer; + public float scroll() { + float width = ThinGL.windowInterface().getFramebufferWidth(); + return timelineContainer().scroll(0.99f * width - 0.15625f * width); + } public ScenarioEditorScreen(Scenario scenario) { this.scenario = scenario; @@ -56,8 +62,13 @@ public void init(Rivet rivet) { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); - container.addChild(new TimelineContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); + container.addChild(new SolidColor(), c -> { + c.color(Color.RED); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.15625f).withAnchorMaxY(0.99f)); + }); + + container.addChild(timelineContainer = new TimelineContainer(this), c -> { + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.15625f).withAnchorMinY(0.655f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); }); container.addChild(new TimelineTabContainer(this), c -> { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index e4137a29..9e5f2970 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -21,9 +21,7 @@ @Accessors(fluent = true) public class TimelineContainer extends Container { private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(202, 74, 92); - - @Getter - private float trackListWidth = 200; + private final TrackListContainer trackListContainer; @Getter private final ScenarioEditorScreen screen; @@ -32,7 +30,11 @@ public TimelineContainer(ScenarioEditorScreen screen) { this.screen = screen; this.addChild(new TimelineTimeSection(this), c -> c.layoutOptions(BorderPosition.TOP)); -// this.addChild(new TrackListContainer(this), c -> c.layoutOptions(BorderPosition.CENTER)); + this.addChild(trackListContainer = new TrackListContainer(this), c -> c.layoutOptions(BorderPosition.CENTER)); + } + + public float scroll(float width) { + return trackListContainer.scrollX() / width; } @Override @@ -46,7 +48,7 @@ public void render(Renderer renderer, Rectangle bounds) { } private void drawTimelineCursor(Renderer renderer, Rectangle bounds) { - final float cursorX = timestampToPosition(screen.timestamp(), trackListWidth, bounds.width() - trackListWidth, screen.scale(), screen.scroll()); + final float cursorX = timestampToPosition(screen.timestamp(), 0, bounds.width(), screen.scale(), screen.scroll()); renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); renderer.fillRect(cursorX, 0, 2, bounds.height(), TIMELINE_CURSOR_COLOR.withAlphaF(0.8f)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index 5ae1dcaf..c7801d05 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -21,24 +21,22 @@ public class TimelineTimeSection extends Component { @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.translate(parent.trackListWidth(), 0, () -> { - for (int i = 0; i <= 5; i++) { - long time = (long) ((DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll()) + (DEFAULT_MAX_TIME * parent.screen().scale() * (i / 5f))); - float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), parent.screen().scale(), parent.screen().scroll()); + for (int i = 0; i <= 5; i++) { + long time = (long) ((DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll()) + (DEFAULT_MAX_TIME * parent.screen().scale() * (i / 5f))); + float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), parent.screen().scale(), parent.screen().scroll()); - float seconds = time / 1000f; - ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); + float seconds = time / 1000f; + ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); - renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); - renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - } - }); + renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); + renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + } } @Override protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { - if (event.x() > parent.trackListWidth() && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { - final float ratio = Math.min((event.x() - parent.trackListWidth() - 1.25f) / (bounds.width() - parent.trackListWidth()), 1); + if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { + final float ratio = Math.min((event.x() - 1.25f) / (bounds.width()), 1); long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); parent.screen().timestamp(newTimestamp); } @@ -48,8 +46,8 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { - if (event.x() > parent.trackListWidth() && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { - final float ratio = Math.min((event.x() - parent.trackListWidth() - 1.25f) / (bounds.width() - parent.trackListWidth()) , 1); + if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { + final float ratio = Math.min((event.x() - 1.25f) / (bounds.width()) , 1); long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); parent.screen().timestamp(newTimestamp); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 6c1e9166..50ce2716 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -26,17 +26,13 @@ public class TrackContainer extends Container { public TrackContainer(TimelineContainer parent) { super(AbsoluteLayout.INSTANCE); this.parent = parent; - - this.addChild(new Button("Test", c -> {}).layoutOptions(new AbsoluteLayoutOptions(2000, 0))); } @Override public void render(Renderer renderer, Rectangle bounds) { -// renderer.fillRect(0, 0, parent.trackListWidth(), bounds.height(), Color.RED); // here for now... - renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().shapeText("Track " + index, Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - renderer.fillRoundedRect(parent.trackListWidth(), 0, bounds.width() + 100, bounds.height(), 5, Color.fromRGB(50, 50, 50)); + renderer.fillRoundedRect(0, 0, bounds.width() + 100, bounds.height(), 5, Color.fromRGB(50, 50, 50)); super.render(renderer, bounds); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index e24889db..04dcfaf4 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -23,13 +23,11 @@ public TrackListContainer(TimelineContainer parent) { @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRect(0, 0, parent.trackListWidth(), bounds.height(), Color.fromRGB(35, 35, 35)); - if (container.children().isEmpty()) { - renderer.fillRoundedRect(parent.trackListWidth(), 0, 300, 60f, 5, Color.fromRGB(50, 50, 50)); + renderer.fillRoundedRect(0, 0, 300, 60f, 5, Color.fromRGB(50, 50, 50)); renderer.scale(0.4f, () -> { ShapedText text = this.rivet().backend().shapeText("Drop anything here to get started.", Color.WHITE); - renderer.text(text, parent.trackListWidth() + 350 ,(60f / 2f - (this.rivet().backend().getTextHeight() * 0.4f) / 2f) / 0.4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP); + renderer.text(text, 50,(60f / 2f - (this.rivet().backend().getTextHeight() * 0.4f) / 2f) / 0.4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP); }); } @@ -41,7 +39,7 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { if (!container.children().isEmpty()) { return super.onComponentDrop(event, bounds); } - if (event.x() > parent.trackListWidth() && event.x() < parent.trackListWidth() + bounds.width() && event.y() > 0 && event.y() < 60f) { + if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < 60f) { final TrackContainer component = new TrackContainer(parent); container.addChild(component); } @@ -51,6 +49,6 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width() - parent.trackListWidth(), constraints.height()); + return constraints; } } From a2a7c7561dbdb5fcdff520daca2c96e12f5962b2 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 5 Jun 2026 12:13:41 +0700 Subject: [PATCH 21/71] more work. --- .../editor/ScenarioEditorScreen.java | 16 ++++++++-- .../containers/track/TrackContainer.java | 15 +++------ .../containers/track/TrackListContainer.java | 3 ++ .../track/tab/TrackTabContainer.java | 32 +++++++++++++++++++ .../track/tab/TrackTabListContainer.java | 26 +++++++++++++++ 5 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index ebabcce4..f3fe6de0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -5,15 +5,20 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; +import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Rectangle; import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.Scenario; import oxy.bascenario.editor.containers.AOContainer; import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.TimelineTabContainer; +import oxy.bascenario.editor.containers.track.tab.TrackTabListContainer; import oxy.bascenario.utils.ExtendableScreen; import oxy.bascenario.utils.TimeUtils; import oxy.bascenario.utils.thingl.ThinGLUtils; @@ -29,6 +34,8 @@ public class ScenarioEditorScreen extends ExtendableScreen { private float scale = 1; private TimelineContainer timelineContainer; + private Container trackTabContainer; + public float scroll() { float width = ThinGL.windowInterface().getFramebufferWidth(); return timelineContainer().scroll(0.99f * width - 0.15625f * width); @@ -62,8 +69,13 @@ public void init(Rivet rivet) { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); - container.addChild(new SolidColor(), c -> { - c.color(Color.RED); + container.addChild(new TrackTabListContainer(trackTabContainer = new Container(new VerticalListLayout(3, false))) { + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); + super.render(renderer, bounds); + } + }, c -> { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.15625f).withAnchorMaxY(0.99f)); }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 50ce2716..e382f013 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -1,39 +1,34 @@ package oxy.bascenario.editor.containers.track; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.containers.TimelineContainer; @Accessors(fluent = true) public class TrackContainer extends Container { - private final TimelineContainer parent; + @Getter + private final TimelineContainer container; @Setter @Getter private int index; + @Getter private float height = 60f; - public TrackContainer(TimelineContainer parent) { + public TrackContainer(TimelineContainer container) { super(AbsoluteLayout.INSTANCE); - this.parent = parent; + this.container = container; } @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().shapeText("Track " + index, Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - renderer.fillRoundedRect(0, 0, bounds.width() + 100, bounds.height(), 5, Color.fromRGB(50, 50, 50)); - super.render(renderer, bounds); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 04dcfaf4..8f9581c8 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -11,6 +11,7 @@ import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; public class TrackListContainer extends ScrollContainer { private final TimelineContainer parent; @@ -42,6 +43,8 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < 60f) { final TrackContainer component = new TrackContainer(parent); container.addChild(component); + + parent.screen().trackTabContainer().addChild(new TrackTabContainer(component)); } return super.onComponentDrop(event, bounds); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java new file mode 100644 index 00000000..d958928a --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java @@ -0,0 +1,32 @@ +package oxy.bascenario.editor.containers.track.tab; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.Layout; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.containers.track.TrackContainer; + +public class TrackTabContainer extends Container { + private final TrackContainer container; + + public TrackTabContainer(TrackContainer container) { + super(AnchorLayout.INSTANCE); + this.container = container; + } + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().shapeText("Track " + container.index(), Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + + super.render(renderer, bounds); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), container.height()); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java new file mode 100644 index 00000000..cb1136ef --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -0,0 +1,26 @@ +package oxy.bascenario.editor.containers.track.tab; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.layout.border.BorderLayout; +import net.lenni0451.rivet.layout.border.BorderPosition; +import net.lenni0451.rivet.math.Size; + +public class TrackTabListContainer extends Container { + public TrackTabListContainer(Container container) { + super(BorderLayout.INSTANCE); + + // Just to take space. + this.addChild(new Component() { + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), 35); + } + }, c -> c.layoutOptions(BorderPosition.TOP)); + + this.addChild(new ScrollContainer(container), c -> c.layoutOptions(BorderPosition.CENTER)); + } +} From 2ea064fc5613a306697b058c078f55564f5c3fc5 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 5 Jun 2026 12:54:10 +0700 Subject: [PATCH 22/71] more work. --- .../editor/ScenarioEditorScreen.java | 13 +++++ .../editor/containers/TimelineContainer.java | 1 + .../containers/object/ObjectComponent.java | 34 ++++++++++++ .../containers/object/ObjectOrEvent.java | 13 +++++ .../containers/track/TrackContainer.java | 3 +- .../containers/track/TrackListContainer.java | 52 +++++++++++++++++-- 6 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectOrEvent.java diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index f3fe6de0..9c3b72b8 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -33,6 +33,14 @@ public class ScenarioEditorScreen extends ExtendableScreen { private long timestamp = 1000; private float scale = 1; + public void scale(float scale) { + this.scale = scale; + + float width = ThinGL.windowInterface().getFramebufferWidth(); + timelineContainer.trackListContainer().resize(0.99f * width - 0.15625f * width); + timelineContainer.trackListContainer().requestLayoutRecalculation(); + } + private TimelineContainer timelineContainer; private Container trackTabContainer; @@ -99,4 +107,9 @@ public void render(Renderer renderer, Rectangle bounds) { @Override public void render(float delta) { } + +// @Override +// public void resize(int width, int height) { +// timelineContainer.trackListContainer(). +// } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 9e5f2970..5c0c61d7 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -21,6 +21,7 @@ @Accessors(fluent = true) public class TimelineContainer extends Container { private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(202, 74, 92); + @Getter private final TrackListContainer trackListContainer; @Getter diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java new file mode 100644 index 00000000..a8f66639 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -0,0 +1,34 @@ +package oxy.bascenario.editor.containers.object; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.editor.containers.track.TrackListContainer; + +@Accessors(fluent = true) +@RequiredArgsConstructor +public class ObjectComponent extends Component { + private static final Color OBJECT_COLOR = Color.fromRGB(202, 74, 92).darker(); + + private final TrackListContainer parent; + @Getter + private final ObjectOrEvent object; + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(30, 30, 30)); + renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 2, OBJECT_COLOR); + } + + @Override + public Size computeIdealSize(Size constraints) { + final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); + return new Size((object.duration / maxTime) * constraints.width(), constraints.height()); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectOrEvent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectOrEvent.java new file mode 100644 index 00000000..8fe9a338 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectOrEvent.java @@ -0,0 +1,13 @@ +package oxy.bascenario.editor.containers.object; + +import lombok.AllArgsConstructor; +import oxy.bascenario.api.render.RenderLayer; + +@AllArgsConstructor +public class ObjectOrEvent { + public long start, duration; + + public Object object; + public RenderLayer layer; + public boolean requireWait; +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index e382f013..33be991f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -28,13 +28,14 @@ public TrackContainer(TimelineContainer container) { @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRoundedRect(0, 0, bounds.width() + 100, bounds.height(), 5, Color.fromRGB(50, 50, 50)); + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(50, 50, 50)); super.render(renderer, bounds); } @Override public Size computeIdealSize(Size constraints) { Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); + // give some extra scroll by default. return new Size(Math.max(idealSize.width(), constraints.width()), height); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 8f9581c8..93c886a0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -1,29 +1,47 @@ package oxy.bascenario.editor.containers.track; +import lombok.Getter; +import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.dragdrop.DropEvent; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.api.render.RenderLayer; import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.object.ObjectComponent; +import oxy.bascenario.editor.containers.object.ObjectOrEvent; import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; +@Accessors(fluent = true) public class TrackListContainer extends ScrollContainer { - private final TimelineContainer parent; + @Getter + private final TimelineContainer timelineContainer; private final Container container; - public TrackListContainer(TimelineContainer parent) { + public TrackListContainer(TimelineContainer timelineContainer) { super(container = new Container(new VerticalListLayout(3, false)), true, true); - this.parent = parent; + this.timelineContainer = timelineContainer; } + private float prevWidth; + @Override public void render(Renderer renderer, Rectangle bounds) { + if (prevWidth != bounds.width()) { + resize(bounds.width()); + this.requestLayoutRecalculation(); + } + + prevWidth = bounds.width(); + if (container.children().isEmpty()) { renderer.fillRoundedRect(0, 0, 300, 60f, 5, Color.fromRGB(50, 50, 50)); renderer.scale(0.4f, () -> { @@ -41,10 +59,12 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { return super.onComponentDrop(event, bounds); } if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < 60f) { - final TrackContainer component = new TrackContainer(parent); + final TrackContainer component = new TrackContainer(timelineContainer); container.addChild(component); - parent.screen().trackTabContainer().addChild(new TrackTabContainer(component)); + timelineContainer.screen().trackTabContainer().addChild(new TrackTabContainer(component)); + + add(bounds.width(), component, new ObjectOrEvent(9000, 1000, null, RenderLayer.TOP, true)); } return super.onComponentDrop(event, bounds); @@ -54,4 +74,26 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { public Size computeIdealSize(Size constraints) { return constraints; } + + private void add(float width, TrackContainer container, ObjectOrEvent object) { + float newX = TimelineContainer.timestampToPosition(object.start, 0, width, timelineContainer.screen().scale(), timelineContainer.screen().scroll()); + container.addChild(new ObjectComponent(this, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(newX, 0))); + } + + public void resize(float width) { + for (Component component : container.children()) { + if (!(component instanceof TrackContainer track)) { + continue; + } + + for (Component child : track.children()) { + if (!(child instanceof ObjectComponent object)) { + continue; + } + + float newX = TimelineContainer.timestampToPosition(object.object().start, 0, width, timelineContainer.screen().scale(), timelineContainer.screen().scroll()); + child.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); + } + } + } } From 98428491e1890089cba5a56a40ff916f2b912a3e Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sat, 6 Jun 2026 00:26:17 +0700 Subject: [PATCH 23/71] Fix scaling on a begg screen resolution for Rivet. --- .../src/main/java/oxy/bascenario/EngineRenderer.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index 88f282d5..fe586008 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -222,4 +222,16 @@ private float[] getMouseScale() { GLFW.glfwGetFramebufferSize(windowHandle, framebufferSizeX, framebufferSizeY); return new float[]{(float) framebufferSizeX[0] / windowSizeX[0], (float) framebufferSizeY[0] / windowSizeY[0]}; } + + @Override + public void resize(int width, int height) { + if (width <= 1920 && height <= 1080) { + this.rivet.scale(1); + } else { + float scale = (width + height) / (1920 + 1080f); + this.rivet.scale(scale); + } + + super.resize(width, height); + } } From 4db06fb848079e4401b36d9921839a1d281aad87 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 7 Jun 2026 16:56:52 +0700 Subject: [PATCH 24/71] More work, selection stuff. --- .../editor/ScenarioEditorScreen.java | 2 +- .../editor/containers/AOContainer.java | 27 ++- .../editor/containers/ao/tab/AoMediaTab.java | 12 ++ .../editor/containers/ao/tab/AoObjectTab.java | 61 +++++++ .../editor/containers/ao/tab/AoTextTab.java | 28 ++- .../containers/object/ObjectComponent.java | 20 ++- .../selection/SelectionManager.java | 62 +++++++ .../timeline/TimelineTimeSection.java | 4 +- .../containers/track/TrackContainer.java | 22 +++ .../containers/track/TrackListContainer.java | 51 +++++- .../java/oxy/bascenario/util/NameUtils.java | 89 ++++++++++ .../oxy/bascenario/util/TimeCompiler.java | 159 ++++++++++++++++++ .../util/components/TextWithName.java | 39 +++++ .../assets/base/uis/editor/emoticon.png | Bin 0 -> 25778 bytes .../assets/base/uis/editor/location-info.png | Bin 0 -> 6087 bytes .../assets/base/uis/editor/preview.png | Bin 0 -> 54774 bytes 16 files changed, 561 insertions(+), 15 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java create mode 100644 editor/src/main/java/oxy/bascenario/util/NameUtils.java create mode 100644 editor/src/main/java/oxy/bascenario/util/TimeCompiler.java create mode 100644 editor/src/main/java/oxy/bascenario/util/components/TextWithName.java create mode 100644 editor/src/main/resources/assets/base/uis/editor/emoticon.png create mode 100644 editor/src/main/resources/assets/base/uis/editor/location-info.png create mode 100644 editor/src/main/resources/assets/base/uis/editor/preview.png diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 9c3b72b8..8288de60 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -63,7 +63,7 @@ public void renderBehindRivet() { public void init(Rivet rivet) { Container container = new Container(AnchorLayout.INSTANCE); - container.addChild(new AOContainer(), c -> { + container.addChild(new AOContainer(this), c -> { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.06f).withAnchorMaxX(0.2f).withAnchorMaxY(0.6f)); }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index e015394c..8b21b4cb 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -1,27 +1,44 @@ package oxy.bascenario.editor.containers; -import lombok.Setter; import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.math.Rectangle; +import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.ao.AOTabContainer; +import oxy.bascenario.editor.containers.ao.tab.AoMediaTab; +import oxy.bascenario.editor.containers.ao.tab.AoObjectTab; import oxy.bascenario.editor.containers.ao.tab.AoTextTab; // Object & Actions container @Accessors(fluent = true) public class AOContainer extends Container { - @Setter - private Tab tab = Tab.Media; + private final ScenarioEditorScreen screen; + private Component currentTab; - public AOContainer() { + public void tab(Tab tab) { + this.removeChild(currentTab); + currentTab = switch (tab) { + case Text -> new AoTextTab(); + case Object -> new AoObjectTab(screen); + default -> null; + }; + + if (currentTab != null) { + this.addChild(currentTab, c -> c.layoutOptions(new AbsoluteLayoutOptions(10, 45))); + } + } + + public AOContainer(ScenarioEditorScreen screen) { super(AbsoluteLayout.INSTANCE); + this.screen = screen; this.addChild(new AOTabContainer(this), c -> c.layoutOptions(new AbsoluteLayoutOptions(0, 0))); - this.addChild(new AoTextTab(), c -> c.layoutOptions(new AbsoluteLayoutOptions(10, 45))); + this.addChild(new AoMediaTab()); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java new file mode 100644 index 00000000..dbf5149f --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java @@ -0,0 +1,12 @@ +package oxy.bascenario.editor.containers.ao.tab; + +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.layout.list.VerticalListLayout; + +public class AoMediaTab extends ScrollContainer { + public AoMediaTab() { + super(new Container(new VerticalListLayout(10, false))); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java new file mode 100644 index 00000000..4b63ba03 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -0,0 +1,61 @@ +package oxy.bascenario.editor.containers.ao.tab; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.component.container.Button; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Size; +import net.raphimc.thingl.ThinGL; +import oxy.bascenario.api.render.elements.LocationInfo; +import oxy.bascenario.api.render.elements.Preview; +import oxy.bascenario.api.render.elements.emoticon.Emoticon; +import oxy.bascenario.api.render.elements.emoticon.EmoticonType; +import oxy.bascenario.api.render.elements.text.font.FontType; +import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.util.TimeCompiler; +import oxy.bascenario.util.components.TextWithName; + +public class AoObjectTab extends ScrollContainer { + public AoObjectTab(ScenarioEditorScreen screen) { + final Container container = new Container(new VerticalListLayout(10, false)); + super(container); + + add(screen, container, "Preview", "assets/base/uis/editor/preview.png", new Preview(FontType.NotoSans, "Title", "Subtitle", null)); + add(screen, container, "Location Info", "assets/base/uis/editor/location-info.png", new LocationInfo(FontType.NotoSans, "Location A", 1000, 200)); + add(screen, container, "Emoticon", "assets/base/uis/editor/emoticon.png", new Emoticon(100, EmoticonType.ANGRY, true)); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), constraints.height() - 40); + } + + private void add(ScenarioEditorScreen screen, Container container, String name, String path, Object object) { + final TextWithName component = new TextWithName(name, path); + + long duration = TimeCompiler.compileTime(object); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } + float screenWidth = ThinGL.windowInterface().getFramebufferWidth(); + final float width = (0.99f * screenWidth - 0.15625f * screenWidth) * (duration / (ScenarioEditorScreen.DEFAULT_MAX_TIME * screen.scale())); + + component.mouseDownListener().add((ignored, bounds) -> { + Button ghost = new Button(new Label(""), _ -> {}); + ghost.interactive(false); + ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); + + rivet().dragAndDropManager().startDrag(object, + ghost, + new Size(width, 30), + 0, + -30 / 2f + ); + return true; + }); + + container.addChild(component); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java index 5d40cec9..fe013ae8 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java @@ -12,8 +12,13 @@ import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.api.render.elements.text.AnimatedText; +import oxy.bascenario.api.render.elements.text.Text; +import oxy.bascenario.api.render.elements.text.TextSegment; import oxy.bascenario.util.components.TypingLabel; +import java.util.List; + public class AoTextTab extends ScrollContainer { public AoTextTab() { final Container container = new Container(new VerticalListLayout(10, false)); @@ -25,19 +30,34 @@ public AoTextTab() { ghost.interactive(false); ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); - // Start the drag with some payload rivet().dragAndDropManager().startDrag( - "Default Text", + new Text(List.of(TextSegment.builder().text("Default Text").build()), 42), ghost, new Size(bounds.width(), bounds.height()), - -bounds.width() / 2f, + 0, -bounds.height() / 2f ); return true; }); container.addChild(defaultText); - container.addChild(new Button(new TypingLabel("Typing Text").scale(0.7f), ignored -> {})); + + final Button typingText = new Button(new TypingLabel("Typing Text").scale(0.7f), ignored -> {}); + typingText.mouseDownListener().add((ignored, bounds) -> { + Button ghost = new Button(new TypingLabel("Typing Text").scale(0.7f), _ -> {}); + ghost.interactive(false); + ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); + + rivet().dragAndDropManager().startDrag( + new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42), + ghost, + new Size(bounds.width(), bounds.height()), + 0, + -bounds.height() / 2f + ); + return true; + }); + container.addChild(typingText); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index a8f66639..4c88c6f5 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -6,13 +6,20 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Button; +import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.api.render.elements.text.Text; +import oxy.bascenario.api.render.elements.text.TextSegment; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.track.TrackListContainer; +import oxy.bascenario.util.NameUtils; + +import java.util.List; @Accessors(fluent = true) -@RequiredArgsConstructor public class ObjectComponent extends Component { private static final Color OBJECT_COLOR = Color.fromRGB(202, 74, 92).darker(); @@ -20,10 +27,19 @@ public class ObjectComponent extends Component { @Getter private final ObjectOrEvent object; + public ObjectComponent(TrackListContainer parent, ObjectOrEvent object) { + this.parent = parent; + this.object = object; + } + @Override public void render(Renderer renderer, Rectangle bounds) { renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(30, 30, 30)); - renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 2, OBJECT_COLOR); + renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); + + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + + this.parent.selectionManager().addIfIntersects(this, bounds); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java new file mode 100644 index 00000000..33b108a5 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -0,0 +1,62 @@ +package oxy.bascenario.editor.containers.selection; + +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Rectangle; +import oxy.bascenario.editor.containers.object.ObjectComponent; + +import java.util.HashSet; +import java.util.Set; + +@Accessors(fluent = true) +@Setter +public class SelectionManager { + private Set objects = new HashSet<>(); + public void reset() { + this.objects.clear(); + } + + public boolean isSelected(ObjectComponent component) { + return objects.contains(component); + } + + private float x, y; + private float x1, y1; + + private Rectangle range = Rectangle.EMPTY; + + public void render(Renderer renderer, Rectangle range) { + if (x == 0 && y == 0) { + return; + } + this.range = range; + + float minX = Math.min(x, x1), minY = Math.min(y, y1); + float maxX = Math.max(x, x1), maxY = Math.max(y, y1); + + renderer.fillRect(minX, minY, maxX - minX, maxY - minY, Color.WHITE.withAlphaF(0.4f)); + renderer.outlineRect(minX, minY, maxX - minX, maxY - minY, 2, Color.WHITE); + } + + public void addIfIntersects(ObjectComponent component, Rectangle rectangle) { + if (x == 0 && y == 0) { + return; + } + + float minX = Math.min(x, x1), minY = Math.min(y, y1); + float maxX = Math.max(x, x1), maxY = Math.max(y, y1); + + minX += this.range.x(); + maxX += this.range.x(); + + minY += this.range.y(); + maxY += this.range.y(); + + if (maxX >= rectangle.x() && minX <= rectangle.maxX() && maxY >= rectangle.y() && minY <= rectangle.maxY()) { + objects.add(component); + } + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index c7801d05..486722ff 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -38,7 +38,7 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { final float ratio = Math.min((event.x() - 1.25f) / (bounds.width()), 1); long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); - parent.screen().timestamp(newTimestamp); + parent.screen().timestamp(Math.max(0, newTimestamp)); } return false; @@ -49,7 +49,7 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { final float ratio = Math.min((event.x() - 1.25f) / (bounds.width()) , 1); long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); - parent.screen().timestamp(newTimestamp); + parent.screen().timestamp(Math.max(0, newTimestamp)); } return false; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 33be991f..0b1b44e7 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -6,10 +6,17 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.dragdrop.DropEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.api.render.RenderLayer; +import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.object.ObjectComponent; +import oxy.bascenario.editor.containers.object.ObjectOrEvent; +import oxy.bascenario.util.TimeCompiler; @Accessors(fluent = true) public class TrackContainer extends Container { @@ -32,6 +39,21 @@ public void render(Renderer renderer, Rectangle bounds) { super.render(renderer, bounds); } + @Override + protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { + long time = (long) ((ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale()) * (event.x() / bounds.width())); + + long duration = TimeCompiler.compileTime(event.dragData()); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } + + final ObjectOrEvent object = new ObjectOrEvent(time, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true); + this.addChild(new ObjectComponent(this.container.trackListContainer(), object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); + + return super.onComponentDrop(event, bounds); + } + @Override public Size computeIdealSize(Size constraints) { Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 93c886a0..139aa80d 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -9,6 +9,9 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.dragdrop.DropEvent; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Rectangle; @@ -18,7 +21,9 @@ import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.object.ObjectComponent; import oxy.bascenario.editor.containers.object.ObjectOrEvent; +import oxy.bascenario.editor.containers.selection.SelectionManager; import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; +import oxy.bascenario.util.TimeCompiler; @Accessors(fluent = true) public class TrackListContainer extends ScrollContainer { @@ -26,6 +31,9 @@ public class TrackListContainer extends ScrollContainer { private final TimelineContainer timelineContainer; private final Container container; + @Getter + private final SelectionManager selectionManager = new SelectionManager(); + public TrackListContainer(TimelineContainer timelineContainer) { super(container = new Container(new VerticalListLayout(3, false)), true, true); this.timelineContainer = timelineContainer; @@ -51,6 +59,8 @@ public void render(Renderer renderer, Rectangle bounds) { } super.render(renderer, bounds); + + this.selectionManager.render(renderer, bounds); } @Override @@ -64,7 +74,11 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { timelineContainer.screen().trackTabContainer().addChild(new TrackTabContainer(component)); - add(bounds.width(), component, new ObjectOrEvent(9000, 1000, null, RenderLayer.TOP, true)); + long duration = TimeCompiler.compileTime(event.dragData()); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } + add(bounds.width(), component, new ObjectOrEvent(0, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true)); } return super.onComponentDrop(event, bounds); @@ -96,4 +110,39 @@ public void resize(float width) { } } } + + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { + if (event.buttons().contains(MouseButton.LEFT)) { + this.selectionManager.x1(event.x()); + this.selectionManager.y1(event.y()); + } + + return super.onComponentMouseMove(event, bounds); + } + + @Override + protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { + if (event.button() == MouseButton.LEFT) { + this.selectionManager.x(event.x()); + this.selectionManager.y(event.y()); + + this.selectionManager.x1(event.x()); + this.selectionManager.y1(event.y()); + + this.selectionManager.reset(); + } + + return super.onComponentMouseDown(event, bounds); + } + + @Override + protected boolean onComponentMouseUp(MouseButtonEvent event, Rectangle bounds) { + if (event.button() == MouseButton.LEFT) { + this.selectionManager.x(0); + this.selectionManager.y(0); + } + + return super.onComponentMouseUp(event, bounds); + } } diff --git a/editor/src/main/java/oxy/bascenario/util/NameUtils.java b/editor/src/main/java/oxy/bascenario/util/NameUtils.java new file mode 100644 index 00000000..5a017d8a --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/util/NameUtils.java @@ -0,0 +1,89 @@ +package oxy.bascenario.util; + +import oxy.bascenario.api.event.*; +import oxy.bascenario.api.event.animation.PlayAnimationEvent; +import oxy.bascenario.api.event.animation.SpriteAnimationEvent; +import oxy.bascenario.api.event.animation.StopAnimationEvent; +import oxy.bascenario.api.event.background.ClearBackgroundEvent; +import oxy.bascenario.api.event.background.SetBackgroundEvent; +import oxy.bascenario.api.event.color.ColorOverlayEvent; +import oxy.bascenario.api.event.color.SetColorEvent; +import oxy.bascenario.api.event.dialogue.*; +import oxy.bascenario.api.event.element.ClearLogEvent; +import oxy.bascenario.api.event.element.ElementEffectEvent; +import oxy.bascenario.api.event.element.focus.FocusElementEvent; +import oxy.bascenario.api.event.element.focus.UnfocusElementEvent; +import oxy.bascenario.api.event.element.values.PositionElementEvent; +import oxy.bascenario.api.event.element.values.RotateElementEvent; +import oxy.bascenario.api.event.log.AddLogEvent; +import oxy.bascenario.api.event.sound.SoundVolumeEvent; +import oxy.bascenario.api.render.elements.LocationInfo; +import oxy.bascenario.api.render.elements.image.AnimatedImage; +import oxy.bascenario.api.render.elements.text.AnimatedText; + +public class NameUtils { + public static String name(Object object) { + return switch (object) { + // Events part. + case PlayAnimationEvent ignored -> "Play Animation"; + case SpriteAnimationEvent ignored -> "Play Sprite Animation"; + case StopAnimationEvent ignored -> "Stop Animation"; + + case StartDialogueEvent ignored -> "Start Dialogue"; + case AddDialogueEvent ignored -> "Add Dialogue"; + case ShowOptionsEvent ignored -> "Show Options"; +// case ShowQuestionSelectionEvent ignored -> "Show Question And Answers"; + + case CloseDialogueEvent ignored -> "Close Dialogue"; + case CloseOptionsEvent ignored -> "Close Options"; + + case PositionElementEvent event -> switch (event.type()) { + case POSITION -> "Move Object"; + case OFFSET -> "Offset Object"; + case SCALE -> "Scale Object"; + case PIVOT -> "Pivot Object"; + }; + case RotateElementEvent ignored -> "Rotate Object"; + + case ElementEffectEvent event -> switch (event.type()) { + case ADD -> "Apply Effect"; + case REMOVE -> "Remove Effect"; + }; + + case ColorOverlayEvent event -> { + if (event.id().isPresent()) { + yield "Color Overlay"; + } else { + yield "Screen Color"; + } + } + case SetColorEvent ignored -> "Set Color"; + + case SetBackgroundEvent ignored -> "Set Background"; + case ClearBackgroundEvent ignored -> "Clear Background"; + + case SoundVolumeEvent ignored -> "Sound Volume"; + case LockClickEvent ignored -> "Lock Click"; + + case LocationInfo ignored -> "Location Info"; + case AnimatedText ignored -> "Typing Text"; + +// case SoundAsElement ignored -> "Sound"; + case AnimatedImage ignored -> "Gif"; + + case ShowButtonsEvent ignored -> "Show Buttons"; + case FocusElementEvent ignored -> "Focus Object"; + case UnfocusElementEvent ignored -> "Un-focus Object"; + + case SetWeatherEvent ignored -> "Set Weather"; + case PopupEvent ignored -> "Image Popup"; + case ScreenTransitionEvent ignored -> "Screen Transition"; + case ScreenEffectEvent ignored -> "Screen Effect"; + + case AddLogEvent ignored -> "Add Log"; + case ClearLogEvent ignored -> "Clear Log"; + + default -> object.getClass().getSimpleName(); + }; + } +} \ No newline at end of file diff --git a/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java b/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java new file mode 100644 index 00000000..d9115b46 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java @@ -0,0 +1,159 @@ +package oxy.bascenario.util; + +import oxy.bascenario.api.event.ScreenTransitionEvent; +import oxy.bascenario.api.event.background.ClearBackgroundEvent; +import oxy.bascenario.api.event.background.SetBackgroundEvent; +import oxy.bascenario.api.event.color.ColorOverlayEvent; +import oxy.bascenario.api.event.api.Event; +import oxy.bascenario.api.event.color.SetColorEvent; +import oxy.bascenario.api.event.dialogue.AddDialogueEvent; +import oxy.bascenario.api.event.dialogue.ShowOptionsEvent; +import oxy.bascenario.api.event.dialogue.StartDialogueEvent; +import oxy.bascenario.api.event.element.values.PositionElementEvent; +import oxy.bascenario.api.event.element.values.RotateElementEvent; +import oxy.bascenario.api.event.log.AddLogEvent; +import oxy.bascenario.api.event.sound.PlaySoundEvent; +import oxy.bascenario.api.event.sound.SoundVolumeEvent; +import oxy.bascenario.api.event.sound.StopSoundEvent; +import oxy.bascenario.api.render.elements.*; +import oxy.bascenario.api.render.elements.emoticon.Emoticon; +import oxy.bascenario.api.render.elements.text.TextSegment; + +public class TimeCompiler { + public static Object addTime(final Object object, int duration) { + if (compileTime(object) == Long.MAX_VALUE) { + return object; + } + + return switch (object) { + case LocationInfo info -> { + LocationInfo.Builder builder = info.toBuilder(); + builder.duration(info.duration() + duration); + yield builder.build(); + } + case Emoticon emoticon -> { + Emoticon.Builder builder = emoticon.toBuilder(); + builder.duration(emoticon.duration() + duration); + yield builder.build(); + } + case PositionElementEvent event -> { + PositionElementEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case RotateElementEvent event -> { + RotateElementEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case PlaySoundEvent event -> { + PlaySoundEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case SoundVolumeEvent event -> { + SoundVolumeEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case StopSoundEvent event -> { + StopSoundEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case ColorOverlayEvent event -> { + ColorOverlayEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case SetColorEvent event -> { + SetColorEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case SetBackgroundEvent event -> { + SetBackgroundEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case ClearBackgroundEvent event -> { + ClearBackgroundEvent.Builder builder = event.toBuilder(); + builder.duration(event.duration() + duration); + yield builder.build(); + } + case ScreenTransitionEvent event -> { + ScreenTransitionEvent.Builder builder = event.toBuilder(); + builder.waitDuration(event.waitDuration() + duration); + yield builder.build(); + } + default -> object; + }; + } + + public static boolean canResize(final Object object) { + return compileTime(object) == Long.MAX_VALUE || object instanceof LocationInfo || object instanceof Emoticon || + object instanceof PositionElementEvent || object instanceof RotateElementEvent || object instanceof ScreenTransitionEvent || + object instanceof PlaySoundEvent || object instanceof SoundVolumeEvent || object instanceof StopSoundEvent || + object instanceof ColorOverlayEvent || object instanceof SetColorEvent || object instanceof ClearBackgroundEvent || object instanceof SetBackgroundEvent; + } + + public static long compileTime(final Object object) { + return switch (object) { + case Preview ignored -> 3900; + case LocationInfo info -> info.duration() + info.fade(); + case Emoticon emoticon -> switch (emoticon.type()) { + case NOTE -> 2600 + emoticon.duration(); + case RESPOND, ANGRY, SWEAT, CHAT, EXCLAMATION_MARK, HEART, SHY, SURPRISED, QUESTION_MARK, ANXIETY, IDEA -> 800 + emoticon.duration(); + case TWINKLE -> emoticon.duration(); + case THINKING, HESITATED -> 600L * 3 + (600L / 3); + case SIGH -> 350 + 500L + emoticon.duration(); + }; + + case StartDialogueEvent event -> compileTime(event.dialogues()); + case AddDialogueEvent event -> compileTime(event.dialogues()); + case AddLogEvent event -> compileTime(event.dialogues()); + + case Dialogue[] dialogues -> compileTime(dialogues); + case Dialogue dialogue -> compileTime(dialogue); + case Event event -> compileTime(event); + default -> Long.MAX_VALUE; + }; + } + + private static long compileTime(final Event object) { + return switch (object) { + case PositionElementEvent event -> Math.max(100, event.duration()); + case RotateElementEvent event -> Math.max(100, event.duration()); + case ShowOptionsEvent ignored -> 250L; // eh. +// case ShowQuestionSelectionEvent ignored -> 400L; + case PlaySoundEvent event -> Math.max(100, event.duration()); + case SoundVolumeEvent event -> Math.max(100, event.duration()); + case StopSoundEvent event -> Math.max(100, event.duration()); + case ColorOverlayEvent event -> Math.max(100, event.duration()); + case SetColorEvent event -> Math.max(100, event.duration()); + case ClearBackgroundEvent event -> Math.max(100, event.duration()); + case SetBackgroundEvent event -> Math.max(100, event.duration()); + case ScreenTransitionEvent event -> Math.max(100, event.inDuration() + event.outDuration() + event.waitDuration()); + + default -> 100L; // has to be something for it to show up in the editor. + }; + } + + private static long compileTime(Dialogue[] dialogues) { + long duration = 0; + for (Dialogue dialogue : dialogues) { + duration += compileTime(dialogue); + } + return duration; + } + + private static long compileTime(Dialogue dialogue) { + final long msPerWord = (long) (Dialogue.MS_PER_WORD * (1 / dialogue.getPlaySpeed()) * 1); + + long duration = 0; + for (TextSegment segment : dialogue.getDialogue().segments()) { + duration += msPerWord * segment.text().replace("\n", "").length(); + } + return duration; + } +} \ No newline at end of file diff --git a/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java b/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java new file mode 100644 index 00000000..6f0bbb11 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java @@ -0,0 +1,39 @@ +package oxy.bascenario.util.components; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.Texture; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.backend.thingl.ThinGLTexture; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.Base; + +public class TextWithName extends Component { + private final String name; + private final Texture texture; + + public TextWithName(String name, String path) { + this.name = name; + this.texture = new ThinGLTexture(Base.instance().assetsManager().texture(path)); + } + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(25, 25, 25)); + renderer.image(this.texture, 10, 10, 255, 141, Color.WHITE); + + ShapedText text = this.rivet().backend().shapeText(name, Color.WHITE); + renderer.scale(0.4f, () -> + renderer.text(text, + (277 / 2f - text.visualBounds().width() * .4f / 2f) / .4f, 160 / .4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP + )); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(255 + 20, 141 + 40); + } +} diff --git a/editor/src/main/resources/assets/base/uis/editor/emoticon.png b/editor/src/main/resources/assets/base/uis/editor/emoticon.png new file mode 100644 index 0000000000000000000000000000000000000000..b9b6f5bf630d455f7a86b341ed0c121ff8d521c8 GIT binary patch literal 25778 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

W1V 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 Lw7pJ2`lS93g6{04 literal 0 HcmV?d00001 From 91845e06abee58a6013aa0f9d6a3acb8332a98de Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 7 Jun 2026 17:02:38 +0700 Subject: [PATCH 25/71] more. --- .../containers/object/ObjectComponent.java | 19 ++++++++++++------- .../selection/SelectionManager.java | 6 ++---- .../containers/track/TrackListContainer.java | 9 +++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 4c88c6f5..60043c7f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -1,24 +1,19 @@ package oxy.bascenario.editor.containers.object; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Button; -import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.input.keyboard.ModifierKey; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.api.render.elements.text.Text; -import oxy.bascenario.api.render.elements.text.TextSegment; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.track.TrackListContainer; import oxy.bascenario.util.NameUtils; -import java.util.List; - @Accessors(fluent = true) public class ObjectComponent extends Component { private static final Color OBJECT_COLOR = Color.fromRGB(202, 74, 92).darker(); @@ -42,6 +37,16 @@ public void render(Renderer renderer, Rectangle bounds) { this.parent.selectionManager().addIfIntersects(this, bounds); } + @Override + protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { + if (!event.modifiers().contains(ModifierKey.CONTROL)) { + this.parent.selectionManager().objects().clear(); + } + + this.parent.selectionManager().objects().add(this); + return true; + } + @Override public Size computeIdealSize(Size constraints) { final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java index 33b108a5..90556da0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -1,5 +1,6 @@ package oxy.bascenario.editor.containers.selection; +import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; @@ -14,11 +15,8 @@ @Accessors(fluent = true) @Setter public class SelectionManager { + @Getter private Set objects = new HashSet<>(); - public void reset() { - this.objects.clear(); - } - public boolean isSelected(ObjectComponent component) { return objects.contains(component); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 139aa80d..b405418e 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -123,17 +123,18 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { - if (event.button() == MouseButton.LEFT) { + boolean onComponent = super.onComponentMouseDown(event, bounds); + + if (!onComponent && event.button() == MouseButton.LEFT) { this.selectionManager.x(event.x()); this.selectionManager.y(event.y()); this.selectionManager.x1(event.x()); this.selectionManager.y1(event.y()); - this.selectionManager.reset(); + this.selectionManager.objects().clear(); } - - return super.onComponentMouseDown(event, bounds); + return onComponent; } @Override From ef85150b02ab9e47fa4802de8319c0c5954e5235 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 7 Jun 2026 19:21:34 +0700 Subject: [PATCH 26/71] Update ThinGL & Rivet. --- .../java/oxy/bascenario/EngineRenderer.java | 17 +++++++---- .../renderer/dialogue/DialogueRenderer.java | 16 +++++----- .../element/thingl/text/TextRenderer.java | 5 ++-- .../oxy/bascenario/utils/font/FontUtils.java | 29 +++++++++++-------- .../oxy/bascenario/utils/font/TextUtils.java | 28 ++++++++++-------- .../utils/thingl/ProgramsExtended.java | 11 ------- .../containers/object/ObjectComponent.java | 2 +- .../timeline/TimelineTimeSection.java | 2 +- .../containers/track/TrackListContainer.java | 4 +-- .../track/tab/TrackTabContainer.java | 2 +- .../util/components/TextWithName.java | 2 +- .../util/components/TypingLabel.java | 4 +-- gradle/libs.versions.toml | 4 +-- 13 files changed, 64 insertions(+), 62 deletions(-) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index fe586008..9e2252e8 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -8,9 +8,11 @@ import com.badlogic.gdx.utils.ScreenUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import net.lenni0451.commons.collections.Maps; import net.lenni0451.rivet.Rivet; import net.lenni0451.rivet.backend.thingl.ThinGLBackend; import net.lenni0451.rivet.backend.thingl.render.ThinGLRenderer; +import net.lenni0451.rivet.backend.thingl.text.ThinGLFont; import net.lenni0451.rivet.backend.thingl.util.GLFWMapper; import net.lenni0451.rivet.input.keyboard.CharEvent; import net.lenni0451.rivet.input.keyboard.KeyEvent; @@ -18,12 +20,14 @@ import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.input.mouse.MouseScrollEvent; -import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.fullsize.FullSizeLayout; import net.lenni0451.rivet.math.Size; import net.raphimc.thingl.ThinGL; -import net.raphimc.thingl.resource.font.impl.FreeTypeFont; -import net.raphimc.thingl.text.font.FontSet; +import net.raphimc.thingl.resource.font.face.impl.FreeTypeFontFace; +import net.raphimc.thingl.resource.font.instance.FontInstance; +import net.raphimc.thingl.resource.font.instance.FontInstanceSet; +import net.raphimc.thingl.resource.font.instance.impl.FreeTypeFontInstance; +import net.raphimc.thingl.text.util.GlyphPredicate; import org.joml.Matrix4fStack; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWFramebufferSizeCallback; @@ -100,12 +104,13 @@ public void create() { } }); - setupRivetCallbacks(); + FontUtils.loadFonts(); - this.backend = new ThinGLBackend(windowHandle, new FontSet(new FreeTypeFont(EngineRenderer.class.getResourceAsStream("/assets/base/fonts/rivet/SFUIText-Regular.ttf").readAllBytes(), 40))); + final FontInstance rivetFont = FontUtils.font("SFUIRegular"); + this.backend = new ThinGLBackend(windowHandle, new ThinGLFont(new FontInstanceSet(Maps.linkedHashMap(rivetFont, GlyphPredicate.all())))); this.rivet = new Rivet(this.backend, FullSizeLayout.INSTANCE, new Size(ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight())); - FontUtils.loadFonts(); + setupRivetCallbacks(); this.setScreen(this.initialScreen); diff --git a/core/src/main/java/oxy/bascenario/screens/renderer/dialogue/DialogueRenderer.java b/core/src/main/java/oxy/bascenario/screens/renderer/dialogue/DialogueRenderer.java index ab0852fc..b28a033c 100644 --- a/core/src/main/java/oxy/bascenario/screens/renderer/dialogue/DialogueRenderer.java +++ b/core/src/main/java/oxy/bascenario/screens/renderer/dialogue/DialogueRenderer.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import net.lenni0451.commons.color.Color; import net.raphimc.thingl.gl.renderer.impl.RendererText; -import net.raphimc.thingl.resource.font.Font; +import net.raphimc.thingl.resource.font.instance.FontInstance; import net.raphimc.thingl.text.TextLine; import net.raphimc.thingl.text.TextRun; import oxy.bascenario.api.Scenario; @@ -46,7 +46,7 @@ public boolean add(int index, boolean newLine, Dialogue... dialogues) { final Text text = dialogue.getDialogue(); for (final TextSegment segment : text.segments()) { - Font font = FontUtils.toFont(scenario, segment); + FontInstance font = FontUtils.toFont(scenario, segment); for (char c : segment.text().toCharArray()) { final String next = texts.get(texts.size() - 1).builder().toString() + c; @@ -83,13 +83,13 @@ public boolean add(int index, boolean newLine, Dialogue... dialogues) { return true; } - private record TextBuilder(float offset, StringBuilder builder, List> segments) { + private record TextBuilder(float offset, StringBuilder builder, List> segments) { } @RequiredArgsConstructor private static final class DialogueText { private final float offset; - private final List> allSegments; + private final List> allSegments; private final float speed; private final long time; private final long prev; @@ -137,7 +137,7 @@ public void renderDialogues() { if (lines.isEmpty() || text.newLine()) { break; } else { - for (Pair pair : text.allSegments) { + for (Pair pair : text.allSegments) { lines.getLast().allSegments.add(new TextRun(pair.right(), pair.left())); } } @@ -155,7 +155,7 @@ public void renderDialogues() { final List segments = new ArrayList<>(); int length = -1; - for (Pair pair : text.allSegments) { + for (Pair pair : text.allSegments) { if (length == words) { break; } @@ -167,7 +167,7 @@ public void renderDialogues() { } else { final TextRun last = segments.getLast(); // Probably not the best idea to do this... But who cares! - boolean same = pair.right().getFamilyName().equals(last.font().getFamilyName()) && pair.right().getSubFamilyName().equals(last.font().getSubFamilyName()); + boolean same = pair.right().getFace().getFamilyName().equals(last.font().getFace().getFamilyName()) && pair.right().getFace().getSubFamilyName().equals(last.font().getFace().getSubFamilyName()); if (same) { last.add(pair.left()); } else { @@ -177,7 +177,7 @@ public void renderDialogues() { } final List all = new ArrayList<>(); - for (Pair pair : text.allSegments) { + for (Pair pair : text.allSegments) { all.add(new TextRun(pair.right(), pair.left())); } diff --git a/core/src/main/java/oxy/bascenario/screens/renderer/element/thingl/text/TextRenderer.java b/core/src/main/java/oxy/bascenario/screens/renderer/element/thingl/text/TextRenderer.java index 1139c1fd..87fad83b 100644 --- a/core/src/main/java/oxy/bascenario/screens/renderer/element/thingl/text/TextRenderer.java +++ b/core/src/main/java/oxy/bascenario/screens/renderer/element/thingl/text/TextRenderer.java @@ -3,13 +3,12 @@ import net.lenni0451.commons.color.Color; import net.raphimc.thingl.ThinGL; import net.raphimc.thingl.gl.renderer.impl.RendererText; -import net.raphimc.thingl.resource.font.Font; +import net.raphimc.thingl.resource.font.instance.FontInstance; import net.raphimc.thingl.text.TextLine; import net.raphimc.thingl.text.TextRun; import net.raphimc.thingl.text.TextSegment; import oxy.bascenario.api.Scenario; import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.api.render.elements.text.AnimatedText; import oxy.bascenario.api.render.elements.text.Text; import oxy.bascenario.screens.renderer.element.base.ThinGLElementRenderer; import oxy.bascenario.utils.font.FontUtils; @@ -28,7 +27,7 @@ public TextRenderer(Text element, RenderLayer layer, Scenario scenario) { size = element.size(); for (oxy.bascenario.api.render.elements.text.TextSegment segment : element.segments()) { - Font font = FontUtils.toFont(scenario, segment); + FontInstance font = FontUtils.toFont(scenario, segment); line.add(new TextRun(font, new TextSegment(segment.text(), segment.color(), segment.toFlags(), segment.outline().isPresent() ? segment.outline().get() : Color.TRANSPARENT))); } } diff --git a/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java b/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java index e7d83bca..e1e5d3de 100644 --- a/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java +++ b/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java @@ -2,8 +2,9 @@ import java.util.*; -import net.raphimc.thingl.resource.font.Font; -import net.raphimc.thingl.resource.font.impl.FreeTypeFont; +import net.raphimc.thingl.resource.font.face.impl.FreeTypeFontFace; +import net.raphimc.thingl.resource.font.instance.FontInstance; +import net.raphimc.thingl.resource.font.instance.impl.FreeTypeFontInstance; import oxy.bascenario.Base; import oxy.bascenario.api.Scenario; import oxy.bascenario.api.render.elements.text.font.FontStyle; @@ -12,19 +13,19 @@ import oxy.bascenario.api.utils.FileInfo; public class FontUtils { - public static Font DEFAULT, SEMI_BOLD; - private static final Map NAME_TO_FONTS = new HashMap<>(); + public static FontInstance DEFAULT, SEMI_BOLD; + private static final Map NAME_TO_FONTS = new HashMap<>(); - public static Font font(FontStyle style, FontType type) { + public static FontInstance font(FontStyle style, FontType type) { return NAME_TO_FONTS.get(type.toName(style)); } - public static Font font(String name) { + public static FontInstance font(String name) { return NAME_TO_FONTS.get(name); } - public static Font toFont(Scenario scenario, TextSegment segment) { - Font font; + public static FontInstance toFont(Scenario scenario, TextSegment segment) { + FontInstance font; if (segment.font().file() != null) { font = NAME_TO_FONTS.get(String.valueOf(segment.font().file().hashCode(scenario.getName()))); if (font == null) { @@ -39,7 +40,7 @@ public static Font toFont(Scenario scenario, TextSegment segment) { public static void loadFonts() { // Editor UI - loadFont("SFUIRegular", "/assets/base/fonts/rivet/SFUIText-Regular.ttf"); + loadFont("SFUIRegular", "/assets/base/fonts/rivet/SFUIText-Regular.ttf", 40); // Global loadFont("NotoSansRegular", "/assets/base/fonts/global/NotoSans-Regular.ttf"); @@ -70,15 +71,19 @@ public static void loadFonts() { SEMI_BOLD = NAME_TO_FONTS.get("ChillRoundSemiBold"); } - public static Font loadSpecificFont(Scenario scenario, FileInfo font) { + public static FontInstance loadSpecificFont(Scenario scenario, FileInfo font) { final byte[] fontData = (byte[]) Base.instance().assetsManager().assets(scenario.getName(), font).asset(); - return new FreeTypeFont(fontData, 65); + return new FreeTypeFontInstance(new FreeTypeFontFace(fontData), 65); } private static void loadFont(String name, String font) { + loadFont(name, font, 65); + } + + private static void loadFont(String name, String font, int size) { try { final byte[] fontData = FontUtils.class.getResourceAsStream(font).readAllBytes(); - NAME_TO_FONTS.put(name, new FreeTypeFont(fontData, 65)); + NAME_TO_FONTS.put(name, new FreeTypeFontInstance(new FreeTypeFontFace(fontData), size)); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/core/src/main/java/oxy/bascenario/utils/font/TextUtils.java b/core/src/main/java/oxy/bascenario/utils/font/TextUtils.java index a4b3b16d..f8eb3d2f 100644 --- a/core/src/main/java/oxy/bascenario/utils/font/TextUtils.java +++ b/core/src/main/java/oxy/bascenario/utils/font/TextUtils.java @@ -35,26 +35,30 @@ public static float getLogicalHeight(float size, final ShapedTextRun textRun) { } public static void textRun(float size, final TextRun run, float x, float y) { - ThinGL.rendererText().pushGlobalScale(size / 65f); - ThinGL.rendererText().textRun(GLOBAL_RENDER_STACK, run, x, y); - ThinGL.rendererText().popGlobalScale(); + GLOBAL_RENDER_STACK.pushMatrix(); + GLOBAL_RENDER_STACK.scale(size / 65f); + ThinGL.rendererText().textRun(GLOBAL_RENDER_STACK, run, x / (size / 65f), y / (size / 65f)); + GLOBAL_RENDER_STACK.popMatrix(); } public static void textRun(float size, final TextRun run, float x, float y, RendererText.VerticalOrigin verticalOrigin, RendererText.HorizontalOrigin horizontalOrigin) { - ThinGL.rendererText().pushGlobalScale(size / 65f); - ThinGL.rendererText().textRun(GLOBAL_RENDER_STACK, run, x, y, verticalOrigin, horizontalOrigin); - ThinGL.rendererText().popGlobalScale(); + GLOBAL_RENDER_STACK.pushMatrix(); + GLOBAL_RENDER_STACK.scale(size / 65f); + ThinGL.rendererText().textRun(GLOBAL_RENDER_STACK, run, x / (size / 65f), y / (size / 65f), verticalOrigin, horizontalOrigin); + GLOBAL_RENDER_STACK.popMatrix(); } public static void textLine(float size, final TextLine textLine, final float x, final float y) { - ThinGL.rendererText().pushGlobalScale(size / 65f); - ThinGL.rendererText().textLine(GLOBAL_RENDER_STACK, textLine.shape(), x, y); - ThinGL.rendererText().popGlobalScale(); + GLOBAL_RENDER_STACK.pushMatrix(); + GLOBAL_RENDER_STACK.scale(size / 65f); + ThinGL.rendererText().textLine(GLOBAL_RENDER_STACK, textLine.shape(), x / (size / 65f), y / (size / 65f)); + GLOBAL_RENDER_STACK.popMatrix(); } public static void textLine(float size, final TextLine textLine, final float x, final float y, final RendererText.VerticalOrigin verticalOrigin, final RendererText.HorizontalOrigin horizontalOrigin) { - ThinGL.rendererText().pushGlobalScale(size / 65f); - ThinGL.rendererText().textLine(GLOBAL_RENDER_STACK, textLine.shape(), x, y, verticalOrigin, horizontalOrigin); - ThinGL.rendererText().popGlobalScale(); + GLOBAL_RENDER_STACK.pushMatrix(); + GLOBAL_RENDER_STACK.scale(size / 65f); + ThinGL.rendererText().textLine(GLOBAL_RENDER_STACK, textLine.shape(), x / (size / 65f), y / (size / 65f), verticalOrigin, horizontalOrigin); + GLOBAL_RENDER_STACK.popMatrix(); } } diff --git a/core/src/main/java/oxy/bascenario/utils/thingl/ProgramsExtended.java b/core/src/main/java/oxy/bascenario/utils/thingl/ProgramsExtended.java index c90b9458..046de653 100644 --- a/core/src/main/java/oxy/bascenario/utils/thingl/ProgramsExtended.java +++ b/core/src/main/java/oxy/bascenario/utils/thingl/ProgramsExtended.java @@ -22,12 +22,6 @@ public class ProgramsExtended extends Programs { private final Map customShaders = new HashMap<>(); - private final Lazy sdfText = Lazy.of(() -> { - final RegularProgram program = new RegularProgram(this.shaderLoader.get("regular/sdf_text", VERTEX), this.getBaseShader("sdf_text", FRAGMENT)); - program.setDebugName("sdf_text"); - return program; - }); - private final Lazy grayscale = Lazy.of(() -> { final ColorProgram program = new ColorProgram(this.shaderLoader.get("post/post_processing", VERTEX), this.getBaseShader("black_and_white", FRAGMENT)); program.setDebugName("black_and_white"); @@ -40,11 +34,6 @@ public class ProgramsExtended extends Programs { return program; }); - @Override - public RegularProgram getSdfText() { - return sdfText.get(); - } - public ColorProgram getGrayscale() { return grayscale.get(); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 60043c7f..3f2691ab 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -32,7 +32,7 @@ public void render(Renderer renderer, Rectangle bounds) { renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(30, 30, 30)); renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); - renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); this.parent.selectionManager().addIfIntersects(this, bounds); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index 486722ff..876d4592 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -26,7 +26,7 @@ public void render(Renderer renderer, Rectangle bounds) { float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), parent.screen().scale(), parent.screen().scroll()); float seconds = time / 1000f; - ShapedText text = this.rivet().backend().shapeText(seconds + "s", Color.WHITE); + ShapedText text = this.rivet().backend().font().shapeText(seconds + "s", Color.WHITE); renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index b405418e..6b495b8f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -53,8 +53,8 @@ public void render(Renderer renderer, Rectangle bounds) { if (container.children().isEmpty()) { renderer.fillRoundedRect(0, 0, 300, 60f, 5, Color.fromRGB(50, 50, 50)); renderer.scale(0.4f, () -> { - ShapedText text = this.rivet().backend().shapeText("Drop anything here to get started.", Color.WHITE); - renderer.text(text, 50,(60f / 2f - (this.rivet().backend().getTextHeight() * 0.4f) / 2f) / 0.4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP); + ShapedText text = this.rivet().backend().font().shapeText("Drop anything here to get started.", Color.WHITE); + renderer.text(text, 50,(60f / 2f - (this.rivet().backend().font().height() * 0.4f) / 2f) / 0.4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP); }); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java index d958928a..d9726c28 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java @@ -20,7 +20,7 @@ public TrackTabContainer(TrackContainer container) { @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().shapeText("Track " + container.index(), Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().font().shapeText("Track " + container.index(), Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); super.render(renderer, bounds); } diff --git a/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java b/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java index 6f0bbb11..cb9680a6 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java +++ b/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java @@ -25,7 +25,7 @@ public void render(Renderer renderer, Rectangle bounds) { renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(25, 25, 25)); renderer.image(this.texture, 10, 10, 255, 141, Color.WHITE); - ShapedText text = this.rivet().backend().shapeText(name, Color.WHITE); + ShapedText text = this.rivet().backend().font().shapeText(name, Color.WHITE); renderer.scale(0.4f, () -> renderer.text(text, (277 / 2f - text.visualBounds().width() * .4f / 2f) / .4f, 160 / .4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP diff --git a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java b/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java index 33146d2d..751889cf 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java +++ b/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java @@ -55,7 +55,7 @@ public TypingLabel scale(final float scale) { private void shapeText() { if (this.reshape) { - this.shapedText = this.rivet().backend().shapeText(this.text, this.rivet().theme().get(Theme.TEXT_COLOR)); + this.shapedText = this.rivet().backend().font().shapeText(this.text, this.rivet().theme().get(Theme.TEXT_COLOR)); this.reshape = false; } } @@ -77,7 +77,7 @@ public void render(final Renderer renderer, final Rectangle bounds) { float y = this.verticalOrigin.offset(bounds.height() / this.scale); float progress = (System.currentTimeMillis() % 3000) / 3000f; - net.lenni0451.rivet.backend.text.ShapedText text1 = this.rivet().backend().shapeText(this.text.substring(0, MathUtils.ceilInt(progress * this.text.length())), this.rivet().theme().get(Theme.TEXT_COLOR)); + net.lenni0451.rivet.backend.text.ShapedText text1 = this.rivet().backend().font().shapeText(this.text.substring(0, MathUtils.ceilInt(progress * this.text.length())), this.rivet().theme().get(Theme.TEXT_COLOR)); renderer.scale(this.scale, () -> renderer.text(text1, x, y, this.horizontalOrigin, this.verticalOrigin)); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 821297cf..7cbe6e76 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ lwjgl = "3.4.0" lwjgl-sdl = "3.4.0" libgdx = "1.13.1" spine = "4.2.5" -thingl = "0.1.1-SNAPSHOT" +thingl = "0.2.0-SNAPSHOT" commons = "1.9.2" mocha = "3.0.1" configurate = "4.2.0" @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "8d7bc03154" +rivet = "1d187fab05" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } From 8bacd889c51472ec2d502e92ec2de13608f64b5b Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Mon, 8 Jun 2026 06:27:07 +0700 Subject: [PATCH 27/71] more work (broken). --- .../editor/ScenarioEditorScreen.java | 15 ++++++------- .../editor/containers/TimelineContainer.java | 22 ++++++++++++++----- .../containers/object/ObjectComponent.java | 7 +++++- .../timeline/TimelineTimeSection.java | 2 +- .../containers/track/TrackContainer.java | 2 +- .../containers/track/TrackListContainer.java | 7 ++++-- .../track/tab/TrackTabListContainer.java | 2 -- .../java/oxy/bascenario/util/NameUtils.java | 2 +- .../oxy/bascenario/util/TimeCompiler.java | 3 ++- 9 files changed, 40 insertions(+), 22 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 8288de60..eb2d1a05 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -7,8 +7,8 @@ import net.lenni0451.rivet.Rivet; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.component.impl.SolidColor; +import net.lenni0451.rivet.input.keyboard.KeyEvent; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; @@ -23,11 +23,13 @@ import oxy.bascenario.utils.TimeUtils; import oxy.bascenario.utils.thingl.ThinGLUtils; +import java.util.function.Predicate; + @Accessors(fluent = true) @Getter @Setter public class ScenarioEditorScreen extends ExtendableScreen { - private final Scenario scenario; + private final Scenario.Builder scenario; public static final long DEFAULT_MAX_TIME = 15000; private long timestamp = 1000; @@ -50,7 +52,7 @@ public float scroll() { } public ScenarioEditorScreen(Scenario scenario) { - this.scenario = scenario; + this.scenario = scenario.toBuilder(); TimeUtils.fakeTimeMillis = System.currentTimeMillis(); } @@ -101,15 +103,12 @@ public void render(Renderer renderer, Rectangle bounds) { // new Pair<>("Pause", () -> {}) // ) // ); + rivet.root().addChild(container); } @Override public void render(float delta) { - } -// @Override -// public void resize(int width, int height) { -// timelineContainer.trackListContainer(). -// } + } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 5c0c61d7..3ce4af15 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -5,9 +5,10 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.component.impl.SolidColor; +import net.lenni0451.rivet.input.keyboard.Key; +import net.lenni0451.rivet.input.keyboard.KeyEvent; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; import net.lenni0451.rivet.math.Rectangle; @@ -40,7 +41,8 @@ public float scroll(float width) { @Override public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); + System.out.println(rivet().focusedComponent() == null ? null : rivet().focusedComponent().getClass()); + renderer.fillRect(0, 0, bounds.width(), 35, Color.fromRGB(35, 35, 35)); super.render(renderer, bounds); @@ -57,10 +59,20 @@ private void drawTimelineCursor(Renderer renderer, Rectangle bounds) { @Override public Size computeIdealSize(Size constraints) { - return Size.EMPTY; + return constraints; } public static float timestampToPosition(long timestamp, float offsetX, float size, float scale, float scroll) { return offsetX + ((timestamp - (DEFAULT_MAX_TIME * scale * scroll)) / (DEFAULT_MAX_TIME * scale)) * size; } + + @Override + protected boolean onComponentKeyUp(KeyEvent event) { + if (event.key() == Key.DELETE) { + trackListContainer.selectionManager().objects().forEach(component -> component.parentTrack().removeChild(component)); + trackListContainer.selectionManager().objects().clear(); + } + + return false; + } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 3f2691ab..94ec536f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -5,12 +5,14 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.input.keyboard.KeyEvent; import net.lenni0451.rivet.input.keyboard.ModifierKey; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.editor.containers.track.TrackContainer; import oxy.bascenario.editor.containers.track.TrackListContainer; import oxy.bascenario.util.NameUtils; @@ -20,10 +22,13 @@ public class ObjectComponent extends Component { private final TrackListContainer parent; @Getter + private final TrackContainer parentTrack; + @Getter private final ObjectOrEvent object; - public ObjectComponent(TrackListContainer parent, ObjectOrEvent object) { + public ObjectComponent(TrackListContainer parent, TrackContainer parentTrack, ObjectOrEvent object) { this.parent = parent; + this.parentTrack = parentTrack; this.object = object; } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index 876d4592..3d6f7d6f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -52,7 +52,7 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) parent.screen().timestamp(Math.max(0, newTimestamp)); } - return false; + return super.onComponentMouseDown(event, bounds); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 0b1b44e7..ed9c6fd1 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -49,7 +49,7 @@ protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { } final ObjectOrEvent object = new ObjectOrEvent(time, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true); - this.addChild(new ObjectComponent(this.container.trackListContainer(), object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); + this.addChild(new ObjectComponent(this.container.trackListContainer(), this, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); return super.onComponentDrop(event, bounds); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 6b495b8f..684bd13e 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -7,7 +7,9 @@ import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.DecoratedContainer; import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.dragdrop.DropEvent; import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; @@ -35,7 +37,8 @@ public class TrackListContainer extends ScrollContainer { private final SelectionManager selectionManager = new SelectionManager(); public TrackListContainer(TimelineContainer timelineContainer) { - super(container = new Container(new VerticalListLayout(3, false)), true, true); + final SolidColor color = new SolidColor(c -> c.color(Color.fromRGB(35, 35, 35).darker())); + super(new DecoratedContainer(color, container = new Container(new VerticalListLayout(3, false))), true, true); this.timelineContainer = timelineContainer; } @@ -91,7 +94,7 @@ public Size computeIdealSize(Size constraints) { private void add(float width, TrackContainer container, ObjectOrEvent object) { float newX = TimelineContainer.timestampToPosition(object.start, 0, width, timelineContainer.screen().scale(), timelineContainer.screen().scroll()); - container.addChild(new ObjectComponent(this, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(newX, 0))); + container.addChild(new ObjectComponent(this, container, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(newX, 0))); } public void resize(float width) { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java index cb1136ef..86e748cf 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -1,7 +1,5 @@ package oxy.bascenario.editor.containers.track.tab; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; diff --git a/editor/src/main/java/oxy/bascenario/util/NameUtils.java b/editor/src/main/java/oxy/bascenario/util/NameUtils.java index f8937d02..172a7904 100644 --- a/editor/src/main/java/oxy/bascenario/util/NameUtils.java +++ b/editor/src/main/java/oxy/bascenario/util/NameUtils.java @@ -32,7 +32,7 @@ public static String name(Object object) { case StartDialogueEvent ignored -> "Start Dialogue"; case AddDialogueEvent ignored -> "Add Dialogue"; case ShowOptionsEvent ignored -> "Show Options"; -// case ShowQuestionSelectionEvent ignored -> "Show Question And Answers"; + case ShowQuestionSelectionEvent ignored -> "Show Question And Answers"; case CloseDialogueEvent ignored -> "Close Dialogue"; case CloseOptionsEvent ignored -> "Close Options"; diff --git a/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java b/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java index f7dd61e7..260100b2 100644 --- a/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java +++ b/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java @@ -8,6 +8,7 @@ import oxy.bascenario.api.event.color.SetColorEvent; import oxy.bascenario.api.event.dialogue.AddDialogueEvent; import oxy.bascenario.api.event.dialogue.ShowOptionsEvent; +import oxy.bascenario.api.event.dialogue.ShowQuestionSelectionEvent; import oxy.bascenario.api.event.dialogue.StartDialogueEvent; import oxy.bascenario.api.event.element.values.PositionElementEvent; import oxy.bascenario.api.event.element.values.RotateElementEvent; @@ -125,7 +126,7 @@ private static long compileTime(final Event object) { case PositionElementEvent event -> Math.max(100, event.duration()); case RotateElementEvent event -> Math.max(100, event.duration()); case ShowOptionsEvent ignored -> 250L; // eh. -// case ShowQuestionSelectionEvent ignored -> 400L; + case ShowQuestionSelectionEvent ignored -> 400L; case PlaySoundEvent event -> Math.max(100, event.duration()); case SoundVolumeEvent event -> Math.max(100, event.duration()); case StopSoundEvent event -> Math.max(100, event.duration()); From ceca31515cf4c6118c18d9ed93a07d983afeea90 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Mon, 8 Jun 2026 16:05:01 +0700 Subject: [PATCH 28/71] stuff. --- .../editor/ScenarioEditorScreen.java | 8 +------- .../editor/containers/TimelineContainer.java | 17 +--------------- .../containers/track/TrackListContainer.java | 20 +++++++++++++++---- .../track/tab/TrackTabListContainer.java | 9 +++++++++ 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index eb2d1a05..4abf2d29 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -79,13 +79,7 @@ public void init(Rivet rivet) { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); - container.addChild(new TrackTabListContainer(trackTabContainer = new Container(new VerticalListLayout(3, false))) { - @Override - public void render(Renderer renderer, Rectangle bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); - super.render(renderer, bounds); - } - }, c -> { + container.addChild(new TrackTabListContainer(trackTabContainer = new Container(new VerticalListLayout(3, false))), c -> { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.15625f).withAnchorMaxY(0.99f)); }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 3ce4af15..2f6abcda 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -5,10 +5,6 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.impl.SolidColor; -import net.lenni0451.rivet.input.keyboard.Key; -import net.lenni0451.rivet.input.keyboard.KeyEvent; -import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; import net.lenni0451.rivet.math.Rectangle; @@ -41,8 +37,7 @@ public float scroll(float width) { @Override public void render(Renderer renderer, Rectangle bounds) { - System.out.println(rivet().focusedComponent() == null ? null : rivet().focusedComponent().getClass()); - + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); renderer.fillRect(0, 0, bounds.width(), 35, Color.fromRGB(35, 35, 35)); super.render(renderer, bounds); @@ -65,14 +60,4 @@ public Size computeIdealSize(Size constraints) { public static float timestampToPosition(long timestamp, float offsetX, float size, float scale, float scroll) { return offsetX + ((timestamp - (DEFAULT_MAX_TIME * scale * scroll)) / (DEFAULT_MAX_TIME * scale)) * size; } - - @Override - protected boolean onComponentKeyUp(KeyEvent event) { - if (event.key() == Key.DELETE) { - trackListContainer.selectionManager().objects().forEach(component -> component.parentTrack().removeChild(component)); - trackListContainer.selectionManager().objects().clear(); - } - - return false; - } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 684bd13e..8acbc394 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -7,10 +7,10 @@ import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.DecoratedContainer; import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.dragdrop.DropEvent; +import net.lenni0451.rivet.input.keyboard.Key; +import net.lenni0451.rivet.input.keyboard.KeyEvent; import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; @@ -37,8 +37,7 @@ public class TrackListContainer extends ScrollContainer { private final SelectionManager selectionManager = new SelectionManager(); public TrackListContainer(TimelineContainer timelineContainer) { - final SolidColor color = new SolidColor(c -> c.color(Color.fromRGB(35, 35, 35).darker())); - super(new DecoratedContainer(color, container = new Container(new VerticalListLayout(3, false))), true, true); + super(container = new Container(new VerticalListLayout(3, false)), true, true); this.timelineContainer = timelineContainer; } @@ -119,6 +118,7 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { if (event.buttons().contains(MouseButton.LEFT)) { this.selectionManager.x1(event.x()); this.selectionManager.y1(event.y()); + this.rivet().focusedComponent(this); } return super.onComponentMouseMove(event, bounds); @@ -136,6 +136,8 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) this.selectionManager.y1(event.y()); this.selectionManager.objects().clear(); + + this.rivet().focusedComponent(this); } return onComponent; } @@ -149,4 +151,14 @@ protected boolean onComponentMouseUp(MouseButtonEvent event, Rectangle bounds) { return super.onComponentMouseUp(event, bounds); } + + @Override + protected boolean onComponentKeyUp(KeyEvent event) { + if (event.key() == Key.DELETE) { + selectionManager.objects().forEach(component -> component.parentTrack().removeChild(component)); + selectionManager().objects().clear(); + } + + return false; + } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java index 86e748cf..0629d0d0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -1,10 +1,13 @@ package oxy.bascenario.editor.containers.track.tab; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; +import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; public class TrackTabListContainer extends Container { @@ -21,4 +24,10 @@ public Size computeIdealSize(Size constraints) { this.addChild(new ScrollContainer(container), c -> c.layoutOptions(BorderPosition.CENTER)); } + + @Override + public void render(Renderer renderer, Rectangle bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); + super.render(renderer, bounds); + } } From e978169b3962b94e46f2df3c77f45e76fde20a33 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 28 Jun 2026 11:24:47 +0700 Subject: [PATCH 29/71] Update Rivet. --- .../java/oxy/bascenario/EngineRenderer.java | 4 +++- .../editor/containers/AOContainer.java | 4 ++-- .../editor/containers/TimelineContainer.java | 6 ++---- .../containers/TimelineTabContainer.java | 3 ++- .../containers/object/ObjectComponent.java | 6 +++--- .../containers/selection/SelectionManager.java | 1 - .../timeline/TimelineTimeSection.java | 6 +++--- .../containers/track/TrackContainer.java | 4 ++-- .../containers/track/TrackListContainer.java | 18 ++++++++++++------ .../track/tab/TrackTabContainer.java | 2 +- .../track/tab/TrackTabListContainer.java | 2 +- .../util/components/TextWithName.java | 2 +- .../util/components/TypingLabel.java | 2 +- gradle/libs.versions.toml | 2 +- launcher/build.gradle.kts | 4 ++++ serializer/build.gradle.kts | 2 +- .../test/java/CompilerResultCompareTest.java | 4 ---- 17 files changed, 39 insertions(+), 33 deletions(-) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index 9e2252e8..d5ffde42 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -37,6 +37,7 @@ import oxy.bascenario.utils.thingl.ThinGLExtended; import oxy.bascenario.utils.thingl.ThinGLUtils; +import java.util.Arrays; import java.util.EnumSet; import java.util.Set; @@ -64,6 +65,7 @@ public void create() { GLFW.glfwSetCursorPosCallback(windowHandle, (window, x, y) -> { float[] mouseScale = this.getMouseScale(); + System.out.println(); this.rivet.onMouseMove(new MouseMoveEvent((float) x * mouseScale[0], (float) y * mouseScale[1], this.heldMouseButtons)); if (window != windowHandle) { @@ -86,7 +88,7 @@ public void create() { this.rivet.onMouseDown(event); } else if (action == GLFW.GLFW_RELEASE) { this.rivet.onMouseUp(event); - this.heldMouseButtons.add(event.button()); + this.heldMouseButtons.remove(event.button()); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index 8b21b4cb..c61def71 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -7,7 +7,7 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.ao.AOTabContainer; import oxy.bascenario.editor.containers.ao.tab.AoMediaTab; @@ -42,7 +42,7 @@ public AOContainer(ScenarioEditorScreen screen) { } @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); super.render(renderer, bounds); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 2f6abcda..7836cbd1 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -36,16 +36,14 @@ public float scroll(float width) { } @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); renderer.fillRect(0, 0, bounds.width(), 35, Color.fromRGB(35, 35, 35)); - super.render(renderer, bounds); - drawTimelineCursor(renderer, bounds); } - private void drawTimelineCursor(Renderer renderer, Rectangle bounds) { + private void drawTimelineCursor(Renderer renderer, Size bounds) { final float cursorX = timestampToPosition(screen.timestamp(), 0, bounds.width(), screen.scale(), screen.scroll()); renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java index 2d20f7da..820e4a72 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java @@ -8,6 +8,7 @@ import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; public class TimelineTabContainer extends Container { @@ -38,7 +39,7 @@ public TimelineTabContainer(final ScenarioEditorScreen screen) { } @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { timeLabel.text(format(screen.timestamp())); renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); super.render(renderer, bounds); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 94ec536f..4096beb6 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -33,17 +33,17 @@ public ObjectComponent(TrackListContainer parent, TrackContainer parentTrack, Ob } @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(30, 30, 30)); renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - this.parent.selectionManager().addIfIntersects(this, bounds); + this.parent.selectionManager().addIfIntersects(this, parentTrack.childBounds(this)); } @Override - protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { + protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { if (!event.modifiers().contains(ModifierKey.CONTROL)) { this.parent.selectionManager().objects().clear(); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java index 90556da0..a9e10cd3 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -5,7 +5,6 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.math.Rectangle; import oxy.bascenario.editor.containers.object.ObjectComponent; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index 3d6f7d6f..8e5e693c 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -20,7 +20,7 @@ public class TimelineTimeSection extends Component { private final TimelineContainer parent; @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { for (int i = 0; i <= 5; i++) { long time = (long) ((DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll()) + (DEFAULT_MAX_TIME * parent.screen().scale() * (i / 5f))); float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), parent.screen().scale(), parent.screen().scroll()); @@ -34,7 +34,7 @@ public void render(Renderer renderer, Rectangle bounds) { } @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { + protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { final float ratio = Math.min((event.x() - 1.25f) / (bounds.width()), 1); long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); @@ -45,7 +45,7 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { } @Override - protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { + protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { final float ratio = Math.min((event.x() - 1.25f) / (bounds.width()) , 1); long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index ed9c6fd1..806f16d4 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -34,13 +34,13 @@ public TrackContainer(TimelineContainer container) { } @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(50, 50, 50)); super.render(renderer, bounds); } @Override - protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { + protected boolean onComponentDrop(DropEvent event, Size bounds) { long time = (long) ((ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale()) * (event.x() / bounds.width())); long duration = TimeCompiler.compileTime(event.dragData()); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 8acbc394..30c438ab 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -27,6 +27,8 @@ import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; import oxy.bascenario.util.TimeCompiler; +import java.util.Arrays; + @Accessors(fluent = true) public class TrackListContainer extends ScrollContainer { @Getter @@ -44,7 +46,7 @@ public TrackListContainer(TimelineContainer timelineContainer) { private float prevWidth; @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { if (prevWidth != bounds.width()) { resize(bounds.width()); this.requestLayoutRecalculation(); @@ -62,11 +64,11 @@ public void render(Renderer renderer, Rectangle bounds) { super.render(renderer, bounds); - this.selectionManager.render(renderer, bounds); + this.selectionManager.render(renderer, timelineContainer.childBounds(this)); } @Override - protected boolean onComponentDrop(DropEvent event, Rectangle bounds) { + protected boolean onComponentDrop(DropEvent event, Size bounds) { if (!container.children().isEmpty()) { return super.onComponentDrop(event, bounds); } @@ -114,21 +116,25 @@ public void resize(float width) { } @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Rectangle bounds) { + protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { + System.out.println("Breh: " + Arrays.toString(event.buttons().toArray())); if (event.buttons().contains(MouseButton.LEFT)) { this.selectionManager.x1(event.x()); this.selectionManager.y1(event.y()); this.rivet().focusedComponent(this); + System.out.println("continue"); } return super.onComponentMouseMove(event, bounds); } @Override - protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) { + protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { boolean onComponent = super.onComponentMouseDown(event, bounds); + System.out.println("xd->" + onComponent); if (!onComponent && event.button() == MouseButton.LEFT) { + System.out.println("start!"); this.selectionManager.x(event.x()); this.selectionManager.y(event.y()); @@ -143,7 +149,7 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Rectangle bounds) } @Override - protected boolean onComponentMouseUp(MouseButtonEvent event, Rectangle bounds) { + protected boolean onComponentMouseUp(MouseButtonEvent event, Size bounds) { if (event.button() == MouseButton.LEFT) { this.selectionManager.x(0); this.selectionManager.y(0); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java index d9726c28..045b0795 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java @@ -19,7 +19,7 @@ public TrackTabContainer(TrackContainer container) { } @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().font().shapeText("Track " + container.index(), Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); super.render(renderer, bounds); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java index 0629d0d0..91a84b2a 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -26,7 +26,7 @@ public Size computeIdealSize(Size constraints) { } @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); super.render(renderer, bounds); } diff --git a/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java b/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java index cb9680a6..c56d1596 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java +++ b/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java @@ -21,7 +21,7 @@ public TextWithName(String name, String path) { } @Override - public void render(Renderer renderer, Rectangle bounds) { + public void render(Renderer renderer, Size bounds) { renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(25, 25, 25)); renderer.image(this.texture, 10, 10, 255, 141, Color.WHITE); diff --git a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java b/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java index 751889cf..e0092ea0 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java +++ b/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java @@ -71,7 +71,7 @@ public void onThemeChanged() { } @Override - public void render(final Renderer renderer, final Rectangle bounds) { + public void render(final Renderer renderer, final Size bounds) { this.shapeText(); float x = this.horizontalOrigin.offset(bounds.width() / this.scale); float y = this.verticalOrigin.offset(bounds.height() / this.scale); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7cbe6e76..05abbdaa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "1d187fab05" +rivet = "ddf3f2c0c3" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } diff --git a/launcher/build.gradle.kts b/launcher/build.gradle.kts index 3d902189..061f90c5 100644 --- a/launcher/build.gradle.kts +++ b/launcher/build.gradle.kts @@ -3,6 +3,10 @@ plugins { id("com.gradleup.shadow") version("9.4.1") } +repositories { + maven("https://jitpack.io") +} + dependencies { implementation(project(":api")) implementation(project(":editor")) diff --git a/serializer/build.gradle.kts b/serializer/build.gradle.kts index 0b117737..9fa0813c 100644 --- a/serializer/build.gradle.kts +++ b/serializer/build.gradle.kts @@ -10,6 +10,6 @@ dependencies { implementation(libs.commons.core) - testImplementation(project(":core")) + testImplementation(project(":api")) testImplementation(libs.gdx.base) } \ No newline at end of file diff --git a/serializer/src/test/java/CompilerResultCompareTest.java b/serializer/src/test/java/CompilerResultCompareTest.java index 1d842cbc..83a058be 100644 --- a/serializer/src/test/java/CompilerResultCompareTest.java +++ b/serializer/src/test/java/CompilerResultCompareTest.java @@ -1,11 +1,8 @@ import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import io.netty.buffer.Unpooled; import net.lenni0451.commons.color.Color; import oxy.bascenario.api.Scenario; import oxy.bascenario.api.effects.Easing; -import oxy.bascenario.api.effects.Effect; import oxy.bascenario.api.event.animation.PlayAnimationEvent; import oxy.bascenario.api.event.animation.SpriteAnimationEvent; import oxy.bascenario.api.event.animation.StopAnimationEvent; @@ -15,7 +12,6 @@ import oxy.bascenario.api.event.dialogue.ShowOptionsEvent; import oxy.bascenario.api.event.dialogue.StartDialogueEvent; import oxy.bascenario.api.event.element.AddElementEvent; -import oxy.bascenario.api.event.element.ElementEffectEvent; import oxy.bascenario.api.event.element.values.PositionElementEvent; import oxy.bascenario.api.event.element.values.RotateElementEvent; import oxy.bascenario.api.event.sound.SoundVolumeEvent; From fe47ccfc77573922a9a620d50e68d5a12d0d4d48 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 28 Jun 2026 11:30:24 +0700 Subject: [PATCH 30/71] Fixes AO text tab. --- .../editor/containers/AOContainer.java | 2 +- .../editor/containers/ao/tab/AoTextTab.java | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index c61def71..75b063c0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -23,7 +23,7 @@ public class AOContainer extends Container { public void tab(Tab tab) { this.removeChild(currentTab); currentTab = switch (tab) { - case Text -> new AoTextTab(); + case Text -> new AoTextTab(screen); case Object -> new AoObjectTab(screen); default -> null; }; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java index fe013ae8..c7544afc 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java @@ -1,41 +1,41 @@ package oxy.bascenario.editor.containers.ao.tab; import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.component.impl.Label; -import net.lenni0451.rivet.component.impl.SolidColor; -import net.lenni0451.rivet.layout.anchor.AnchorLayout; -import net.lenni0451.rivet.layout.grid.GridLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; +import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.render.elements.text.AnimatedText; import oxy.bascenario.api.render.elements.text.Text; import oxy.bascenario.api.render.elements.text.TextSegment; +import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.util.components.TypingLabel; import java.util.List; public class AoTextTab extends ScrollContainer { - public AoTextTab() { + public AoTextTab(ScenarioEditorScreen screen) { final Container container = new Container(new VerticalListLayout(10, false)); super(container); + float screenWidth = ThinGL.windowInterface().getFramebufferWidth(); + final float width = (0.99f * screenWidth - 0.15625f * screenWidth) * (1000L / (ScenarioEditorScreen.DEFAULT_MAX_TIME * screen.scale())); + final Button defaultText = new Button(new Label("Default Text").scale(0.7f), ignored -> {}); defaultText.mouseDownListener().add((ignored, bounds) -> { - Button ghost = new Button(new Label("Default Text").scale(0.7f), _ -> {}); + Button ghost = new Button(new Label("").scale(0.7f), _ -> {}); ghost.interactive(false); ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); rivet().dragAndDropManager().startDrag( new Text(List.of(TextSegment.builder().text("Default Text").build()), 42), ghost, - new Size(bounds.width(), bounds.height()), + new Size(width, 30), 0, - -bounds.height() / 2f + -30 / 2f ); return true; }); @@ -44,16 +44,16 @@ public AoTextTab() { final Button typingText = new Button(new TypingLabel("Typing Text").scale(0.7f), ignored -> {}); typingText.mouseDownListener().add((ignored, bounds) -> { - Button ghost = new Button(new TypingLabel("Typing Text").scale(0.7f), _ -> {}); + Button ghost = new Button(new Label("").scale(0.7f), _ -> {}); ghost.interactive(false); ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); rivet().dragAndDropManager().startDrag( new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42), ghost, - new Size(bounds.width(), bounds.height()), + new Size(width, 30), 0, - -bounds.height() / 2f + -30 / 2f ); return true; }); From 65025fdda6f87f0051dc01906b36b9844b631d15 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Tue, 30 Jun 2026 13:51:25 +0700 Subject: [PATCH 31/71] stuff. --- .../java/oxy/bascenario/EngineRenderer.java | 1 - .../containers/track/TrackListContainer.java | 7 +- .../track/tab/TrackTabContainer.java | 20 +++- .../util/components/ButtonImage.java | 96 ++++++++++++++++++ .../assets/base/uis/editor/add_24.png | Bin 0 -> 1033 bytes .../assets/base/uis/editor/trash_18.png | Bin 0 -> 703 bytes 6 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java create mode 100644 editor/src/main/resources/assets/base/uis/editor/add_24.png create mode 100644 editor/src/main/resources/assets/base/uis/editor/trash_18.png diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index d5ffde42..36fcf9c8 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -65,7 +65,6 @@ public void create() { GLFW.glfwSetCursorPosCallback(windowHandle, (window, x, y) -> { float[] mouseScale = this.getMouseScale(); - System.out.println(); this.rivet.onMouseMove(new MouseMoveEvent((float) x * mouseScale[0], (float) y * mouseScale[1], this.heldMouseButtons)); if (window != windowHandle) { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 30c438ab..e86065f3 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -27,12 +27,11 @@ import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; import oxy.bascenario.util.TimeCompiler; -import java.util.Arrays; - @Accessors(fluent = true) public class TrackListContainer extends ScrollContainer { @Getter private final TimelineContainer timelineContainer; + @Getter private final Container container; @Getter @@ -117,12 +116,10 @@ public void resize(float width) { @Override protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { - System.out.println("Breh: " + Arrays.toString(event.buttons().toArray())); if (event.buttons().contains(MouseButton.LEFT)) { this.selectionManager.x1(event.x()); this.selectionManager.y1(event.y()); this.rivet().focusedComponent(this); - System.out.println("continue"); } return super.onComponentMouseMove(event, bounds); @@ -132,9 +129,7 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { boolean onComponent = super.onComponentMouseDown(event, bounds); - System.out.println("xd->" + onComponent); if (!onComponent && event.button() == MouseButton.LEFT) { - System.out.println("start!"); this.selectionManager.x(event.x()); this.selectionManager.y(event.y()); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java index 045b0795..b01f154d 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java @@ -3,23 +3,41 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.impl.Image; +import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.layout.Layout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.Base; import oxy.bascenario.editor.containers.track.TrackContainer; +import oxy.bascenario.util.components.ButtonImage; public class TrackTabContainer extends Container { private final TrackContainer container; public TrackTabContainer(TrackContainer container) { - super(AnchorLayout.INSTANCE); + super(AbsoluteLayout.INSTANCE); this.container = container; + + final ButtonImage trash = new ButtonImage(Base.instance().assetsManager().texture("assets/base/uis/editor/trash_18.png"), event -> { + if (event.button() != MouseButton.LEFT) { + return; + } + + this.container.container().screen().trackTabContainer().removeChild(this); + this.container.container().trackListContainer().container().removeChild(container); + }); + trash.hoverColor().set(Color.RED); + this.addChild(trash, image -> image.layoutOptions(new AbsoluteLayoutOptions(5, 35))); } @Override public void render(Renderer renderer, Size bounds) { +// renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.RED); renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().font().shapeText("Track " + container.index(), Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); super.render(renderer, bounds); diff --git a/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java b/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java new file mode 100644 index 00000000..e0315521 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java @@ -0,0 +1,96 @@ +package oxy.bascenario.util.components; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.Texture; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.thingl.ThinGLTexture; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.ListenerList; +import net.lenni0451.rivet.component.container.Button; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.theme.ThemeOption; +import net.raphimc.thingl.gl.resource.image.texture.impl.Texture2D; +import oxy.bascenario.utils.animation.math.ColorAnimations; + +@RequiredArgsConstructor +@Accessors(fluent = true, chain = true, makeFinal = true) +public class ButtonImage extends Component { + private boolean hovered = false; + + @Getter + private final Texture texture; + + private final ClickListener clickListener; + + @Getter + private final ThemeOption hoverColor; + + @Getter + private final ThemeOption blendDuration; + + private final ColorAnimations color = new ColorAnimations(Color.WHITE); + + public ButtonImage(final Texture2D texture, final ClickListener clickListener) { + this.texture = new ThinGLTexture(texture); + this.clickListener = clickListener; + + this.hoverColor = new ThemeOption<>(this, null); + this.hoverColor.set(Color.WHITE); + + this.blendDuration = new ThemeOption<>(this, null); + this.blendDuration.set(400); + } + + @Override + protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { + if (event.button() == MouseButton.LEFT) { + clickListener.onClick(event); + return true; + } + + return false; + } + + @Override + public void render(final Renderer renderer, final Size size) { + renderer.image(this.texture, 0, 0, size.width(), size.height(), color.color()); + } + + @Override + public Size computeIdealSize(final Size constraints) { + return new Size(this.texture.width(), this.texture.height()); + } + + @Override + protected void onComponentRemoved() { + this.hovered = false; + } + + @Override + protected void onComponentDisabled() { + this.hovered = false; + } + + @Override + protected void onComponentMouseEnter() { + this.hovered = true; + color.set(this.hoverColor.value(), this.blendDuration.value()); + } + + @Override + protected void onComponentMouseLeave() { + this.hovered = false; + color.set(Color.WHITE, this.blendDuration.value()); + } + + @FunctionalInterface + public interface ClickListener { + void onClick(final MouseButtonEvent event); + } +} diff --git a/editor/src/main/resources/assets/base/uis/editor/add_24.png b/editor/src/main/resources/assets/base/uis/editor/add_24.png new file mode 100644 index 0000000000000000000000000000000000000000..58b6cbbe5fc49a36c246090feb98bd7c74c8f177 GIT binary patch literal 1033 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=fph@~GoGtRnhaz}mbgZg1m~xflqVLYG6W=M=9TFAxrQj1>KW=8 z8VNJHmjD&*NR9AJ^YqkW-~e)18Kf9l85n^qFCdnNvO&(#U}Of1GXdF#j7$uIKspMD zGuv6f;yFMz2$*p*FuY(!(CD@@FaRYdursg#)fyNX8!#?_m$Nv;eZ8x(p2rK(ZHKn289NvhM+^+v@4!7*Y}UcD7@-aG=2PbTiSTlbv3xt_@Q0 zJ%3B7D&Ru*C9y9H<}bYO^jD&edt+0Dz!!zA+xina*iBgj56))GDXZE1^Sue@ZSPyX zDfj;*{QCc%&FOy1y_DPE<+?d~5B*ud=Kb~QJJ|>?jlCod35sWa!LnlnAT z>b)Vj>&+pS)8(Q2wr%^i>2*Um!^PU{AnvpFk-{_o`TsTeb@f8#+9=`gY8<^{>-6*2 z9L{#X)3nyiDZ3z!@uK>Ivb1d*CFRt`O%JK;==wd?tz&=d##a^MdpXaUU%!7y`}%^Z zY9YIOZ_ZXQiJEjl@LZ&eQ$|y9Wd*ClV%PnwbqBOJU)WU1Ec=vgOHR(2IL3ucyFQ%q z%vhyQJu?p_m z3wYO?EC^lB`jzVgOUs3}3GN~WC(qmL0cxLQaJt>5VEreKi@R-rE?H-OE$6X;`X8BX zw|=C(WUk>7u-&9`#=QIjN9EG>Q%y!EZRc5`25d196uUX$mH6-{B8e^kV)TH sE!=$j;Y+{YpS&YJ9lp%!GLKmdKI;Vst0M#mpYybcN literal 0 HcmV?d00001 diff --git a/editor/src/main/resources/assets/base/uis/editor/trash_18.png b/editor/src/main/resources/assets/base/uis/editor/trash_18.png new file mode 100644 index 0000000000000000000000000000000000000000..ec2fbcb761a0f0b0fb5e8f97d93f9bbaad69d3b1 GIT binary patch literal 703 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*bKspeF8T-T+J_a%*OI#yLg7ec#$`gxH83GbB^GfvmTtgI0^$hh4 zjf5H9OMr@Yq(*qAd3tIwZ~!^13{s4&42(dQ7Z6KB*&yd=FfxP1nSg9VMkWS9ARPt7 zne8lK@f;u<1eAcrykJJq=(aL203|1|Gq3>F8WQV0J5OE3=IrGvX4xAJuV%1J{cG?*`6+rAr}5~C-2o`4HRiT&(Rs=*b%@X80f39 zr$IBXH|oWf5Suo42lb09k8xKpTomAZ^xV&JLPy8^t>5g@-_0`?H{bW<^u5jR=S($D zf2Ni>Yu5~$^X`G)9?s#3k~zFo=k&TSn_Q=_H@t1Yr#R>1cB!ojZ_=2SE)37jd@xV* z{&W@Bz%L8r4hqh+Fz2b>`gdZvgxq_nbH*LJZ#6d0`PXc{BgQf(r&1=B=i9-S&1N+R z-76ZM?Wc2UGz%DY7BTHk{a{|RC$nYl$3t>v<(D!!9^NeKO=ym*<&`<)6upqsa`T>_ zQJhtuOgFHVZx#KfkUp)bm-i84)UStYw;ke@xv=fY!RztOZ3}pl&d*#LBW!=GeH)Yb zM`_MG&cD}wyqBKqD-icsz03BFMaSN|ftS|{zUk|T-N6vPRW$6uqHRsJZ=xvJgcz!plzheAbZk`R#d-mr#)rHGF-^bYYCFFBp`=JI< O6nMJ&xvX Date: Thu, 2 Jul 2026 19:14:24 +0700 Subject: [PATCH 32/71] stuff. --- .../editor/ScenarioEditorScreen.java | 8 +-- .../containers/track/TrackContainer.java | 12 ++--- .../containers/track/TrackListContainer.java | 1 - .../track/tab/TrackTabContainer.java | 2 +- .../track/tab/TrackTabListContainer.java | 50 ++++++++++++++---- .../util/components/ButtonImage.java | 11 ++-- .../assets/base/uis/editor/add_24.png | Bin 1033 -> 661 bytes 7 files changed, 57 insertions(+), 27 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 4abf2d29..4e885f6e 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -79,14 +79,14 @@ public void init(Rivet rivet) { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); - container.addChild(new TrackTabListContainer(trackTabContainer = new Container(new VerticalListLayout(3, false))), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.15625f).withAnchorMaxY(0.99f)); - }); - container.addChild(timelineContainer = new TimelineContainer(this), c -> { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.15625f).withAnchorMinY(0.655f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); }); + container.addChild(new TrackTabListContainer(trackTabContainer = new Container(new VerticalListLayout(3, false)), timelineContainer), c -> { + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.15625f).withAnchorMaxY(0.99f)); + }); + container.addChild(new TimelineTabContainer(this), c -> { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.605f).withAnchorMaxX(0.99f).withAnchorMaxY(0.65f)); }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 806f16d4..9e03f9a0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -1,15 +1,14 @@ package oxy.bascenario.editor.containers.track; import lombok.Getter; -import lombok.Setter; import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.dragdrop.DropEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.api.render.RenderLayer; import oxy.bascenario.editor.ScenarioEditorScreen; @@ -23,8 +22,6 @@ public class TrackContainer extends Container { @Getter private final TimelineContainer container; - @Setter @Getter - private int index; @Getter private float height = 60f; @@ -35,7 +32,7 @@ public TrackContainer(TimelineContainer container) { @Override public void render(Renderer renderer, Size bounds) { - renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(50, 50, 50)); + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(40, 40, 40)); super.render(renderer, bounds); } @@ -57,7 +54,10 @@ protected boolean onComponentDrop(DropEvent event, Size bounds) { @Override public Size computeIdealSize(Size constraints) { Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); - // give some extra scroll by default. return new Size(Math.max(idealSize.width(), constraints.width()), height); } + + public int index() { + return container.trackListContainer().container().children().indexOf(this); + } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index e86065f3..41fd65fd 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -16,7 +16,6 @@ import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.api.render.RenderLayer; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java index b01f154d..f29b65bb 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java @@ -31,7 +31,7 @@ public TrackTabContainer(TrackContainer container) { this.container.container().screen().trackTabContainer().removeChild(this); this.container.container().trackListContainer().container().removeChild(container); }); - trash.hoverColor().set(Color.RED); + trash.hoverColor().set(Color.fromRGB(202, 74, 92)); this.addChild(trash, image -> image.layoutOptions(new AbsoluteLayoutOptions(5, 35))); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java index 91a84b2a..2dd4e171 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -2,27 +2,35 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.Base; +import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.track.TrackContainer; +import oxy.bascenario.util.components.ButtonImage; public class TrackTabListContainer extends Container { - public TrackTabListContainer(Container container) { + private final ScrollContainer scrollContainer; + + public TrackTabListContainer(Container container, TimelineContainer timelineContainer) { super(BorderLayout.INSTANCE); // Just to take space. - this.addChild(new Component() { + this.addChild(new UpperContainer(timelineContainer, container), c -> c.layoutOptions(BorderPosition.TOP)); + + this.scrollContainer = new ScrollContainer(container, false, false) { @Override - public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), 35); + public void render(Renderer renderer, Size size) { + super.render(renderer, size); } - }, c -> c.layoutOptions(BorderPosition.TOP)); - - this.addChild(new ScrollContainer(container), c -> c.layoutOptions(BorderPosition.CENTER)); + }; + this.addChild(this.scrollContainer, c -> c.layoutOptions(BorderPosition.CENTER)); } @Override @@ -30,4 +38,28 @@ public void render(Renderer renderer, Size bounds) { renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); super.render(renderer, bounds); } + + private static class UpperContainer extends Container { + public UpperContainer(TimelineContainer timelineContainer, Container container) { + super(AbsoluteLayout.INSTANCE); + + final ButtonImage add = new ButtonImage(Base.instance().assetsManager().texture("assets/base/uis/editor/add_24.png"), event -> { + if (event.button() != MouseButton.LEFT) { + return; + } + + final TrackContainer component = new TrackContainer(timelineContainer); + timelineContainer.trackListContainer().container().addChild(component); + container.addChild(new TrackTabContainer(component)); + }); + add.hoverColor().set(Color.fromRGB(202, 74, 92)); + + this.addChild(add, c -> c.layoutOptions(new AbsoluteLayoutOptions(5, 5))); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), 35); + } + } } diff --git a/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java b/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java index e0315521..648f874d 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java +++ b/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java @@ -2,29 +2,28 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.Setter; import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.Texture; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.backend.thingl.ThinGLTexture; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.ListenerList; -import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.theme.ThemeOption; +import net.raphimc.thingl.ThinGL; import net.raphimc.thingl.gl.resource.image.texture.impl.Texture2D; import oxy.bascenario.utils.animation.math.ColorAnimations; +import static oxy.bascenario.utils.thingl.ThinGLUtils.GLOBAL_RENDER_STACK; + @RequiredArgsConstructor @Accessors(fluent = true, chain = true, makeFinal = true) public class ButtonImage extends Component { private boolean hovered = false; @Getter - private final Texture texture; + private final ThinGLTexture texture; private final ClickListener clickListener; @@ -44,7 +43,7 @@ public ButtonImage(final Texture2D texture, final ClickListener clickListener) { this.hoverColor.set(Color.WHITE); this.blendDuration = new ThemeOption<>(this, null); - this.blendDuration.set(400); + this.blendDuration.set(800); } @Override diff --git a/editor/src/main/resources/assets/base/uis/editor/add_24.png b/editor/src/main/resources/assets/base/uis/editor/add_24.png index 58b6cbbe5fc49a36c246090feb98bd7c74c8f177..f43920e13fba7e82f912d836e613fc3ba728f6a9 100644 GIT binary patch delta 424 zcmV;Z0ayNs2$cmPiBL{Q4GJ0x0000DNk~Le0000O0000O2nGNE0N{5$_>mzf3U2@a z0B-^DqTEA~Q+Ej*3jhG<0{{RVk<(#+&Rzee+H$`C0003&Nkl0AL zprZ$#%Z(!V4|H-k7e)WAgE)xZq<7*EP;hk66I^dNTi+-73L^EAA$j0w5(<5rFKyF7 z+qTZA<(ZtwLphQ|lXHE1%P;do&gG4~l8tnZ06CUVG8d1MYx;m%=Dc_ZAr&Qmy3j-} z{dDqkUg<#1OEDpU1uex90lT4Jgb@MTp`XOmOl8O-FV)!$<0yO9zFHe&$qfE`iTdDX zFqMh`EQJpkgnln~@<37=d^fu>=hm%B9lLwN*WEzuykn91V5OF6Z0(!1F;-kSeD%uE z5Woyl*)mgUDA7WloiL8Vf>QHiV?TkXGbLgMrT8RZ7WzfFCxAV)6t`@d zm1KL%%}iFmH~pL;bLlsIo+6u#f16L`iTwN9vLX9e$?ms>hvh+1@@uIV*ts9&{Ipm3 SkkZ@$00000Q+EkA3jhG<0{{Rvk<(#+(daf56Lkl?0008DNklm{vHBu&rSB6?8Ji_#YKpr4`NLj4$i3$cQD5cD8w+b@ua2Q>vz!J7tbc6_t> z?=%V9OWKWO-~3?u|L+Du-(=oo+xH%e2#v!0pax6zUbBu3C=gy-@+@`pp_<6 zyN%hKAT(N%&=JmAL_%nSb9s0LK%+&!1%ALQv(U1610CfCv}|n5UIC#3y70g$c@pTjv+xRlMvIjKA{K0a%rOnR5Pc39 zTrgzlxV+XMDJ7fPRbWPXe_@R;?bDxOhd96V`vR=7&$Sb<;Ui0*z#jF9(U!xauK-T~ z^tjL|#JYjN;;0P17jr+35w8GfwCK0M;1-mLB!nr+_92WSl-jy|$?QYD0yF{o9FRvW z*qFTnpwXfU&?cD2L}2NEohk~vJPCXX@ z^$PF=gwTeNM-(v0&pf;WpwW^r%ZEI0ukr}dyF8+=0IoOIY~wH|{2aF0_-5$?dlW9p~F4ph5Z|2-8yH#oo{ho>JO zG{&Xf+pr`F-BL|N?A)DhIEGb<4o}ZO$Hi800!4?X?T@%rlJ}~iv)ke6PW$ppSn|Wu d1w@_zqJI|M({gC5$_M}e002ovPDHLkV1jDXZ5{vs From 8ace82f64981f47c27f0511caeae7340f052e035 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 3 Jul 2026 12:31:40 +0700 Subject: [PATCH 33/71] Fix stuff and update Rivet for the N times. --- core/src/main/java/oxy/bascenario/EngineRenderer.java | 7 +++---- .../editor/containers/track/TrackListContainer.java | 6 ++---- gradle/libs.versions.toml | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index 36fcf9c8..7a605f69 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -23,10 +23,8 @@ import net.lenni0451.rivet.layout.fullsize.FullSizeLayout; import net.lenni0451.rivet.math.Size; import net.raphimc.thingl.ThinGL; -import net.raphimc.thingl.resource.font.face.impl.FreeTypeFontFace; import net.raphimc.thingl.resource.font.instance.FontInstance; import net.raphimc.thingl.resource.font.instance.FontInstanceSet; -import net.raphimc.thingl.resource.font.instance.impl.FreeTypeFontInstance; import net.raphimc.thingl.text.util.GlyphPredicate; import org.joml.Matrix4fStack; import org.lwjgl.glfw.GLFW; @@ -37,7 +35,6 @@ import oxy.bascenario.utils.thingl.ThinGLExtended; import oxy.bascenario.utils.thingl.ThinGLUtils; -import java.util.Arrays; import java.util.EnumSet; import java.util.Set; @@ -133,6 +130,8 @@ public void setScreen(Screen screen) { private boolean someTempHackyBool; private boolean fullScreen; + + private final ThinGLRenderer renderer = new ThinGLRenderer(); @Override public void render() { ScreenUtils.clear(0, 0, 0, 1, true); @@ -147,7 +146,7 @@ public void render() { } ThinGL.programs().getMsaa().bindInput(); - ThinGLRenderer.renderList(ThinGLUtils.GLOBAL_RENDER_STACK, this.rivet.render()); + renderer.renderList(ThinGLUtils.GLOBAL_RENDER_STACK, this.rivet.render()); ThinGL.programs().getMsaa().unbindInput(); ThinGL.programs().getMsaa().renderFullscreen(); ThinGL.programs().getMsaa().clearInput(); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 41fd65fd..8b122070 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -126,9 +126,7 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { - boolean onComponent = super.onComponentMouseDown(event, bounds); - - if (!onComponent && event.button() == MouseButton.LEFT) { + if (!super.onComponentMouseDown(event, bounds) && event.button() == MouseButton.LEFT) { this.selectionManager.x(event.x()); this.selectionManager.y(event.y()); @@ -139,7 +137,7 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { this.rivet().focusedComponent(this); } - return onComponent; + return true; } @Override diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 05abbdaa..a6274bef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "ddf3f2c0c3" +rivet = "e08d71f0ce" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } From aaac901c20c271dcd77b4c1eda38a777065ca521 Mon Sep 17 00:00:00 2001 From: oxy Date: Fri, 3 Jul 2026 18:03:40 +0700 Subject: [PATCH 34/71] Tell JitPack to use Java 25. --- jitpack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jitpack.yml b/jitpack.yml index a2027925..aaa58906 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,2 +1,2 @@ jdk: - - openjdk21 \ No newline at end of file + - openjdk25 From 0bd41b3bb9db812308e0a31fe2e223c63567a260 Mon Sep 17 00:00:00 2001 From: oxy Date: Fri, 3 Jul 2026 18:08:29 +0700 Subject: [PATCH 35/71] Oops. --- core/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 3d940eee..f7b12b2c 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { api(project(":api")) api(project(":serializer")) - api(variantOf(libs.raphimc.thingl)) + api(libs.raphimc.thingl) api(libs.commons.animations) api(libs.unnamed.mocha) @@ -58,4 +58,4 @@ dependencies { api(libs.rivet.core) api(libs.rivet.thingl.backend) -} \ No newline at end of file +} From 5726597aa9de0c6834db030d72c34b25ce8fcd7d Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sat, 4 Jul 2026 11:36:39 +0700 Subject: [PATCH 36/71] Fix niche rivet bug. --- core/src/main/java/oxy/bascenario/EngineRenderer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index 7a605f69..dce07900 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -122,6 +122,7 @@ public void setScreen(Screen screen) { TimeUtils.fakeTimeMillis = null; super.setScreen(screen); + this.rivet.root().clearChildren(); if (screen instanceof ExtendableScreen extendableScreen) { extendableScreen.init(this.rivet); } From 0dafff7ee959dc6e185233ce86c7b390d340aef4 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 18:23:15 +0700 Subject: [PATCH 37/71] Partial support for moving component around. --- .../editor/containers/ao/tab/AoObjectTab.java | 2 +- .../containers/object/ObjectComponent.java | 46 +++++++++++++++++++ .../selection/SelectionManager.java | 3 ++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java index 4b63ba03..df33147c 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -29,7 +29,7 @@ public AoObjectTab(ScenarioEditorScreen screen) { @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), constraints.height() - 40); + return new Size(constraints.width(), Math.max(0, constraints.height() - 40)); } private void add(ScenarioEditorScreen screen, Container container, String name, String path, Object object) { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 4096beb6..27b34901 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -7,11 +7,15 @@ import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.input.keyboard.KeyEvent; import net.lenni0451.rivet.input.keyboard.ModifierKey; +import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.track.TrackContainer; import oxy.bascenario.editor.containers.track.TrackListContainer; import oxy.bascenario.util.NameUtils; @@ -44,6 +48,12 @@ public void render(Renderer renderer, Size bounds) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { + this.parent.selectionManager().prevX(event.x()); + this.parent.selectionManager().prevY(event.y()); + if (this.parent.selectionManager().isSelected(this)) { + return true; + } + if (!event.modifiers().contains(ModifierKey.CONTROL)) { this.parent.selectionManager().objects().clear(); } @@ -52,6 +62,42 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { return true; } + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { + if (!event.buttons().contains(MouseButton.LEFT)) { + return true; + } + + final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); + for (Component baseComponent : this.parentTrack.children()) { + if (!(baseComponent instanceof ObjectComponent component) || !this.parent.selectionManager().isSelected(component)) { + continue; + } + + Rectangle rectangle = this.parentTrack.childBounds(component); + float delta = (event.x() - this.parent.selectionManager().prevX()); + float newX = rectangle.x() + delta; + + component.object.start = (long) ((newX / this.parentTrack.relativeBounds().width()) * maxTime); + component.object.start = Math.max(0, component.object.start); + newX = TimelineContainer.timestampToPosition( + component.object.start, + 0, + this.parentTrack.relativeBounds().width(), + this.parent.timelineContainer().screen().scale(), + this.parent.timelineContainer().screen().scroll() + ); + + component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); + } + + + this.parent.selectionManager().prevX(event.x()); + this.parent.selectionManager().prevY(event.y()); + + return true; + } + @Override public Size computeIdealSize(Size constraints) { final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java index a9e10cd3..fbfe9350 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -20,6 +20,9 @@ public boolean isSelected(ObjectComponent component) { return objects.contains(component); } + @Getter + private float prevX, prevY; + private float x, y; private float x1, y1; From f1c8890547a02211a3afc167cfda99b7b0b64d4d Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 18:52:03 +0700 Subject: [PATCH 38/71] Fixed scrolling desync. --- .../containers/track/tab/TrackTabListContainer.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java index 2dd4e171..5eec6ba3 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -5,6 +5,7 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.border.BorderLayout; @@ -15,6 +16,8 @@ import oxy.bascenario.editor.containers.track.TrackContainer; import oxy.bascenario.util.components.ButtonImage; +import java.lang.reflect.Field; + public class TrackTabListContainer extends Container { private final ScrollContainer scrollContainer; @@ -24,12 +27,15 @@ public TrackTabListContainer(Container container, TimelineContainer timelineCont // Just to take space. this.addChild(new UpperContainer(timelineContainer, container), c -> c.layoutOptions(BorderPosition.TOP)); - this.scrollContainer = new ScrollContainer(container, false, false) { + this.scrollContainer = new ScrollContainer(container, false, true) { @Override public void render(Renderer renderer, Size size) { + this.scrollY(timelineContainer.trackListContainer().scrollY(), true); super.render(renderer, size); } }; + this.scrollContainer.barWidth().set(0f); + this.addChild(this.scrollContainer, c -> c.layoutOptions(BorderPosition.CENTER)); } From 22e72d19c7ad5b7d2834f2380be8fd32c2c1806a Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 19:00:58 +0700 Subject: [PATCH 39/71] Disallowing object overlapping when adding new objects. --- .../editor/containers/ao/tab/AoTextTab.java | 2 +- .../containers/track/TrackContainer.java | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java index c7544afc..61f4e472 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java @@ -62,6 +62,6 @@ public AoTextTab(ScenarioEditorScreen screen) { @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), constraints.height() - 40); + return new Size(constraints.width(), Math.max(0, constraints.height() - 40)); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 9e03f9a0..2adc5036 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -4,6 +4,7 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.dragdrop.DropEvent; @@ -15,6 +16,7 @@ import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.object.ObjectComponent; import oxy.bascenario.editor.containers.object.ObjectOrEvent; +import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; import oxy.bascenario.util.TimeCompiler; @Accessors(fluent = true) @@ -46,7 +48,32 @@ protected boolean onComponentDrop(DropEvent event, Size bounds) { } final ObjectOrEvent object = new ObjectOrEvent(time, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true); - this.addChild(new ObjectComponent(this.container.trackListContainer(), this, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); + + boolean accepted = true; + for (Component baseComponent : children()) { + if (!(baseComponent instanceof ObjectComponent component)) { + continue; + } + + long otherMin = component.object().start; + + long maxTime = component.object().start + component.object().duration; + long otherMaxTime = component.object().start + component.object().duration; + + if (maxTime >= otherMin && time <= otherMaxTime) { + accepted = false; + break; + } + } + + if (accepted) { + this.addChild(new ObjectComponent(this.container.trackListContainer(), this, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); + } else { + final TrackContainer newTrack = new TrackContainer(container); + container.trackListContainer().container().addChild(newTrack); + container.screen().trackTabContainer().addChild(new TrackTabContainer(newTrack)); + newTrack.addChild(new ObjectComponent(this.container.trackListContainer(), newTrack, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); + } return super.onComponentDrop(event, bounds); } From 3d45fe5bbe47aaa24aa9e33b4eadb077e827a627 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 19:20:33 +0700 Subject: [PATCH 40/71] Fixes crashes on minimize, update Rivet. --- .../main/java/oxy/bascenario/EngineRenderer.java | 4 +++- .../oxy/bascenario/managers/other/GifAsset.java | 2 +- .../editor/containers/ao/AOTabContainer.java | 6 +----- .../editor/containers/object/ObjectComponent.java | 14 +++++++++----- .../containers/timeline/TimelineTimeSection.java | 2 +- gradle/libs.versions.toml | 2 +- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index dce07900..2628f46b 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -10,7 +10,9 @@ import lombok.SneakyThrows; import net.lenni0451.commons.collections.Maps; import net.lenni0451.rivet.Rivet; +import net.lenni0451.rivet.backend.render.deferred.DeferredRenderer; import net.lenni0451.rivet.backend.thingl.ThinGLBackend; +import net.lenni0451.rivet.backend.thingl.render.BatchedRenderListExecutor; import net.lenni0451.rivet.backend.thingl.render.ThinGLRenderer; import net.lenni0451.rivet.backend.thingl.text.ThinGLFont; import net.lenni0451.rivet.backend.thingl.util.GLFWMapper; @@ -147,7 +149,7 @@ public void render() { } ThinGL.programs().getMsaa().bindInput(); - renderer.renderList(ThinGLUtils.GLOBAL_RENDER_STACK, this.rivet.render()); + BatchedRenderListExecutor.INSTANCE.renderList(this.renderer, this.rivet.render(new DeferredRenderer()).complete()); ThinGL.programs().getMsaa().unbindInput(); ThinGL.programs().getMsaa().renderFullscreen(); ThinGL.programs().getMsaa().clearInput(); diff --git a/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java b/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java index 360195e9..1196c233 100644 --- a/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java +++ b/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import net.raphimc.thingl.gl.texture.animated.SequencedTexture; -import net.raphimc.thingl.image.animated.impl.AwtGifImage; +import net.raphimc.thingl.resource.image.animated.impl.AwtGifImage; @RequiredArgsConstructor public class GifAsset { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java index 0f40af33..9587c3db 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java @@ -25,11 +25,7 @@ public Size computeIdealSize(Size constraints) { private void add(AOContainer parent, Container container, AOContainer.Tab targetTab) { final Label label = new Label(targetTab.name()).scale(0.5f); - final Button button = new Button(label, ignored -> {}); - button.clickListener().add(ignored -> { - parent.tab(targetTab); - }); - + final Button button = new Button(label, ignored -> parent.tab(targetTab)); container.addChild(button); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 27b34901..cb867f7d 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -68,6 +68,15 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { return true; } + float delta = (event.x() - this.parent.selectionManager().prevX()); + this.parent.selectionManager().prevX(event.x()); + this.parent.selectionManager().prevY(event.y()); + System.out.println(event.x()); + +// if (Math.abs(delta) < 3) { +// return true; +// } + final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); for (Component baseComponent : this.parentTrack.children()) { if (!(baseComponent instanceof ObjectComponent component) || !this.parent.selectionManager().isSelected(component)) { @@ -75,7 +84,6 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { } Rectangle rectangle = this.parentTrack.childBounds(component); - float delta = (event.x() - this.parent.selectionManager().prevX()); float newX = rectangle.x() + delta; component.object.start = (long) ((newX / this.parentTrack.relativeBounds().width()) * maxTime); @@ -91,10 +99,6 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); } - - this.parent.selectionManager().prevX(event.x()); - this.parent.selectionManager().prevY(event.y()); - return true; } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index 8e5e693c..5d45ffb0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -57,6 +57,6 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { @Override public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), 35); + return new Size(constraints.width(), Math.min(35, constraints.height())); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a6274bef..15dbf628 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "e08d71f0ce" +rivet = "2bdf13b2f7" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } From a8496b8167e1407f1a7368d1ee3eca9136923bd2 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 19:44:18 +0700 Subject: [PATCH 41/71] Downgrade Rivet. --- .../java/oxy/bascenario/EngineRenderer.java | 4 +- .../bascenario/managers/other/GifAsset.java | 2 +- core/src/test/java/DefaultRivetTest.java | 74 +++++++++++++++++++ core/src/test/java/RivetTest.java | 20 ----- gradle/libs.versions.toml | 2 +- 5 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 core/src/test/java/DefaultRivetTest.java delete mode 100644 core/src/test/java/RivetTest.java diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index 2628f46b..dce07900 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -10,9 +10,7 @@ import lombok.SneakyThrows; import net.lenni0451.commons.collections.Maps; import net.lenni0451.rivet.Rivet; -import net.lenni0451.rivet.backend.render.deferred.DeferredRenderer; import net.lenni0451.rivet.backend.thingl.ThinGLBackend; -import net.lenni0451.rivet.backend.thingl.render.BatchedRenderListExecutor; import net.lenni0451.rivet.backend.thingl.render.ThinGLRenderer; import net.lenni0451.rivet.backend.thingl.text.ThinGLFont; import net.lenni0451.rivet.backend.thingl.util.GLFWMapper; @@ -149,7 +147,7 @@ public void render() { } ThinGL.programs().getMsaa().bindInput(); - BatchedRenderListExecutor.INSTANCE.renderList(this.renderer, this.rivet.render(new DeferredRenderer()).complete()); + renderer.renderList(ThinGLUtils.GLOBAL_RENDER_STACK, this.rivet.render()); ThinGL.programs().getMsaa().unbindInput(); ThinGL.programs().getMsaa().renderFullscreen(); ThinGL.programs().getMsaa().clearInput(); diff --git a/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java b/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java index 1196c233..360195e9 100644 --- a/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java +++ b/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import net.raphimc.thingl.gl.texture.animated.SequencedTexture; -import net.raphimc.thingl.resource.image.animated.impl.AwtGifImage; +import net.raphimc.thingl.image.animated.impl.AwtGifImage; @RequiredArgsConstructor public class GifAsset { diff --git a/core/src/test/java/DefaultRivetTest.java b/core/src/test/java/DefaultRivetTest.java new file mode 100644 index 00000000..6c876827 --- /dev/null +++ b/core/src/test/java/DefaultRivetTest.java @@ -0,0 +1,74 @@ +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.Rivet; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.thingl.RivetThinGLApplication; +import net.lenni0451.rivet.component.container.Button; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Size; +import net.raphimc.thingl.resource.font.face.impl.FreeTypeFontFace; +import net.raphimc.thingl.resource.font.instance.FontInstance; +import net.raphimc.thingl.resource.font.instance.FontInstanceSet; +import net.raphimc.thingl.text.util.GlyphPredicate; +import org.lwjgl.glfw.GLFW; + +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.SequencedMap; + +public class DefaultRivetTest extends RivetThinGLApplication { + + public static void main(String[] args) { + new DefaultRivetTest().run(); + } + + @Override + protected void init(final Rivet rivet) { + Container container = new Container(AnchorLayout.INSTANCE); + + final Container innerContainer = new Container(AnchorLayout.INSTANCE) { + @Override + public void render(Renderer renderer, Size size) { + renderer.fillRect(0, 0, size.width(), size.height(), Color.WHITE); + super.render(renderer, size); + } + }; + container.addChild(innerContainer, c -> { + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.1F).withAnchorMinY(0.1F).withAnchorMaxX(0.8f).withAnchorMaxY(0.6f)); + }); + + Container innerInnerContainer = new Container(new VerticalListLayout(3, false)); + innerContainer.addChild(new ScrollContainer(innerInnerContainer)); + + for (int i = 0; i < 100; i++) { + innerInnerContainer.addChild(new Button("BUTTON " + i, () -> {})); + } + + rivet.root().addChild(container); + } + + static { + if (System.getProperty("os.name").contains("Linux")) { + GLFW.glfwInitHint(GLFW.GLFW_PLATFORM, GLFW.GLFW_PLATFORM_X11); + } + } + + public static FontInstanceSet createFont(final int size, final InputStream... streams) throws IOException { + SequencedMap fonts = new LinkedHashMap<>(); + for (InputStream is : streams) { + FontInstance font = new FreeTypeFontFace(is.readAllBytes()).getInstance(size); + fonts.put(font, GlyphPredicate.all()); + } + return new FontInstanceSet(fonts); + } + + + @Override + protected FontInstanceSet createFont() throws Exception { + return createFont(40, DefaultRivetTest.class.getResourceAsStream("assets/base/fonts/global/NotoSans-Regular.ttf")); + } +} \ No newline at end of file diff --git a/core/src/test/java/RivetTest.java b/core/src/test/java/RivetTest.java deleted file mode 100644 index 1d365d2e..00000000 --- a/core/src/test/java/RivetTest.java +++ /dev/null @@ -1,20 +0,0 @@ -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.Rivet; -import net.lenni0451.rivet.component.impl.slider.Slider; -import net.lenni0451.rivet.input.keyboard.Key; -import net.lenni0451.rivet.layout.grid.GridAnchor; -import net.lenni0451.rivet.layout.grid.GridFill; -import net.lenni0451.rivet.layout.grid.GridLayout; -import net.lenni0451.rivet.layout.grid.GridLayoutOptions; -import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Padding; -import net.lenni0451.rivet.text.model.TextOrigin; -import net.lenni0451.rivet.theme.Theme; -import net.lenni0451.rivet.theme.impl.DefaultDark; -import oxy.bascenario.utils.ExtendableScreen; -import oxy.bascenario.utils.Launcher; - -public class RivetTest { - public static void main(String[] args) { - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 15dbf628..a6274bef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "2bdf13b2f7" +rivet = "e08d71f0ce" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } From 7e9e6433d0134c8aae61bb5b1d90d222a2159632 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 20:14:45 +0700 Subject: [PATCH 42/71] Fix jitter object dragging. --- .../java/oxy/bascenario/EngineRenderer.java | 14 ++-- .../bascenario/managers/other/GifAsset.java | 2 +- core/src/test/java/DefaultRivetTest.java | 29 ++++++--- .../containers/object/ObjectComponent.java | 42 ------------ .../containers/track/TrackContainer.java | 65 +++++++++++++++++++ gradle/libs.versions.toml | 2 +- 6 files changed, 93 insertions(+), 61 deletions(-) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index dce07900..da9a0dc4 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -10,6 +10,8 @@ import lombok.SneakyThrows; import net.lenni0451.commons.collections.Maps; import net.lenni0451.rivet.Rivet; +import net.lenni0451.rivet.backend.render.deferred.DeferredRenderer; +import net.lenni0451.rivet.backend.render.deferred.RenderListExecutor; import net.lenni0451.rivet.backend.thingl.ThinGLBackend; import net.lenni0451.rivet.backend.thingl.render.ThinGLRenderer; import net.lenni0451.rivet.backend.thingl.text.ThinGLFont; @@ -61,15 +63,11 @@ public void create() { // GLFW.glfwSetWindowAspectRatio(windowHandle, 16, 9); GLFW.glfwSetCursorPosCallback(windowHandle, (window, x, y) -> { - float[] mouseScale = this.getMouseScale(); - this.rivet.onMouseMove(new MouseMoveEvent((float) x * mouseScale[0], (float) y * mouseScale[1], this.heldMouseButtons)); - - if (window != windowHandle) { - return; - } - this.mouseX = x; this.mouseY = y; + + float[] mouseScale = this.getMouseScale(); + this.rivet.onMouseMove(new MouseMoveEvent((float) x * mouseScale[0], (float) y * mouseScale[1], this.heldMouseButtons)); }); GLFW.glfwSetMouseButtonCallback(windowHandle, (window, button, action, mode) -> { float[] mouseScale = this.getMouseScale(); @@ -147,7 +145,7 @@ public void render() { } ThinGL.programs().getMsaa().bindInput(); - renderer.renderList(ThinGLUtils.GLOBAL_RENDER_STACK, this.rivet.render()); + RenderListExecutor.INSTANCE.renderList(this.renderer, this.rivet.render(new DeferredRenderer()).complete()); ThinGL.programs().getMsaa().unbindInput(); ThinGL.programs().getMsaa().renderFullscreen(); ThinGL.programs().getMsaa().clearInput(); diff --git a/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java b/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java index 360195e9..1196c233 100644 --- a/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java +++ b/core/src/main/java/oxy/bascenario/managers/other/GifAsset.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import net.raphimc.thingl.gl.texture.animated.SequencedTexture; -import net.raphimc.thingl.image.animated.impl.AwtGifImage; +import net.raphimc.thingl.resource.image.animated.impl.AwtGifImage; @RequiredArgsConstructor public class GifAsset { diff --git a/core/src/test/java/DefaultRivetTest.java b/core/src/test/java/DefaultRivetTest.java index 6c876827..9402d0b4 100644 --- a/core/src/test/java/DefaultRivetTest.java +++ b/core/src/test/java/DefaultRivetTest.java @@ -5,10 +5,16 @@ import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.component.impl.FormattedLabel; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextFormat; import net.raphimc.thingl.resource.font.face.impl.FreeTypeFontFace; import net.raphimc.thingl.resource.font.instance.FontInstance; import net.raphimc.thingl.resource.font.instance.FontInstanceSet; @@ -30,23 +36,28 @@ public static void main(String[] args) { protected void init(final Rivet rivet) { Container container = new Container(AnchorLayout.INSTANCE); - final Container innerContainer = new Container(AnchorLayout.INSTANCE) { + final Container innerContainer = new Container(AbsoluteLayout.INSTANCE) { @Override public void render(Renderer renderer, Size size) { - renderer.fillRect(0, 0, size.width(), size.height(), Color.WHITE); + renderer.fillRect(0, 0, size.width(), size.height(), Color.BLUE); + + float stuff = (System.currentTimeMillis() % 5000) / 5000f; + + this.children().getFirst().layoutOptions(new AbsoluteLayoutOptions(stuff * size.width(), 0)); super.render(renderer, size); } }; container.addChild(innerContainer, c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.1F).withAnchorMinY(0.1F).withAnchorMaxX(0.8f).withAnchorMaxY(0.6f)); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.1F).withAnchorMinY(0.1F).withAnchorMaxX(0.9f).withAnchorMaxY(0.9f)); }); - Container innerInnerContainer = new Container(new VerticalListLayout(3, false)); - innerContainer.addChild(new ScrollContainer(innerInnerContainer)); - - for (int i = 0; i < 100; i++) { - innerInnerContainer.addChild(new Button("BUTTON " + i, () -> {})); - } + innerContainer.addChild(new Label("Test Label") { + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { + System.out.println(event); + return super.onComponentMouseMove(event, size); + } + }); rivet.root().addChild(container); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index cb867f7d..59070220 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -48,8 +48,6 @@ public void render(Renderer renderer, Size bounds) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { - this.parent.selectionManager().prevX(event.x()); - this.parent.selectionManager().prevY(event.y()); if (this.parent.selectionManager().isSelected(this)) { return true; } @@ -62,46 +60,6 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { return true; } - @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { - if (!event.buttons().contains(MouseButton.LEFT)) { - return true; - } - - float delta = (event.x() - this.parent.selectionManager().prevX()); - this.parent.selectionManager().prevX(event.x()); - this.parent.selectionManager().prevY(event.y()); - System.out.println(event.x()); - -// if (Math.abs(delta) < 3) { -// return true; -// } - - final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); - for (Component baseComponent : this.parentTrack.children()) { - if (!(baseComponent instanceof ObjectComponent component) || !this.parent.selectionManager().isSelected(component)) { - continue; - } - - Rectangle rectangle = this.parentTrack.childBounds(component); - float newX = rectangle.x() + delta; - - component.object.start = (long) ((newX / this.parentTrack.relativeBounds().width()) * maxTime); - component.object.start = Math.max(0, component.object.start); - newX = TimelineContainer.timestampToPosition( - component.object.start, - 0, - this.parentTrack.relativeBounds().width(), - this.parent.timelineContainer().screen().scale(), - this.parent.timelineContainer().screen().scroll() - ); - - component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); - } - - return true; - } - @Override public Size computeIdealSize(Size constraints) { final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 2adc5036..cf3a4417 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -8,8 +8,12 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.dragdrop.DropEvent; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.api.render.RenderLayer; import oxy.bascenario.editor.ScenarioEditorScreen; @@ -78,6 +82,67 @@ protected boolean onComponentDrop(DropEvent event, Size bounds) { return super.onComponentDrop(event, bounds); } +// @Override +// protected boolean onComponentMouseDown(MouseButtonEvent event, Size size) { +// boolean clickedOn = super.onComponentMouseDown(event, size); +// if (clickedOn) { +// this.container.trackListContainer().selectionManager().prevX(event.x()); +// this.container.trackListContainer().selectionManager().prevX(event.y()); +// } +// return clickedOn; +// } + + @Override + protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { + this.container.trackListContainer().selectionManager().prevX(Float.MAX_VALUE); + this.container.trackListContainer().selectionManager().prevX(Float.MAX_VALUE); + + System.out.println("Up!"); + return super.onComponentMouseUp(event, size); + } + + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { + if (!event.buttons().contains(MouseButton.LEFT)) { + return true; + } + + if (this.container.trackListContainer().selectionManager().prevX() == Float.MAX_VALUE || this.container.trackListContainer().selectionManager().prevY() == Float.MAX_VALUE) { + this.container.trackListContainer().selectionManager().prevX(event.x()); + this.container.trackListContainer().selectionManager().prevY(event.y()); + return true; + } + + float delta = (event.x() - this.container.trackListContainer().selectionManager().prevX()); + this.container.trackListContainer().selectionManager().prevX(event.x()); + this.container.trackListContainer().selectionManager().prevY(event.y()); + + final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale(); + for (Component baseComponent : children()) { + if (!(baseComponent instanceof ObjectComponent component) || !this.container.trackListContainer().selectionManager().isSelected(component)) { + continue; + } + + Rectangle rectangle = this.childBounds(component); + float newX = rectangle.x() + delta; + + component.object().start = (long) ((newX / size.width()) * maxTime); + component.object().start = Math.max(0, component.object().start); + newX = TimelineContainer.timestampToPosition( + component.object().start, + 0, + size.width(), + container.screen().scale(), + container.screen().scroll() + ); + + + component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); + } + + return super.onComponentMouseMove(event, size); + } + @Override public Size computeIdealSize(Size constraints) { Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a6274bef..15dbf628 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "e08d71f0ce" +rivet = "2bdf13b2f7" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } From cab78d1dd75d0ff1a0e769c5fd80584676197a4b Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 20:18:59 +0700 Subject: [PATCH 43/71] Added extra initial scrolling. --- .../containers/object/ObjectComponent.java | 6 ------ .../containers/track/TrackContainer.java | 18 +++++++----------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 59070220..525fd226 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -5,17 +5,11 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.input.keyboard.KeyEvent; import net.lenni0451.rivet.input.keyboard.ModifierKey; -import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; -import net.lenni0451.rivet.input.mouse.MouseMoveEvent; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.track.TrackContainer; import oxy.bascenario.editor.containers.track.TrackListContainer; import oxy.bascenario.util.NameUtils; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index cf3a4417..8154be89 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -34,6 +34,13 @@ public class TrackContainer extends Container { public TrackContainer(TimelineContainer container) { super(AbsoluteLayout.INSTANCE); this.container = container; + + this.addChild(new Component() { + @Override + public Size computeIdealSize(Size constraints) { + return new Size(1.0e-16f, 1.0e-16f); + } + }, c -> c.layoutOptions(new AbsoluteLayoutOptions(3000, 0, 5f, 5f))); } @Override @@ -82,22 +89,11 @@ protected boolean onComponentDrop(DropEvent event, Size bounds) { return super.onComponentDrop(event, bounds); } -// @Override -// protected boolean onComponentMouseDown(MouseButtonEvent event, Size size) { -// boolean clickedOn = super.onComponentMouseDown(event, size); -// if (clickedOn) { -// this.container.trackListContainer().selectionManager().prevX(event.x()); -// this.container.trackListContainer().selectionManager().prevX(event.y()); -// } -// return clickedOn; -// } - @Override protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { this.container.trackListContainer().selectionManager().prevX(Float.MAX_VALUE); this.container.trackListContainer().selectionManager().prevX(Float.MAX_VALUE); - System.out.println("Up!"); return super.onComponentMouseUp(event, size); } From 1870bb40fbf924ba62c0a3cacf56e23b5eff8b80 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 20:28:19 +0700 Subject: [PATCH 44/71] Nicer looking scroll. --- .../timeline/TimelineTimeSection.java | 19 +++++++++++++------ .../containers/track/TrackContainer.java | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index 5d45ffb0..fcbc3464 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -11,6 +11,7 @@ import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.TimelineContainer; import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; @@ -21,15 +22,21 @@ public class TimelineTimeSection extends Component { @Override public void render(Renderer renderer, Size bounds) { - for (int i = 0; i <= 5; i++) { - long time = (long) ((DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll()) + (DEFAULT_MAX_TIME * parent.screen().scale() * (i / 5f))); - float segmentX = TimelineContainer.timestampToPosition(time, 0, bounds.width(), parent.screen().scale(), parent.screen().scroll()); + float x = -parent.trackListContainer().scrollX(); + while (!(x >= bounds.width())) { + renderer.fillRect(x, 0, 1, 10, Color.WHITE); - float seconds = time / 1000f; + final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.screen().scale(); + float time = (x / bounds.width()) * maxTime; + + float seconds = Math.abs(time) / 1000f; + seconds = Math.round(seconds * 100.0f) / 100.0f; ShapedText text = this.rivet().backend().font().shapeText(seconds + "s", Color.WHITE); + float currentX = x; + + renderer.scale(0.4f, () -> renderer.text(text, currentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - renderer.fillRect(segmentX, 0, 1, 10, Color.WHITE); - renderer.scale(0.4f, () -> renderer.text(text, segmentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + x += bounds.width() / 5f; } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 8154be89..d57390b8 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -35,6 +35,7 @@ public TrackContainer(TimelineContainer container) { super(AbsoluteLayout.INSTANCE); this.container = container; + // So the user can scroll more by default. this.addChild(new Component() { @Override public Size computeIdealSize(Size constraints) { From 61dcff6b91f066e582545eec01085e0fa36d3b02 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 20:59:17 +0700 Subject: [PATCH 45/71] More fixes, still broken. --- .../editor/containers/TimelineContainer.java | 8 ++++++-- .../containers/timeline/TimelineTimeSection.java | 1 + .../editor/containers/track/TrackContainer.java | 14 +++++--------- .../containers/track/TrackListContainer.java | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 7836cbd1..3dae2c1a 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -44,7 +44,7 @@ public void render(Renderer renderer, Size bounds) { } private void drawTimelineCursor(Renderer renderer, Size bounds) { - final float cursorX = timestampToPosition(screen.timestamp(), 0, bounds.width(), screen.scale(), screen.scroll()); + final float cursorX = legacyTimestampToPosition(screen.timestamp(), 0, bounds.width(), screen.scale(), screen.scroll()); renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); renderer.fillRect(cursorX, 0, 2, bounds.height(), TIMELINE_CURSOR_COLOR.withAlphaF(0.8f)); @@ -55,7 +55,11 @@ public Size computeIdealSize(Size constraints) { return constraints; } - public static float timestampToPosition(long timestamp, float offsetX, float size, float scale, float scroll) { + public static float timestampToPosition(long timestamp, float offsetX, float size, float scale) { + return offsetX + (timestamp / (DEFAULT_MAX_TIME * scale)) * size; + } + + public static float legacyTimestampToPosition(long timestamp, float offsetX, float size, float scale, float scroll) { return offsetX + ((timestamp - (DEFAULT_MAX_TIME * scale * scroll)) / (DEFAULT_MAX_TIME * scale)) * size; } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index fcbc3464..1ee1dad3 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -28,6 +28,7 @@ public void render(Renderer renderer, Size bounds) { final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.screen().scale(); float time = (x / bounds.width()) * maxTime; + time += (ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.screen().scroll()); float seconds = Math.abs(time) / 1000f; seconds = Math.round(seconds * 100.0f) / 100.0f; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index d57390b8..3f761dac 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -52,7 +52,8 @@ public void render(Renderer renderer, Size bounds) { @Override protected boolean onComponentDrop(DropEvent event, Size bounds) { - long time = (long) ((ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale()) * (event.x() / bounds.width())); + final float TOTAL_TIME = ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale(); + long time = (long) (TOTAL_TIME * (event.x() / bounds.width())); long duration = TimeCompiler.compileTime(event.dragData()); if (duration == Long.MAX_VALUE) { @@ -120,20 +121,15 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { continue; } - Rectangle rectangle = this.childBounds(component); - float newX = rectangle.x() + delta; - - component.object().start = (long) ((newX / size.width()) * maxTime); + component.object().start += (long) ((delta / size.width()) * maxTime); component.object().start = Math.max(0, component.object().start); - newX = TimelineContainer.timestampToPosition( + float newX = TimelineContainer.timestampToPosition( component.object().start, 0, size.width(), - container.screen().scale(), - container.screen().scroll() + container.screen().scale() ); - component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 8b122070..03b0b451 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -92,7 +92,7 @@ public Size computeIdealSize(Size constraints) { } private void add(float width, TrackContainer container, ObjectOrEvent object) { - float newX = TimelineContainer.timestampToPosition(object.start, 0, width, timelineContainer.screen().scale(), timelineContainer.screen().scroll()); + float newX = TimelineContainer.timestampToPosition(object.start, 0, width, timelineContainer.screen().scale()); container.addChild(new ObjectComponent(this, container, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(newX, 0))); } @@ -107,7 +107,7 @@ public void resize(float width) { continue; } - float newX = TimelineContainer.timestampToPosition(object.object().start, 0, width, timelineContainer.screen().scale(), timelineContainer.screen().scroll()); + float newX = TimelineContainer.timestampToPosition(object.object().start, 0, width, timelineContainer.screen().scale()); child.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); } } From 9ee636dda5c48899001adb2d61aec3353dd948fe Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 5 Jul 2026 21:19:04 +0700 Subject: [PATCH 46/71] Fix stuff. --- .../editor/containers/object/ObjectComponent.java | 2 +- .../editor/containers/track/TrackContainer.java | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 525fd226..5515ae46 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -57,6 +57,6 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { @Override public Size computeIdealSize(Size constraints) { final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); - return new Size((object.duration / maxTime) * constraints.width(), constraints.height()); + return new Size((object.duration / maxTime) * parentTrack.cachedWidth(), constraints.height()); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 3f761dac..c409edc8 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -31,6 +31,9 @@ public class TrackContainer extends Container { @Getter private float height = 60f; + @Getter + private float cachedWidth; + public TrackContainer(TimelineContainer container) { super(AbsoluteLayout.INSTANCE); this.container = container; @@ -53,7 +56,7 @@ public void render(Renderer renderer, Size bounds) { @Override protected boolean onComponentDrop(DropEvent event, Size bounds) { final float TOTAL_TIME = ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale(); - long time = (long) (TOTAL_TIME * (event.x() / bounds.width())); + long time = (long) (TOTAL_TIME * (event.x() / cachedWidth)); long duration = TimeCompiler.compileTime(event.dragData()); if (duration == Long.MAX_VALUE) { @@ -121,12 +124,12 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { continue; } - component.object().start += (long) ((delta / size.width()) * maxTime); + component.object().start += (long) ((delta / cachedWidth) * maxTime); component.object().start = Math.max(0, component.object().start); float newX = TimelineContainer.timestampToPosition( component.object().start, 0, - size.width(), + cachedWidth, container.screen().scale() ); @@ -138,6 +141,7 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { @Override public Size computeIdealSize(Size constraints) { + cachedWidth = constraints.width(); Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); return new Size(Math.max(idealSize.width(), constraints.width()), height); } From 9e08faed9027d93c52155c4083162175b35d542b Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Tue, 7 Jul 2026 19:21:06 +0700 Subject: [PATCH 47/71] Fixes initial mouse drag. --- .../editor/containers/selection/SelectionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java index fbfe9350..27ec72b1 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -21,7 +21,7 @@ public boolean isSelected(ObjectComponent component) { } @Getter - private float prevX, prevY; + private float prevX = Float.MAX_VALUE, prevY = Float.MAX_VALUE; private float x, y; private float x1, y1; From 339b4b759cf55039cc73eaabdccf28eacda747b3 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Tue, 7 Jul 2026 19:39:18 +0700 Subject: [PATCH 48/71] Added a play button. --- .../editor/ScenarioEditorScreen.java | 28 ++++-- .../containers/TimelineTabContainer.java | 11 +++ .../editor/containers/ao/tab/AoObjectTab.java | 6 +- .../util/components/PlayButton.java | 81 ++++++++++++++++++ .../uis/editor/{ => objects}/emoticon.png | Bin .../editor/{ => objects}/location-info.png | Bin .../base/uis/editor/{ => objects}/preview.png | Bin .../assets/base/uis/editor/pause_18.png | Bin 0 -> 519 bytes .../assets/base/uis/editor/play_18.png | Bin 0 -> 582 bytes 9 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/util/components/PlayButton.java rename editor/src/main/resources/assets/base/uis/editor/{ => objects}/emoticon.png (100%) rename editor/src/main/resources/assets/base/uis/editor/{ => objects}/location-info.png (100%) rename editor/src/main/resources/assets/base/uis/editor/{ => objects}/preview.png (100%) create mode 100644 editor/src/main/resources/assets/base/uis/editor/pause_18.png create mode 100644 editor/src/main/resources/assets/base/uis/editor/play_18.png diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 4e885f6e..349282bf 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -32,7 +32,18 @@ public class ScenarioEditorScreen extends ExtendableScreen { private final Scenario.Builder scenario; public static final long DEFAULT_MAX_TIME = 15000; + + private boolean playing; private long timestamp = 1000; + public void timestamp(long timestamp) { + this.timestamp = timestamp; + this.playing = false; + + if (timelineTabContainer != null) { + timelineTabContainer.playButton().state(false); + } + } + private float scale = 1; public void scale(float scale) { @@ -43,6 +54,7 @@ public void scale(float scale) { timelineContainer.trackListContainer().requestLayoutRecalculation(); } + private TimelineTabContainer timelineTabContainer; private TimelineContainer timelineContainer; private Container trackTabContainer; @@ -56,8 +68,17 @@ public ScenarioEditorScreen(Scenario scenario) { TimeUtils.fakeTimeMillis = System.currentTimeMillis(); } + private long since = 0; @Override public void renderBehindRivet() { + if (since == 0) { + since = System.currentTimeMillis(); + } + if (playing) { + timestamp += System.currentTimeMillis() - since; + } + since = System.currentTimeMillis(); + ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(50, 50, 50)); } @@ -87,7 +108,7 @@ public void init(Rivet rivet) { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.15625f).withAnchorMaxY(0.99f)); }); - container.addChild(new TimelineTabContainer(this), c -> { + container.addChild(timelineTabContainer = new TimelineTabContainer(this), c -> { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.605f).withAnchorMaxX(0.99f).withAnchorMaxY(0.65f)); }); @@ -100,9 +121,4 @@ public void init(Rivet rivet) { rivet.root().addChild(container); } - - @Override - public void render(float delta) { - - } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java index 820e4a72..61605dcc 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java @@ -1,5 +1,7 @@ package oxy.bascenario.editor.containers; +import lombok.Getter; +import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; @@ -10,11 +12,16 @@ import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.util.components.PlayButton; +@Accessors(fluent = true) public class TimelineTabContainer extends Container { private final ScenarioEditorScreen screen; private final Label timeLabel; + + @Getter + private final PlayButton playButton; public TimelineTabContainer(final ScenarioEditorScreen screen) { super(AnchorLayout.INSTANCE); @@ -35,6 +42,10 @@ public TimelineTabContainer(final ScenarioEditorScreen screen) { this.addChild(timeLabel); + playButton = new PlayButton(screen.playing(), screen::playing); + playButton.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.5f).withPivotY(0.5f)); + this.addChild(playButton); + this.screen = screen; } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java index df33147c..b10493e8 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -22,9 +22,9 @@ public AoObjectTab(ScenarioEditorScreen screen) { final Container container = new Container(new VerticalListLayout(10, false)); super(container); - add(screen, container, "Preview", "assets/base/uis/editor/preview.png", new Preview(FontType.NotoSans, "Title", "Subtitle", null)); - add(screen, container, "Location Info", "assets/base/uis/editor/location-info.png", new LocationInfo(FontType.NotoSans, "Location A", 1000, 200)); - add(screen, container, "Emoticon", "assets/base/uis/editor/emoticon.png", new Emoticon(100, EmoticonType.ANGRY, true)); + add(screen, container, "Preview", "assets/base/uis/editor/objects/preview.png", new Preview(FontType.NotoSans, "Title", "Subtitle", null)); + add(screen, container, "Location Info", "assets/base/uis/editor/objects/location-info.png", new LocationInfo(FontType.NotoSans, "Location A", 1000, 200)); + add(screen, container, "Emoticon", "assets/base/uis/editor/objects/emoticon.png", new Emoticon(100, EmoticonType.ANGRY, true)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/util/components/PlayButton.java b/editor/src/main/java/oxy/bascenario/util/components/PlayButton.java new file mode 100644 index 00000000..bd14a8b4 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/util/components/PlayButton.java @@ -0,0 +1,81 @@ +package oxy.bascenario.util.components; + +import it.unimi.dsi.fastutil.booleans.BooleanPredicate; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.Texture; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.thingl.ThinGLTexture; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.theme.ThemeOption; +import oxy.bascenario.Base; +import oxy.bascenario.utils.animation.math.ColorAnimations; + +import java.util.function.Consumer; + +@RequiredArgsConstructor +@Accessors(fluent = true, chain = true, makeFinal = true) +public class PlayButton extends Component { + @Setter + private boolean state; + private Consumer onStateChanged; + + @Getter + private final ThemeOption blendDuration; + + private final ColorAnimations color = new ColorAnimations(Color.WHITE); + + private Texture texture1, texture2; + public PlayButton(boolean state, Consumer onStateChanged) { + this.state = state; + this.onStateChanged = onStateChanged; + + this.blendDuration = new ThemeOption<>(this, null); + this.blendDuration.set(800); + + this.texture1 = new ThinGLTexture(Base.instance().assetsManager().texture("assets/base/uis/editor/pause_18.png")); + this.texture2 = new ThinGLTexture(Base.instance().assetsManager().texture("assets/base/uis/editor/play_18.png")); + } + + @Override + protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { + if (event.button() == MouseButton.LEFT) { + this.state = !this.state; + onStateChanged.accept(this.state); + return true; + } + + return false; + } + + @Override + public void render(final Renderer renderer, final Size size) { + renderer.image(state ? this.texture1 : this.texture2, 0, 0, size.width(), size.height(), color.color()); + } + + @Override + public Size computeIdealSize(final Size constraints) { + return new Size(18, 18); + } + + @Override + protected void onComponentMouseEnter() { + color.set(Color.fromRGB(202, 74, 92), this.blendDuration.value()); + } + + @Override + protected void onComponentMouseLeave() { + color.set(Color.WHITE, this.blendDuration.value()); + } + + @FunctionalInterface + public interface ClickListener { + void onClick(final MouseButtonEvent event); + } +} diff --git a/editor/src/main/resources/assets/base/uis/editor/emoticon.png b/editor/src/main/resources/assets/base/uis/editor/objects/emoticon.png similarity index 100% rename from editor/src/main/resources/assets/base/uis/editor/emoticon.png rename to editor/src/main/resources/assets/base/uis/editor/objects/emoticon.png diff --git a/editor/src/main/resources/assets/base/uis/editor/location-info.png b/editor/src/main/resources/assets/base/uis/editor/objects/location-info.png similarity index 100% rename from editor/src/main/resources/assets/base/uis/editor/location-info.png rename to editor/src/main/resources/assets/base/uis/editor/objects/location-info.png diff --git a/editor/src/main/resources/assets/base/uis/editor/preview.png b/editor/src/main/resources/assets/base/uis/editor/objects/preview.png similarity index 100% rename from editor/src/main/resources/assets/base/uis/editor/preview.png rename to editor/src/main/resources/assets/base/uis/editor/objects/preview.png diff --git a/editor/src/main/resources/assets/base/uis/editor/pause_18.png b/editor/src/main/resources/assets/base/uis/editor/pause_18.png new file mode 100644 index 0000000000000000000000000000000000000000..10e39c9440c903c52d26ba0a1debb8505d58c512 GIT binary patch literal 519 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*bKsp4385gfTA_`KW=8 z8VNJHmjD&*NR9AJ^YqkW-~e)18Kf9l85n^qFCdnNvO&(#U}Of1GXdF#j7$uIKspMD zGuv6f;yFMz2;>2cdBKdJ(QRd507_0^XJ7%UH83(ZU|aw(7i1^v0*G1DfNT(80-DSO zRvBbz0c1gS85$UXWQ+D3(49VC+Zo8*?CIhdV&VUGiX+z{0|D1$@msa7{^@&rtIoZ0 za-8n!lvI6;>1s;*bKsp4385gfTA_`KW=8 z8VNJHmjD&*NR9AJ^YqkW-~e)18Kf9l85n^qFCdnNvO&(#U}Of1GXdF#j7$uIKspMD zGuv6f;yFMz2;>2cdBKdJ(QRd507_0^XJ7%UH83(ZU|aw(7i1^v0*G1DfNT(80-DSO zRvBbz0c1gS85$UXWQ+D3(49VC+Zo9Gm5&x8cgI;Wo-TN*&@NrAd_@O)L{f9mtwhaZMn!k4E#1=cSYknzcss2&S qZE<*B!1az5iM{eYf*+DDzmiwjW&bZ~=7~g5NO-#XxvX Date: Fri, 10 Jul 2026 19:57:02 +0700 Subject: [PATCH 49/71] Fix: scroll bar not showing up for tab. --- .../oxy/bascenario/editor/ScenarioEditorScreen.java | 7 +++++-- .../bascenario/editor/containers/AOContainer.java | 12 ++++++------ .../editor/containers/track/TrackContainer.java | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 349282bf..14be8527 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -37,10 +37,13 @@ public class ScenarioEditorScreen extends ExtendableScreen { private long timestamp = 1000; public void timestamp(long timestamp) { this.timestamp = timestamp; - this.playing = false; + playing(false); + } + public void playing(boolean playing) { + this.playing = playing; if (timelineTabContainer != null) { - timelineTabContainer.playButton().state(false); + timelineTabContainer.playButton().state(playing); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index 75b063c0..6ccd81d4 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -5,8 +5,8 @@ import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.border.BorderLayout; +import net.lenni0451.rivet.layout.border.BorderPosition; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.ao.AOTabContainer; @@ -29,16 +29,16 @@ public void tab(Tab tab) { }; if (currentTab != null) { - this.addChild(currentTab, c -> c.layoutOptions(new AbsoluteLayoutOptions(10, 45))); + this.addChild(currentTab, c -> c.layoutOptions(BorderPosition.BOTTOM)); } } public AOContainer(ScenarioEditorScreen screen) { - super(AbsoluteLayout.INSTANCE); + super(BorderLayout.INSTANCE); this.screen = screen; - this.addChild(new AOTabContainer(this), c -> c.layoutOptions(new AbsoluteLayoutOptions(0, 0))); - this.addChild(new AoMediaTab()); + this.addChild(new AOTabContainer(this), c -> c.layoutOptions(BorderPosition.TOP)); + this.addChild(new AoMediaTab(), c -> c.layoutOptions(BorderPosition.BOTTOM)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index c409edc8..1b0b362b 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -55,6 +55,8 @@ public void render(Renderer renderer, Size bounds) { @Override protected boolean onComponentDrop(DropEvent event, Size bounds) { + container.screen().playing(false); + final float TOTAL_TIME = ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale(); long time = (long) (TOTAL_TIME * (event.x() / cachedWidth)); From 57cc444b5323a0eac817f742636994c873fec206 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 10 Jul 2026 20:22:52 +0700 Subject: [PATCH 50/71] Fix edge case behaviour for adding element. --- .../containers/track/TrackContainer.java | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 1b0b362b..0af3dd85 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -67,31 +67,41 @@ protected boolean onComponentDrop(DropEvent event, Size bounds) { final ObjectOrEvent object = new ObjectOrEvent(time, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true); - boolean accepted = true; - for (Component baseComponent : children()) { - if (!(baseComponent instanceof ObjectComponent component)) { - continue; + TrackContainer track = this; + while (true) { + boolean accepted = true; + for (Component baseComponent : track.children()) { + if (!(baseComponent instanceof ObjectComponent component)) { + continue; + } + + long otherMin = component.object().start; + + long maxTime = component.object().start + component.object().duration; + long otherMaxTime = component.object().start + component.object().duration; + + if (maxTime >= otherMin && time <= otherMaxTime) { + accepted = false; + break; + } } - long otherMin = component.object().start; - - long maxTime = component.object().start + component.object().duration; - long otherMaxTime = component.object().start + component.object().duration; - - if (maxTime >= otherMin && time <= otherMaxTime) { - accepted = false; + if (!accepted) { + int size = container.trackListContainer().container().children().size(); + if (track.index() >= size - 1) { + track = new TrackContainer(container); + container.trackListContainer().container().addChild(track); + container.screen().trackTabContainer().addChild(new TrackTabContainer(track)); + break; + } else { + track = (TrackContainer) container.trackListContainer().container().children().get(track.index() + 1); + } + } else { break; } } - if (accepted) { - this.addChild(new ObjectComponent(this.container.trackListContainer(), this, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); - } else { - final TrackContainer newTrack = new TrackContainer(container); - container.trackListContainer().container().addChild(newTrack); - container.screen().trackTabContainer().addChild(new TrackTabContainer(newTrack)); - newTrack.addChild(new ObjectComponent(this.container.trackListContainer(), newTrack, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); - } + track.addChild(new ObjectComponent(this.container.trackListContainer(), track, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); return super.onComponentDrop(event, bounds); } From d4250e3a174887ed0ea666d9d422688a24b458ee Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 12 Jul 2026 11:11:14 +0700 Subject: [PATCH 51/71] more work and broken stuff. --- core/src/test/java/DefaultRivetTest.java | 74 ++++++++++++++----- .../editor/ScenarioEditorScreen.java | 18 ++--- .../editor/containers/AOContainer.java | 11 ++- .../editor/containers/TimelineContainer.java | 15 +--- .../containers/TimelineTabContainer.java | 2 +- .../editor/containers/ao/tab/AoMediaTab.java | 1 - .../editor/containers/ao/tab/AoObjectTab.java | 16 +--- .../editor/containers/ao/tab/AoTextTab.java | 26 +++---- .../object/FakeObjectComponent.java | 41 ++++++++++ .../containers/object/ObjectComponent.java | 4 +- .../timeline/TimelineTimeSection.java | 29 +++----- .../containers/track/TrackContainer.java | 35 ++++----- .../containers/track/TrackListContainer.java | 12 +-- .../util/components/TypingLabel.java | 6 +- gradle/libs.versions.toml | 2 +- 15 files changed, 170 insertions(+), 122 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java diff --git a/core/src/test/java/DefaultRivetTest.java b/core/src/test/java/DefaultRivetTest.java index 9402d0b4..6d22d4f3 100644 --- a/core/src/test/java/DefaultRivetTest.java +++ b/core/src/test/java/DefaultRivetTest.java @@ -1,7 +1,9 @@ +import lombok.RequiredArgsConstructor; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.backend.thingl.RivetThinGLApplication; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; @@ -12,9 +14,12 @@ import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; +import net.lenni0451.rivet.layout.border.BorderLayout; +import net.lenni0451.rivet.layout.border.BorderPosition; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextFormat; +import net.lenni0451.rivet.text.model.TextOrigin; import net.raphimc.thingl.resource.font.face.impl.FreeTypeFontFace; import net.raphimc.thingl.resource.font.instance.FontInstance; import net.raphimc.thingl.resource.font.instance.FontInstanceSet; @@ -32,34 +37,69 @@ public static void main(String[] args) { new DefaultRivetTest().run(); } + private static final int ONE_SECOND = 200; + @Override protected void init(final Rivet rivet) { - Container container = new Container(AnchorLayout.INSTANCE); - - final Container innerContainer = new Container(AbsoluteLayout.INSTANCE) { + Container everything = new Container(BorderLayout.INSTANCE); + everything.addChild(new Timeline().layoutOptions(BorderPosition.TOP)); + { + Container elements = new Container(AbsoluteLayout.INSTANCE); + int extra = 0; + for (int i = 0; i < 20; i++) { + elements.addChild(new TimelineElement(1).layoutOptions(new AbsoluteLayoutOptions(i * ONE_SECOND + extra, 0))); + extra += ONE_SECOND; + } + everything.addChild(elements.layoutOptions(BorderPosition.CENTER)); + } + rivet.root().addChild(new ScrollContainer(everything, true, true) { @Override public void render(Renderer renderer, Size size) { - renderer.fillRect(0, 0, size.width(), size.height(), Color.BLUE); - - float stuff = (System.currentTimeMillis() % 5000) / 5000f; - - this.children().getFirst().layoutOptions(new AbsoluteLayoutOptions(stuff * size.width(), 0)); + System.out.println(size); super.render(renderer, size); } - }; - container.addChild(innerContainer, c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.1F).withAnchorMinY(0.1F).withAnchorMaxX(0.9f).withAnchorMaxY(0.9f)); - }); - innerContainer.addChild(new Label("Test Label") { @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { - System.out.println(event); - return super.onComponentMouseMove(event, size); + public Size computeIdealSize(Size constraints) { + System.out.println(constraints); + return super.computeIdealSize(constraints); } }); + } + - rivet.root().addChild(container); + private static class Timeline extends Component { + @Override + public void render(final Renderer renderer, final Size size) { + renderer.fillRect(0, 0, size.width(), size.height(), Color.LIGHT_GRAY); + for (int i = 0; i < size.width() / ONE_SECOND; i++) { + renderer.fillRect(i * ONE_SECOND, 0, 1, size.height(), Color.BLACK); + renderer.text(this.rivet().backend().font().shapeText(String.valueOf(i), Color.RED), i * ONE_SECOND, size.height() / 2F, TextOrigin.Horizontal.VISUAL_CENTER, TextOrigin.Vertical.VISUAL_CENTER); + } + } + + @Override + public Size computeIdealSize(final Size constraints) { + return new Size(0, 50); + } + } + + private static class TimelineElement extends Component { + private final int length; + + private TimelineElement(int length) { + this.length = length; + } + + @Override + public void render(final Renderer renderer, final Size size) { + renderer.fillRect(0, 0, size.width(), size.height(), Color.BLUE); + } + + @Override + public Size computeIdealSize(final Size constraints) { + return new Size(ONE_SECOND * this.length, 50); + } } static { diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 14be8527..7ea0efa4 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -31,7 +31,14 @@ public class ScenarioEditorScreen extends ExtendableScreen { private final Scenario.Builder scenario; - public static final long DEFAULT_MAX_TIME = 15000; + private static final float ONE_SECOND_WIDTH = 128; + public float oneSecondWidth() { + return ONE_SECOND_WIDTH * scale; + } + public float oneMilSecondWidth() { + return (ONE_SECOND_WIDTH / 1000f) * scale; + } +// public static final long DEFAULT_MAX_TIME = 15000; private boolean playing; private long timestamp = 1000; @@ -51,9 +58,7 @@ public void playing(boolean playing) { public void scale(float scale) { this.scale = scale; - - float width = ThinGL.windowInterface().getFramebufferWidth(); - timelineContainer.trackListContainer().resize(0.99f * width - 0.15625f * width); + timelineContainer.trackListContainer().recalculateObjectPosition(); timelineContainer.trackListContainer().requestLayoutRecalculation(); } @@ -61,11 +66,6 @@ public void scale(float scale) { private TimelineContainer timelineContainer; private Container trackTabContainer; - public float scroll() { - float width = ThinGL.windowInterface().getFramebufferWidth(); - return timelineContainer().scroll(0.99f * width - 0.15625f * width); - } - public ScenarioEditorScreen(Scenario scenario) { this.scenario = scenario.toBuilder(); TimeUtils.fakeTimeMillis = System.currentTimeMillis(); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index 6ccd81d4..43a0e3a7 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -29,7 +29,7 @@ public void tab(Tab tab) { }; if (currentTab != null) { - this.addChild(currentTab, c -> c.layoutOptions(BorderPosition.BOTTOM)); + this.addChild(currentTab, c -> c.layoutOptions(BorderPosition.CENTER)); } } @@ -37,8 +37,15 @@ public AOContainer(ScenarioEditorScreen screen) { super(BorderLayout.INSTANCE); this.screen = screen; + this.addChild(new Component() { + @Override + public Size computeIdealSize(Size size) { + return new Size(10, 10); + } + }, c -> c.layoutOptions(BorderPosition.LEFT)); + this.addChild(new AOTabContainer(this), c -> c.layoutOptions(BorderPosition.TOP)); - this.addChild(new AoMediaTab(), c -> c.layoutOptions(BorderPosition.BOTTOM)); + this.addChild(new AoMediaTab(), c -> c.layoutOptions(BorderPosition.CENTER)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 3dae2c1a..62b62747 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -7,14 +7,11 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.timeline.TimelineTimeSection; import oxy.bascenario.editor.containers.track.TrackListContainer; -import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; - @Accessors(fluent = true) public class TimelineContainer extends Container { private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(202, 74, 92); @@ -31,10 +28,6 @@ public TimelineContainer(ScenarioEditorScreen screen) { this.addChild(trackListContainer = new TrackListContainer(this), c -> c.layoutOptions(BorderPosition.CENTER)); } - public float scroll(float width) { - return trackListContainer.scrollX() / width; - } - @Override public void render(Renderer renderer, Size bounds) { renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); @@ -44,7 +37,7 @@ public void render(Renderer renderer, Size bounds) { } private void drawTimelineCursor(Renderer renderer, Size bounds) { - final float cursorX = legacyTimestampToPosition(screen.timestamp(), 0, bounds.width(), screen.scale(), screen.scroll()); + final float cursorX = screen.timestamp() * (screen.oneSecondWidth() / 1000f) - trackListContainer.scrollX(); renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); renderer.fillRect(cursorX, 0, 2, bounds.height(), TIMELINE_CURSOR_COLOR.withAlphaF(0.8f)); @@ -56,10 +49,6 @@ public Size computeIdealSize(Size constraints) { } public static float timestampToPosition(long timestamp, float offsetX, float size, float scale) { - return offsetX + (timestamp / (DEFAULT_MAX_TIME * scale)) * size; - } - - public static float legacyTimestampToPosition(long timestamp, float offsetX, float size, float scale, float scroll) { - return offsetX + ((timestamp - (DEFAULT_MAX_TIME * scale * scroll)) / (DEFAULT_MAX_TIME * scale)) * size; + return offsetX + (timestamp * size * scale); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java index 61605dcc..c475db7e 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java @@ -25,7 +25,7 @@ public class TimelineTabContainer extends Container { public TimelineTabContainer(final ScenarioEditorScreen screen) { super(AnchorLayout.INSTANCE); - final Slider slider = new Slider(0.01, 5, 0.01, 1); + final Slider slider = new Slider(0.5, 5, 0.01, 1); slider.barHeight().set(5f); slider.thumbWidth().set(20f); slider.thumbHeight().set(20f); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java index dbf5149f..ed19fa36 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java @@ -1,6 +1,5 @@ package oxy.bascenario.editor.containers.ao.tab; -import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.layout.list.VerticalListLayout; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java index b10493e8..e054a627 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -14,6 +14,7 @@ import oxy.bascenario.api.render.elements.emoticon.EmoticonType; import oxy.bascenario.api.render.elements.text.font.FontType; import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.editor.containers.object.FakeObjectComponent; import oxy.bascenario.util.TimeCompiler; import oxy.bascenario.util.components.TextWithName; @@ -39,20 +40,11 @@ private void add(ScenarioEditorScreen screen, Container container, String name, if (duration == Long.MAX_VALUE) { duration = 1000L; } - float screenWidth = ThinGL.windowInterface().getFramebufferWidth(); - final float width = (0.99f * screenWidth - 0.15625f * screenWidth) * (duration / (ScenarioEditorScreen.DEFAULT_MAX_TIME * screen.scale())); + long fDuration = duration; component.mouseDownListener().add((ignored, bounds) -> { - Button ghost = new Button(new Label(""), _ -> {}); - ghost.interactive(false); - ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); - - rivet().dragAndDropManager().startDrag(object, - ghost, - new Size(width, 30), - 0, - -30 / 2f - ); + FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration, 0, 0); + rivet().dragAndDropManager().startDrag(object, ghost, 0, -60 / 2f); return true; }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java index 61f4e472..e49d8cfd 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java @@ -1,17 +1,16 @@ package oxy.bascenario.editor.containers.ao.tab; -import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Size; -import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.render.elements.text.AnimatedText; import oxy.bascenario.api.render.elements.text.Text; import oxy.bascenario.api.render.elements.text.TextSegment; import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.editor.containers.object.FakeObjectComponent; import oxy.bascenario.util.components.TypingLabel; import java.util.List; @@ -21,19 +20,14 @@ public AoTextTab(ScenarioEditorScreen screen) { final Container container = new Container(new VerticalListLayout(10, false)); super(container); - float screenWidth = ThinGL.windowInterface().getFramebufferWidth(); - final float width = (0.99f * screenWidth - 0.15625f * screenWidth) * (1000L / (ScenarioEditorScreen.DEFAULT_MAX_TIME * screen.scale())); - final Button defaultText = new Button(new Label("Default Text").scale(0.7f), ignored -> {}); - defaultText.mouseDownListener().add((ignored, bounds) -> { - Button ghost = new Button(new Label("").scale(0.7f), _ -> {}); - ghost.interactive(false); - ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); + defaultText.mouseDownListener().add((ignored, size) -> { + final Object object = new Text(List.of(TextSegment.builder().text("Default Text").build()), 42); + FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L, 0, 0); rivet().dragAndDropManager().startDrag( - new Text(List.of(TextSegment.builder().text("Default Text").build()), 42), + object, ghost, - new Size(width, 30), 0, -30 / 2f ); @@ -44,16 +38,14 @@ public AoTextTab(ScenarioEditorScreen screen) { final Button typingText = new Button(new TypingLabel("Typing Text").scale(0.7f), ignored -> {}); typingText.mouseDownListener().add((ignored, bounds) -> { - Button ghost = new Button(new Label("").scale(0.7f), _ -> {}); - ghost.interactive(false); - ghost.inactiveColor().set(Color.fromRGB(35, 35, 35).withAlphaF(0.5f)); + final Object object = new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42); + FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L, 0, 0); rivet().dragAndDropManager().startDrag( - new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42), + object, ghost, - new Size(width, 30), 0, - -30 / 2f + -60 / 2f ); return true; }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java new file mode 100644 index 00000000..4868407f --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java @@ -0,0 +1,41 @@ +package oxy.bascenario.editor.containers.object; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.track.TrackContainer; +import oxy.bascenario.util.NameUtils; + +@AllArgsConstructor +@Accessors(fluent = true) +public class FakeObjectComponent extends Component { + private final TimelineContainer timelineContainer; + private final Object object; + private final long duration; + + @Getter + private float offsetX, offsetY; + + @Override + public void render(Renderer renderer, Size bounds) { + renderer.translate(offsetX, offsetY, () -> { + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(30, 30, 30)); + renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 2, Color.fromRGB(202, 74, 92).darker()); + + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + }); + } + + @Override + public Size computeIdealSize(Size constraints) { + float widthToAMil = timelineContainer.screen().oneSecondWidth() / 1000f; + return new Size(widthToAMil * duration, 60); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 5515ae46..9dab9c67 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -56,7 +56,7 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { @Override public Size computeIdealSize(Size constraints) { - final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.timelineContainer().screen().scale(); - return new Size((object.duration / maxTime) * parentTrack.cachedWidth(), constraints.height()); + float widthToAMil = parent.timelineContainer().screen().oneSecondWidth() / 1000f; + return new Size(widthToAMil * object.duration, constraints.height()); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java index 1ee1dad3..5cf99dca 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java @@ -8,44 +8,37 @@ import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.TimelineContainer; -import static oxy.bascenario.editor.ScenarioEditorScreen.DEFAULT_MAX_TIME; - @RequiredArgsConstructor public class TimelineTimeSection extends Component { private final TimelineContainer parent; @Override - public void render(Renderer renderer, Size bounds) { + public void render(Renderer renderer, Size size) { float x = -parent.trackListContainer().scrollX(); - while (!(x >= bounds.width())) { + while (!(x >= size.width())) { renderer.fillRect(x, 0, 1, 10, Color.WHITE); - final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.screen().scale(); - float time = (x / bounds.width()) * maxTime; - time += (ScenarioEditorScreen.DEFAULT_MAX_TIME * parent.screen().scroll()); + float time = (x + parent.trackListContainer().scrollX()) / parent.screen().oneSecondWidth(); - float seconds = Math.abs(time) / 1000f; - seconds = Math.round(seconds * 100.0f) / 100.0f; - ShapedText text = this.rivet().backend().font().shapeText(seconds + "s", Color.WHITE); + time = Math.round(time * 100.0f) / 100.0f; + ShapedText text = this.rivet().backend().font().shapeText(time + "s", Color.WHITE); float currentX = x; - renderer.scale(0.4f, () -> renderer.text(text, currentX / 0.4f, (25 / 35f) * bounds.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + renderer.scale(0.4f, () -> renderer.text(text, currentX / 0.4f, (25 / 35f) * size.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - x += bounds.width() / 5f; + x += parent.screen().oneSecondWidth(); } } @Override protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { - final float ratio = Math.min((event.x() - 1.25f) / (bounds.width()), 1); - long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); + float x = parent.trackListContainer().scrollX() + event.x() - 1.25f; + long newTimestamp = (long) (x / (parent.screen().oneMilSecondWidth())); parent.screen().timestamp(Math.max(0, newTimestamp)); } @@ -55,8 +48,8 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { - final float ratio = Math.min((event.x() - 1.25f) / (bounds.width()) , 1); - long newTimestamp = (long) (DEFAULT_MAX_TIME * parent.screen().scale() * parent.screen().scroll() + ratio * DEFAULT_MAX_TIME * parent.screen().scale()); + float x = parent.trackListContainer().scrollX() + event.x() - 1.25f; + long newTimestamp = (long) (x / (parent.screen().oneMilSecondWidth())); parent.screen().timestamp(Math.max(0, newTimestamp)); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 0af3dd85..34c6c27c 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -31,9 +31,6 @@ public class TrackContainer extends Container { @Getter private float height = 60f; - @Getter - private float cachedWidth; - public TrackContainer(TimelineContainer container) { super(AbsoluteLayout.INSTANCE); this.container = container; @@ -48,17 +45,16 @@ public Size computeIdealSize(Size constraints) { } @Override - public void render(Renderer renderer, Size bounds) { - renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(40, 40, 40)); - super.render(renderer, bounds); + public void render(Renderer renderer, Size size) { + renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(40, 40, 40)); + super.render(renderer, size); } @Override protected boolean onComponentDrop(DropEvent event, Size bounds) { container.screen().playing(false); - final float TOTAL_TIME = ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale(); - long time = (long) (TOTAL_TIME * (event.x() / cachedWidth)); + long time = (long) (event.x() / container.screen().oneMilSecondWidth()); long duration = TimeCompiler.compileTime(event.dragData()); if (duration == Long.MAX_VALUE) { @@ -130,22 +126,22 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { this.container.trackListContainer().selectionManager().prevX(event.x()); this.container.trackListContainer().selectionManager().prevY(event.y()); - final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale(); +// final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale(); for (Component baseComponent : children()) { if (!(baseComponent instanceof ObjectComponent component) || !this.container.trackListContainer().selectionManager().isSelected(component)) { continue; } - component.object().start += (long) ((delta / cachedWidth) * maxTime); - component.object().start = Math.max(0, component.object().start); - float newX = TimelineContainer.timestampToPosition( - component.object().start, - 0, - cachedWidth, - container.screen().scale() - ); +// component.object().start += (long) ((delta / cachedWidth) * maxTime); +// component.object().start = Math.max(0, component.object().start); +// float newX = TimelineContainer.timestampToPosition( +// component.object().start, +// 0, +// cachedWidth, +// container.screen().scale() +// ); - component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); +// component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); } return super.onComponentMouseMove(event, size); @@ -153,9 +149,8 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { @Override public Size computeIdealSize(Size constraints) { - cachedWidth = constraints.width(); Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); - return new Size(Math.max(idealSize.width(), constraints.width()), height); + return new Size(idealSize.width(), height); } public int index() { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 03b0b451..106f76d5 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -46,7 +46,7 @@ public TrackListContainer(TimelineContainer timelineContainer) { @Override public void render(Renderer renderer, Size bounds) { if (prevWidth != bounds.width()) { - resize(bounds.width()); + recalculateObjectPosition(); this.requestLayoutRecalculation(); } @@ -80,7 +80,7 @@ protected boolean onComponentDrop(DropEvent event, Size bounds) { if (duration == Long.MAX_VALUE) { duration = 1000L; } - add(bounds.width(), component, new ObjectOrEvent(0, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true)); + add(component, new ObjectOrEvent(0, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true)); } return super.onComponentDrop(event, bounds); @@ -91,12 +91,12 @@ public Size computeIdealSize(Size constraints) { return constraints; } - private void add(float width, TrackContainer container, ObjectOrEvent object) { - float newX = TimelineContainer.timestampToPosition(object.start, 0, width, timelineContainer.screen().scale()); + private void add(TrackContainer container, ObjectOrEvent object) { + float newX = TimelineContainer.timestampToPosition(object.start, 0, timelineContainer.screen().oneMilSecondWidth(), timelineContainer.screen().scale()); container.addChild(new ObjectComponent(this, container, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(newX, 0))); } - public void resize(float width) { + public void recalculateObjectPosition() { for (Component component : container.children()) { if (!(component instanceof TrackContainer track)) { continue; @@ -107,7 +107,7 @@ public void resize(float width) { continue; } - float newX = TimelineContainer.timestampToPosition(object.object().start, 0, width, timelineContainer.screen().scale()); + float newX = TimelineContainer.timestampToPosition(object.object().start, 0, timelineContainer.screen().oneMilSecondWidth(), timelineContainer.screen().scale()); child.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); } } diff --git a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java b/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java index e0092ea0..5c5f3941 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java +++ b/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java @@ -66,15 +66,15 @@ protected void onComponentAdded() { } @Override - public void onThemeChanged() { + protected void onComponentThemeChanged() { this.reshape = true; } @Override public void render(final Renderer renderer, final Size bounds) { this.shapeText(); - float x = this.horizontalOrigin.offset(bounds.width() / this.scale); - float y = this.verticalOrigin.offset(bounds.height() / this.scale); + float x = this.horizontalOrigin.position(bounds.width() / this.scale); + float y = this.verticalOrigin.position(bounds.height() / this.scale); float progress = (System.currentTimeMillis() % 3000) / 3000f; net.lenni0451.rivet.backend.text.ShapedText text1 = this.rivet().backend().font().shapeText(this.text.substring(0, MathUtils.ceilInt(progress * this.text.length())), this.rivet().theme().get(Theme.TEXT_COLOR)); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 15dbf628..c4d80d1a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "2bdf13b2f7" +rivet = "eff65bbbb5" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } From 9876e8aca9d665637b4467b8fc15d070d0ec3f04 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 12 Jul 2026 16:51:49 +0700 Subject: [PATCH 52/71] stuff. --- .../java/oxy/bascenario/EngineRenderer.java | 1 + .../java/oxy/bascenario/utils}/RivetUtil.java | 14 ++++++++++- .../oxy/bascenario/utils/math/MathUtils.java | 7 ++++++ .../containers/TimelineTabContainer.java | 3 +-- .../editor/containers/ao/tab/AoObjectTab.java | 8 ++----- .../editor/containers/ao/tab/AoTextTab.java | 2 +- .../object/FakeObjectComponent.java | 4 +--- .../containers/object/ObjectComponent.java | 3 +-- .../containers/track/TrackContainer.java | 24 ++++++++----------- .../containers/track/TrackListContainer.java | 2 +- .../track/tab/TrackTabContainer.java | 6 +---- .../track/tab/TrackTabListContainer.java | 5 +--- .../bascenario/{util => utils}/NameUtils.java | 2 +- .../{util => utils}/TimeCompiler.java | 2 +- .../components/ButtonImage.java | 5 +--- .../components/PlayButton.java | 3 +-- .../components/TextWithName.java | 3 +-- .../components/TypingLabel.java | 3 +-- 18 files changed, 46 insertions(+), 51 deletions(-) rename {editor/src/main/java/oxy/bascenario/util => core/src/main/java/oxy/bascenario/utils}/RivetUtil.java (73%) rename editor/src/main/java/oxy/bascenario/{util => utils}/NameUtils.java (99%) rename editor/src/main/java/oxy/bascenario/{util => utils}/TimeCompiler.java (99%) rename editor/src/main/java/oxy/bascenario/{util => utils}/components/ButtonImage.java (94%) rename editor/src/main/java/oxy/bascenario/{util => utils}/components/PlayButton.java (96%) rename editor/src/main/java/oxy/bascenario/{util => utils}/components/TextWithName.java (94%) rename editor/src/main/java/oxy/bascenario/{util => utils}/components/TypingLabel.java (97%) diff --git a/core/src/main/java/oxy/bascenario/EngineRenderer.java b/core/src/main/java/oxy/bascenario/EngineRenderer.java index da9a0dc4..aeb25497 100644 --- a/core/src/main/java/oxy/bascenario/EngineRenderer.java +++ b/core/src/main/java/oxy/bascenario/EngineRenderer.java @@ -107,6 +107,7 @@ public void create() { this.rivet = new Rivet(this.backend, FullSizeLayout.INSTANCE, new Size(ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight())); setupRivetCallbacks(); + RivetUtil.setupTheme(rivet); this.setScreen(this.initialScreen); diff --git a/editor/src/main/java/oxy/bascenario/util/RivetUtil.java b/core/src/main/java/oxy/bascenario/utils/RivetUtil.java similarity index 73% rename from editor/src/main/java/oxy/bascenario/util/RivetUtil.java rename to core/src/main/java/oxy/bascenario/utils/RivetUtil.java index 50ecde4e..fdb377a3 100644 --- a/editor/src/main/java/oxy/bascenario/util/RivetUtil.java +++ b/core/src/main/java/oxy/bascenario/utils/RivetUtil.java @@ -1,16 +1,28 @@ -package oxy.bascenario.util; +package oxy.bascenario.utils; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.Rivet; import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.ComboBox; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.theme.Theme; import oxy.bascenario.utils.math.Pair; import java.util.List; public class RivetUtil { + public static void setupTheme(Rivet rivet) { + rivet.theme(new Theme() { + @Override + protected void addValues(final Rivet rivet, final Values values) { + values.put(Theme.SLIDER_THUMB_COLOR, Color.WHITE); + } + }); + } + public static void dropDownList(Container container, String name, AnchorLayoutOptions layout, List> lists) { final Container buttons = new Container(new VerticalListLayout(1, true)); diff --git a/core/src/main/java/oxy/bascenario/utils/math/MathUtils.java b/core/src/main/java/oxy/bascenario/utils/math/MathUtils.java index 9f5eb379..ac9e1004 100644 --- a/core/src/main/java/oxy/bascenario/utils/math/MathUtils.java +++ b/core/src/main/java/oxy/bascenario/utils/math/MathUtils.java @@ -5,6 +5,13 @@ public class MathUtils { public static Vector4f findBackgroundRender(Vector2f minSize) { +// if (minSize.x > 1920 && minSize.y > 1080) { +// int centerX = (int) (1920f / 2F - (minSize.x / 2)); +// int centerY = (int) (1080f / 2F - (minSize.y / 2)); +// +// return new Vector4f(centerX, centerY, minSize.x, minSize.y); +// } + float bestRatio = Math.max(1920f / minSize.x, 1080f / minSize.y); float newSizeX = minSize.x * bestRatio, newSizeY = minSize.y * bestRatio; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java index c475db7e..5038974f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java @@ -9,10 +9,9 @@ import net.lenni0451.rivet.component.impl.slider.Slider; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.util.components.PlayButton; +import oxy.bascenario.utils.components.PlayButton; @Accessors(fluent = true) public class TimelineTabContainer extends Container { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java index e054a627..385ef5c6 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -1,13 +1,9 @@ package oxy.bascenario.editor.containers.ao.tab; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Size; -import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.render.elements.LocationInfo; import oxy.bascenario.api.render.elements.Preview; import oxy.bascenario.api.render.elements.emoticon.Emoticon; @@ -15,8 +11,8 @@ import oxy.bascenario.api.render.elements.text.font.FontType; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.object.FakeObjectComponent; -import oxy.bascenario.util.TimeCompiler; -import oxy.bascenario.util.components.TextWithName; +import oxy.bascenario.utils.TimeCompiler; +import oxy.bascenario.utils.components.TextWithName; public class AoObjectTab extends ScrollContainer { public AoObjectTab(ScenarioEditorScreen screen) { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java index e49d8cfd..d6ef56ca 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java @@ -11,7 +11,7 @@ import oxy.bascenario.api.render.elements.text.TextSegment; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.object.FakeObjectComponent; -import oxy.bascenario.util.components.TypingLabel; +import oxy.bascenario.utils.components.TypingLabel; import java.util.List; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java index 4868407f..cf718876 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java @@ -8,10 +8,8 @@ import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.editor.containers.track.TrackContainer; -import oxy.bascenario.util.NameUtils; +import oxy.bascenario.utils.NameUtils; @AllArgsConstructor @Accessors(fluent = true) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 9dab9c67..a6a792a1 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -9,10 +9,9 @@ import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.track.TrackContainer; import oxy.bascenario.editor.containers.track.TrackListContainer; -import oxy.bascenario.util.NameUtils; +import oxy.bascenario.utils.NameUtils; @Accessors(fluent = true) public class ObjectComponent extends Component { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 34c6c27c..e3f5f268 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -6,22 +6,19 @@ import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.dragdrop.DropEvent; import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.object.ObjectComponent; import oxy.bascenario.editor.containers.object.ObjectOrEvent; import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; -import oxy.bascenario.util.TimeCompiler; +import oxy.bascenario.utils.TimeCompiler; @Accessors(fluent = true) public class TrackContainer extends Container { @@ -126,22 +123,21 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { this.container.trackListContainer().selectionManager().prevX(event.x()); this.container.trackListContainer().selectionManager().prevY(event.y()); -// final float maxTime = ScenarioEditorScreen.DEFAULT_MAX_TIME * container.screen().scale(); for (Component baseComponent : children()) { if (!(baseComponent instanceof ObjectComponent component) || !this.container.trackListContainer().selectionManager().isSelected(component)) { continue; } -// component.object().start += (long) ((delta / cachedWidth) * maxTime); -// component.object().start = Math.max(0, component.object().start); -// float newX = TimelineContainer.timestampToPosition( -// component.object().start, -// 0, -// cachedWidth, -// container.screen().scale() -// ); + component.object().start += (long) (delta / container().screen().oneMilSecondWidth()); + component.object().start = Math.max(0, component.object().start); + float newX = TimelineContainer.timestampToPosition( + component.object().start, + 0, + container().screen().oneMilSecondWidth(), + container.screen().scale() + ); -// component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); + component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); } return super.onComponentMouseMove(event, size); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 106f76d5..c85861a5 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -24,7 +24,7 @@ import oxy.bascenario.editor.containers.object.ObjectOrEvent; import oxy.bascenario.editor.containers.selection.SelectionManager; import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; -import oxy.bascenario.util.TimeCompiler; +import oxy.bascenario.utils.TimeCompiler; @Accessors(fluent = true) public class TrackListContainer extends ScrollContainer { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java index f29b65bb..1eef1ad0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java @@ -3,18 +3,14 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.impl.Image; import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.layout.Layout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.layout.anchor.AnchorLayout; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.Base; import oxy.bascenario.editor.containers.track.TrackContainer; -import oxy.bascenario.util.components.ButtonImage; +import oxy.bascenario.utils.components.ButtonImage; public class TrackTabContainer extends Container { private final TrackContainer container; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java index 5eec6ba3..6e7dfc58 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -5,7 +5,6 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.layout.border.BorderLayout; @@ -14,9 +13,7 @@ import oxy.bascenario.Base; import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.track.TrackContainer; -import oxy.bascenario.util.components.ButtonImage; - -import java.lang.reflect.Field; +import oxy.bascenario.utils.components.ButtonImage; public class TrackTabListContainer extends Container { private final ScrollContainer scrollContainer; diff --git a/editor/src/main/java/oxy/bascenario/util/NameUtils.java b/editor/src/main/java/oxy/bascenario/utils/NameUtils.java similarity index 99% rename from editor/src/main/java/oxy/bascenario/util/NameUtils.java rename to editor/src/main/java/oxy/bascenario/utils/NameUtils.java index 172a7904..d2885a25 100644 --- a/editor/src/main/java/oxy/bascenario/util/NameUtils.java +++ b/editor/src/main/java/oxy/bascenario/utils/NameUtils.java @@ -1,4 +1,4 @@ -package oxy.bascenario.util; +package oxy.bascenario.utils; import oxy.bascenario.api.event.*; import oxy.bascenario.api.event.animation.PlayAnimationEvent; diff --git a/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java b/editor/src/main/java/oxy/bascenario/utils/TimeCompiler.java similarity index 99% rename from editor/src/main/java/oxy/bascenario/util/TimeCompiler.java rename to editor/src/main/java/oxy/bascenario/utils/TimeCompiler.java index 260100b2..87764b67 100644 --- a/editor/src/main/java/oxy/bascenario/util/TimeCompiler.java +++ b/editor/src/main/java/oxy/bascenario/utils/TimeCompiler.java @@ -1,4 +1,4 @@ -package oxy.bascenario.util; +package oxy.bascenario.utils; import oxy.bascenario.api.event.ScreenTransitionEvent; import oxy.bascenario.api.event.background.ClearBackgroundEvent; diff --git a/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java b/editor/src/main/java/oxy/bascenario/utils/components/ButtonImage.java similarity index 94% rename from editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java rename to editor/src/main/java/oxy/bascenario/utils/components/ButtonImage.java index 648f874d..a828b790 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/ButtonImage.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/ButtonImage.java @@ -1,4 +1,4 @@ -package oxy.bascenario.util.components; +package oxy.bascenario.utils.components; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -11,12 +11,9 @@ import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.theme.ThemeOption; -import net.raphimc.thingl.ThinGL; import net.raphimc.thingl.gl.resource.image.texture.impl.Texture2D; import oxy.bascenario.utils.animation.math.ColorAnimations; -import static oxy.bascenario.utils.thingl.ThinGLUtils.GLOBAL_RENDER_STACK; - @RequiredArgsConstructor @Accessors(fluent = true, chain = true, makeFinal = true) public class ButtonImage extends Component { diff --git a/editor/src/main/java/oxy/bascenario/util/components/PlayButton.java b/editor/src/main/java/oxy/bascenario/utils/components/PlayButton.java similarity index 96% rename from editor/src/main/java/oxy/bascenario/util/components/PlayButton.java rename to editor/src/main/java/oxy/bascenario/utils/components/PlayButton.java index bd14a8b4..7a6cea07 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/PlayButton.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/PlayButton.java @@ -1,6 +1,5 @@ -package oxy.bascenario.util.components; +package oxy.bascenario.utils.components; -import it.unimi.dsi.fastutil.booleans.BooleanPredicate; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; diff --git a/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java b/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java similarity index 94% rename from editor/src/main/java/oxy/bascenario/util/components/TextWithName.java rename to editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java index c56d1596..40271218 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/TextWithName.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java @@ -1,4 +1,4 @@ -package oxy.bascenario.util.components; +package oxy.bascenario.utils.components; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.Texture; @@ -6,7 +6,6 @@ import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.backend.thingl.ThinGLTexture; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.Base; diff --git a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java b/editor/src/main/java/oxy/bascenario/utils/components/TypingLabel.java similarity index 97% rename from editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java rename to editor/src/main/java/oxy/bascenario/utils/components/TypingLabel.java index 5c5f3941..c3d236dd 100644 --- a/editor/src/main/java/oxy/bascenario/util/components/TypingLabel.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/TypingLabel.java @@ -1,4 +1,4 @@ -package oxy.bascenario.util.components; +package oxy.bascenario.utils.components; import lombok.Getter; import lombok.Setter; @@ -7,7 +7,6 @@ import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import net.lenni0451.rivet.theme.Theme; From 6ad34db87922aea11e69e206043cae808be60b76 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 12 Jul 2026 17:36:54 +0700 Subject: [PATCH 53/71] start on reworking the ui. --- .../bascenario/editor/ScenarioEditorScreen.java | 17 +++++++++-------- .../editor/containers/AOContainer.java | 12 ++++++++++-- .../editor/containers/TimelineContainer.java | 2 +- .../containers/object/FakeObjectComponent.java | 4 ++-- .../containers/object/ObjectComponent.java | 6 +++--- .../containers/track/TrackListContainer.java | 2 +- .../containers/track/tab/TrackTabContainer.java | 2 +- .../track/tab/TrackTabListContainer.java | 2 +- .../bascenario/utils/components/PlayButton.java | 2 +- .../utils/components/TextWithName.java | 2 +- 10 files changed, 30 insertions(+), 21 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 7ea0efa4..d1f0d00f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -82,7 +82,7 @@ public void renderBehindRivet() { } since = System.currentTimeMillis(); - ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(50, 50, 50)); + ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(22, 22, 22)); } @Override @@ -90,29 +90,30 @@ public void init(Rivet rivet) { Container container = new Container(AnchorLayout.INSTANCE); container.addChild(new AOContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.06f).withAnchorMaxX(0.2f).withAnchorMaxY(0.6f)); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.005f).withAnchorMinY(0.02f).withAnchorMaxX(0.20f).withAnchorMaxY(0.6f)); }); + // preview container.addChild(new SolidColor(), c -> { - c.color(Color.fromRGB(35, 35, 35)); - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.06f).withAnchorMaxX(0.8f).withAnchorMaxY(0.6f)); + c.color(Color.BLACK); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.02f).withAnchorMaxX(0.8f).withAnchorMaxY(0.55f)); }); container.addChild(new SolidColor(), c -> { - c.color(Color.fromRGB(35, 35, 35)); +// c.color(Color.fromRGB(35, 35, 35)); c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); container.addChild(timelineContainer = new TimelineContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.15625f).withAnchorMinY(0.655f).withAnchorMaxX(0.99f).withAnchorMaxY(0.99f)); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.15525f).withAnchorMinY(0.61f).withAnchorMaxX(1).withAnchorMaxY(1)); }); container.addChild(new TrackTabListContainer(trackTabContainer = new Container(new VerticalListLayout(3, false)), timelineContainer), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.655f).withAnchorMaxX(0.15625f).withAnchorMaxY(0.99f)); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0).withAnchorMinY(0.61f).withAnchorMaxX(0.15525f).withAnchorMaxY(1)); }); container.addChild(timelineTabContainer = new TimelineTabContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.605f).withAnchorMaxX(0.99f).withAnchorMaxY(0.65f)); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.55f).withAnchorMaxX(0.8f).withAnchorMaxY(0.6f)); }); // RivetUtil.dropDownList(container, "Timeline", AnchorLayoutOptions.EMPTY.withAnchorMinX(0.015f).withAnchorMinY(0.01f), diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index 43a0e3a7..4645563d 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -5,8 +5,11 @@ import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.DecoratedContainer; +import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; +import net.lenni0451.rivet.math.Padding; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.ao.AOTabContainer; @@ -37,14 +40,19 @@ public AOContainer(ScenarioEditorScreen screen) { super(BorderLayout.INSTANCE); this.screen = screen; + // Just to take space. this.addChild(new Component() { @Override public Size computeIdealSize(Size size) { - return new Size(10, 10); + return new Size(20, 20); } }, c -> c.layoutOptions(BorderPosition.LEFT)); - this.addChild(new AOTabContainer(this), c -> c.layoutOptions(BorderPosition.TOP)); + this.addChild(new DecoratedContainer(new SolidColor(), new AOTabContainer(this)), c -> { + c.layoutOptions(BorderPosition.TOP); + c.innerPadding(new Padding(20, 10, 20, 0)); + }); + this.addChild(new AoMediaTab(), c -> c.layoutOptions(BorderPosition.CENTER)); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index 62b62747..c210ca72 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -14,7 +14,7 @@ @Accessors(fluent = true) public class TimelineContainer extends Container { - private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(202, 74, 92); + private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(145, 218, 255); @Getter private final TrackListContainer trackListContainer; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java index cf718876..c6bcb988 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java @@ -24,8 +24,8 @@ public class FakeObjectComponent extends Component { @Override public void render(Renderer renderer, Size bounds) { renderer.translate(offsetX, offsetY, () -> { - renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(30, 30, 30)); - renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 2, Color.fromRGB(202, 74, 92).darker()); + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); + renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, Color.fromRGB(145, 218, 255).darker()); renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index a6a792a1..b78c1a61 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -15,7 +15,7 @@ @Accessors(fluent = true) public class ObjectComponent extends Component { - private static final Color OBJECT_COLOR = Color.fromRGB(202, 74, 92).darker(); + private static final Color OBJECT_COLOR = Color.fromRGB(145, 218, 255).darker(); private final TrackListContainer parent; @Getter @@ -31,8 +31,8 @@ public ObjectComponent(TrackListContainer parent, TrackContainer parentTrack, Ob @Override public void render(Renderer renderer, Size bounds) { - renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(30, 30, 30)); - renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); + renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index c85861a5..0f17579f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -53,7 +53,7 @@ public void render(Renderer renderer, Size bounds) { prevWidth = bounds.width(); if (container.children().isEmpty()) { - renderer.fillRoundedRect(0, 0, 300, 60f, 5, Color.fromRGB(50, 50, 50)); + renderer.fillRect(0, 0, 300, 60f, Color.fromRGB(50, 50, 50)); renderer.scale(0.4f, () -> { ShapedText text = this.rivet().backend().font().shapeText("Drop anything here to get started.", Color.WHITE); renderer.text(text, 50,(60f / 2f - (this.rivet().backend().font().height() * 0.4f) / 2f) / 0.4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java index 1eef1ad0..7c35f20e 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java @@ -27,7 +27,7 @@ public TrackTabContainer(TrackContainer container) { this.container.container().screen().trackTabContainer().removeChild(this); this.container.container().trackListContainer().container().removeChild(container); }); - trash.hoverColor().set(Color.fromRGB(202, 74, 92)); + trash.hoverColor().set(Color.fromRGB(145, 218, 255)); this.addChild(trash, image -> image.layoutOptions(new AbsoluteLayoutOptions(5, 35))); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java index 6e7dfc58..1f697655 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -55,7 +55,7 @@ public UpperContainer(TimelineContainer timelineContainer, Container container) timelineContainer.trackListContainer().container().addChild(component); container.addChild(new TrackTabContainer(component)); }); - add.hoverColor().set(Color.fromRGB(202, 74, 92)); + add.hoverColor().set(Color.fromRGB(145, 218, 255)); this.addChild(add, c -> c.layoutOptions(new AbsoluteLayoutOptions(5, 5))); } diff --git a/editor/src/main/java/oxy/bascenario/utils/components/PlayButton.java b/editor/src/main/java/oxy/bascenario/utils/components/PlayButton.java index 7a6cea07..5b37ef0b 100644 --- a/editor/src/main/java/oxy/bascenario/utils/components/PlayButton.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/PlayButton.java @@ -65,7 +65,7 @@ public Size computeIdealSize(final Size constraints) { @Override protected void onComponentMouseEnter() { - color.set(Color.fromRGB(202, 74, 92), this.blendDuration.value()); + color.set(Color.fromRGB(145, 218, 255), this.blendDuration.value()); } @Override diff --git a/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java b/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java index 40271218..4b440736 100644 --- a/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java @@ -21,7 +21,7 @@ public TextWithName(String name, String path) { @Override public void render(Renderer renderer, Size bounds) { - renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, Color.fromRGB(25, 25, 25)); + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(25, 25, 25)); renderer.image(this.texture, 10, 10, 255, 141, Color.WHITE); ShapedText text = this.rivet().backend().font().shapeText(name, Color.WHITE); From d08e689aa7a11bf196d86374c61de9ea1e439fdb Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 12 Jul 2026 17:47:28 +0700 Subject: [PATCH 54/71] Fix dragging object in. --- .../editor/containers/ao/tab/AoObjectTab.java | 2 +- .../editor/containers/ao/tab/AoTextTab.java | 4 +- .../object/FakeObjectComponent.java | 2 + .../containers/track/TrackContainer.java | 73 ++++++++++--------- .../containers/track/TrackListContainer.java | 36 ++++----- 5 files changed, 59 insertions(+), 58 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java index 385ef5c6..fbcd8b1d 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -40,7 +40,7 @@ private void add(ScenarioEditorScreen screen, Container container, String name, long fDuration = duration; component.mouseDownListener().add((ignored, bounds) -> { FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration, 0, 0); - rivet().dragAndDropManager().startDrag(object, ghost, 0, -60 / 2f); + rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); return true; }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java index d6ef56ca..8ddf54f0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java @@ -26,7 +26,7 @@ public AoTextTab(ScenarioEditorScreen screen) { FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L, 0, 0); rivet().dragAndDropManager().startDrag( - object, + ghost, ghost, 0, -30 / 2f @@ -42,7 +42,7 @@ public AoTextTab(ScenarioEditorScreen screen) { FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L, 0, 0); rivet().dragAndDropManager().startDrag( - object, + ghost, ghost, 0, -60 / 2f diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java index c6bcb988..6013c370 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java @@ -15,6 +15,8 @@ @Accessors(fluent = true) public class FakeObjectComponent extends Component { private final TimelineContainer timelineContainer; + + @Getter private final Object object; private final long duration; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index e3f5f268..08443c4b 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -15,6 +15,7 @@ import net.lenni0451.rivet.math.Size; import oxy.bascenario.api.render.RenderLayer; import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.object.FakeObjectComponent; import oxy.bascenario.editor.containers.object.ObjectComponent; import oxy.bascenario.editor.containers.object.ObjectOrEvent; import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; @@ -51,52 +52,56 @@ public void render(Renderer renderer, Size size) { protected boolean onComponentDrop(DropEvent event, Size bounds) { container.screen().playing(false); - long time = (long) (event.x() / container.screen().oneMilSecondWidth()); - - long duration = TimeCompiler.compileTime(event.dragData()); - if (duration == Long.MAX_VALUE) { - duration = 1000L; - } + event.dragData().forEach(data -> { + if (!(data instanceof FakeObjectComponent dataComponent)) { + return; + } + long time = (long) ((event.x() + dataComponent.offsetX()) / container.screen().oneMilSecondWidth()); + long duration = TimeCompiler.compileTime(dataComponent.object()); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } - final ObjectOrEvent object = new ObjectOrEvent(time, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true); + final ObjectOrEvent object = new ObjectOrEvent(time, duration, dataComponent.object(), RenderLayer.ABOVE_DIALOGUE, true); - TrackContainer track = this; - while (true) { - boolean accepted = true; - for (Component baseComponent : track.children()) { - if (!(baseComponent instanceof ObjectComponent component)) { - continue; - } + TrackContainer track = this; + while (true) { + boolean accepted = true; + for (Component baseComponent : track.children()) { + if (!(baseComponent instanceof ObjectComponent component)) { + continue; + } - long otherMin = component.object().start; + long otherMin = component.object().start; - long maxTime = component.object().start + component.object().duration; - long otherMaxTime = component.object().start + component.object().duration; + long maxTime = component.object().start + component.object().duration; + long otherMaxTime = component.object().start + component.object().duration; - if (maxTime >= otherMin && time <= otherMaxTime) { - accepted = false; - break; + if (maxTime >= otherMin && time <= otherMaxTime) { + accepted = false; + break; + } } - } - if (!accepted) { - int size = container.trackListContainer().container().children().size(); - if (track.index() >= size - 1) { - track = new TrackContainer(container); - container.trackListContainer().container().addChild(track); - container.screen().trackTabContainer().addChild(new TrackTabContainer(track)); - break; + if (!accepted) { + int size = container.trackListContainer().container().children().size(); + if (track.index() >= size - 1) { + track = new TrackContainer(container); + container.trackListContainer().container().addChild(track); + container.screen().trackTabContainer().addChild(new TrackTabContainer(track)); + break; + } else { + track = (TrackContainer) container.trackListContainer().container().children().get(track.index() + 1); + } } else { - track = (TrackContainer) container.trackListContainer().container().children().get(track.index() + 1); + break; } - } else { - break; } - } - track.addChild(new ObjectComponent(this.container.trackListContainer(), track, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x(), 0))); + track.addChild(new ObjectComponent(this.container.trackListContainer(), track, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x() + dataComponent.offsetX(), 0))); + }); - return super.onComponentDrop(event, bounds); + return !event.dragData().isEmpty(); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 0f17579f..0a140aeb 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -39,6 +39,9 @@ public class TrackListContainer extends ScrollContainer { public TrackListContainer(TimelineContainer timelineContainer) { super(container = new Container(new VerticalListLayout(3, false)), true, true); this.timelineContainer = timelineContainer; + + final TrackContainer component = new TrackContainer(timelineContainer); + container.addChild(component); } private float prevWidth; @@ -51,15 +54,6 @@ public void render(Renderer renderer, Size bounds) { } prevWidth = bounds.width(); - - if (container.children().isEmpty()) { - renderer.fillRect(0, 0, 300, 60f, Color.fromRGB(50, 50, 50)); - renderer.scale(0.4f, () -> { - ShapedText text = this.rivet().backend().font().shapeText("Drop anything here to get started.", Color.WHITE); - renderer.text(text, 50,(60f / 2f - (this.rivet().backend().font().height() * 0.4f) / 2f) / 0.4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP); - }); - } - super.render(renderer, bounds); this.selectionManager.render(renderer, timelineContainer.childBounds(this)); @@ -70,18 +64,18 @@ protected boolean onComponentDrop(DropEvent event, Size bounds) { if (!container.children().isEmpty()) { return super.onComponentDrop(event, bounds); } - if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < 60f) { - final TrackContainer component = new TrackContainer(timelineContainer); - container.addChild(component); - - timelineContainer.screen().trackTabContainer().addChild(new TrackTabContainer(component)); - - long duration = TimeCompiler.compileTime(event.dragData()); - if (duration == Long.MAX_VALUE) { - duration = 1000L; - } - add(component, new ObjectOrEvent(0, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true)); - } +// if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < 60f) { +// final TrackContainer component = new TrackContainer(timelineContainer); +// container.addChild(component); +// +// timelineContainer.screen().trackTabContainer().addChild(new TrackTabContainer(component)); +// +// long duration = TimeCompiler.compileTime(event.dragData()); +// if (duration == Long.MAX_VALUE) { +// duration = 1000L; +// } +// add(component, new ObjectOrEvent(0, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true)); +// } return super.onComponentDrop(event, bounds); } From 245d483114c76d66918eba54eab4de50c15ee4dc Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 12 Jul 2026 17:48:14 +0700 Subject: [PATCH 55/71] oops. --- .../bascenario/editor/containers/track/TrackListContainer.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 0a140aeb..13ba18ec 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -39,9 +39,6 @@ public class TrackListContainer extends ScrollContainer { public TrackListContainer(TimelineContainer timelineContainer) { super(container = new Container(new VerticalListLayout(3, false)), true, true); this.timelineContainer = timelineContainer; - - final TrackContainer component = new TrackContainer(timelineContainer); - container.addChild(component); } private float prevWidth; From bc1fa6a6a55141ef20f59a2b97dd5e01d698ba47 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 12 Jul 2026 23:12:08 +0700 Subject: [PATCH 56/71] Added proper object dragging. --- .../editor/containers/ao/tab/AoObjectTab.java | 4 +- .../editor/containers/ao/tab/AoTextTab.java | 6 +- .../object/FakeObjectComponent.java | 41 ------ .../containers/object/ObjectComponent.java | 64 ++++++++- .../selection/SelectionManager.java | 3 - .../containers/track/TrackContainer.java | 104 +-------------- .../containers/track/TrackListContainer.java | 28 +--- .../utils/drag/FakeObjectComponent.java | 89 +++++++++++++ .../utils/drag/SecondaryDragComponent.java | 122 ++++++++++++++++++ 9 files changed, 281 insertions(+), 180 deletions(-) delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java create mode 100644 editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java create mode 100644 editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java index fbcd8b1d..0c248160 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -10,7 +10,7 @@ import oxy.bascenario.api.render.elements.emoticon.EmoticonType; import oxy.bascenario.api.render.elements.text.font.FontType; import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.editor.containers.object.FakeObjectComponent; +import oxy.bascenario.utils.drag.FakeObjectComponent; import oxy.bascenario.utils.TimeCompiler; import oxy.bascenario.utils.components.TextWithName; @@ -39,7 +39,7 @@ private void add(ScenarioEditorScreen screen, Container container, String name, long fDuration = duration; component.mouseDownListener().add((ignored, bounds) -> { - FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration, 0, 0); + FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration); rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); return true; }); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java index 8ddf54f0..0e6fa3d1 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java @@ -10,7 +10,7 @@ import oxy.bascenario.api.render.elements.text.Text; import oxy.bascenario.api.render.elements.text.TextSegment; import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.editor.containers.object.FakeObjectComponent; +import oxy.bascenario.utils.drag.FakeObjectComponent; import oxy.bascenario.utils.components.TypingLabel; import java.util.List; @@ -23,7 +23,7 @@ public AoTextTab(ScenarioEditorScreen screen) { final Button defaultText = new Button(new Label("Default Text").scale(0.7f), ignored -> {}); defaultText.mouseDownListener().add((ignored, size) -> { final Object object = new Text(List.of(TextSegment.builder().text("Default Text").build()), 42); - FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L, 0, 0); + FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L); rivet().dragAndDropManager().startDrag( ghost, @@ -39,7 +39,7 @@ public AoTextTab(ScenarioEditorScreen screen) { final Button typingText = new Button(new TypingLabel("Typing Text").scale(0.7f), ignored -> {}); typingText.mouseDownListener().add((ignored, bounds) -> { final Object object = new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42); - FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L, 0, 0); + FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L); rivet().dragAndDropManager().startDrag( ghost, diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java deleted file mode 100644 index 6013c370..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/FakeObjectComponent.java +++ /dev/null @@ -1,41 +0,0 @@ -package oxy.bascenario.editor.containers.object; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.utils.NameUtils; - -@AllArgsConstructor -@Accessors(fluent = true) -public class FakeObjectComponent extends Component { - private final TimelineContainer timelineContainer; - - @Getter - private final Object object; - private final long duration; - - @Getter - private float offsetX, offsetY; - - @Override - public void render(Renderer renderer, Size bounds) { - renderer.translate(offsetX, offsetY, () -> { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); - renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, Color.fromRGB(145, 218, 255).darker()); - - renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - }); - } - - @Override - public Size computeIdealSize(Size constraints) { - float widthToAMil = timelineContainer.screen().oneSecondWidth() / 1000f; - return new Size(widthToAMil * duration, 60); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index b78c1a61..30eb1b31 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -1,17 +1,25 @@ package oxy.bascenario.editor.containers.object; import lombok.Getter; +import lombok.Setter; import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.input.keyboard.ModifierKey; +import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.containers.track.TrackContainer; import oxy.bascenario.editor.containers.track.TrackListContainer; +import oxy.bascenario.utils.Launcher; import oxy.bascenario.utils.NameUtils; +import oxy.bascenario.utils.drag.SecondaryDragComponent; + +import java.util.ArrayList; +import java.util.List; @Accessors(fluent = true) public class ObjectComponent extends Component { @@ -19,24 +27,31 @@ public class ObjectComponent extends Component { private final TrackListContainer parent; @Getter - private final TrackContainer parentTrack; - @Getter private final ObjectOrEvent object; - public ObjectComponent(TrackListContainer parent, TrackContainer parentTrack, ObjectOrEvent object) { + @Setter + private boolean dragging; + + public ObjectComponent(TrackListContainer parent, ObjectOrEvent object) { this.parent = parent; - this.parentTrack = parentTrack; this.object = object; } @Override public void render(Renderer renderer, Size bounds) { + if (dragging) { + if (!rivet().dragAndDropManager().isDragging()) { + dragging = false; + } + return; + } + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - this.parent.selectionManager().addIfIntersects(this, parentTrack.childBounds(this)); + this.parent.selectionManager().addIfIntersects(this, parent().childBounds(this)); } @Override @@ -53,6 +68,43 @@ protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { return true; } + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { + if (!event.buttons().contains(MouseButton.LEFT)) { + return false; + } + + if (rivet().dragAndDropManager().isDragging()) { + return true; + } + + Rectangle clickedBounds = parent().childBounds(this); + + final List list = new ArrayList<>(); + for (ObjectComponent component : parent.selectionManager().objects()) { + Rectangle bounds = component.parent().childBounds(component); + float x = -event.x() + (bounds.x() - clickedBounds.x()); + float y = -event.y(); + if (component.parent() != parent()) { + y += (component.parent().absoluteBounds().y() - parent().absoluteBounds().y()); + } + + list.add(new SecondaryDragComponent.Data(component, x, y)); + component.dragging(true); + } + + // Ehhh using mouseX and mouseY from EngineRenderer is a bit hacky here... + final SecondaryDragComponent dragComponent = new SecondaryDragComponent(parent.timelineContainer(), (float) Launcher.WINDOW.mouseX, (float) Launcher.WINDOW.mouseY, list); + + rivet().dragAndDropManager().startDrag( + dragComponent, + dragComponent, + (float) -Launcher.WINDOW.mouseX, (float) -Launcher.WINDOW.mouseY + ); + + return true; + } + @Override public Size computeIdealSize(Size constraints) { float widthToAMil = parent.timelineContainer().screen().oneSecondWidth() / 1000f; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java index 27ec72b1..a9e10cd3 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -20,9 +20,6 @@ public boolean isSelected(ObjectComponent component) { return objects.contains(component); } - @Getter - private float prevX = Float.MAX_VALUE, prevY = Float.MAX_VALUE; - private float x, y; private float x1, y1; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java index 08443c4b..718e7666 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java @@ -7,19 +7,12 @@ import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.dragdrop.DropEvent; -import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.input.mouse.MouseButtonEvent; -import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.math.Size; -import oxy.bascenario.api.render.RenderLayer; import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.editor.containers.object.FakeObjectComponent; -import oxy.bascenario.editor.containers.object.ObjectComponent; -import oxy.bascenario.editor.containers.object.ObjectOrEvent; -import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; -import oxy.bascenario.utils.TimeCompiler; +import oxy.bascenario.utils.drag.FakeObjectComponent; +import oxy.bascenario.utils.drag.SecondaryDragComponent; @Accessors(fluent = true) public class TrackContainer extends Container { @@ -53,101 +46,16 @@ protected boolean onComponentDrop(DropEvent event, Size bounds) { container.screen().playing(false); event.dragData().forEach(data -> { - if (!(data instanceof FakeObjectComponent dataComponent)) { - return; + if (data instanceof FakeObjectComponent component) { + component.handle(this, event.x()); + } else if (data instanceof SecondaryDragComponent component) { + component.handle(this, event.x()); } - long time = (long) ((event.x() + dataComponent.offsetX()) / container.screen().oneMilSecondWidth()); - long duration = TimeCompiler.compileTime(dataComponent.object()); - if (duration == Long.MAX_VALUE) { - duration = 1000L; - } - - final ObjectOrEvent object = new ObjectOrEvent(time, duration, dataComponent.object(), RenderLayer.ABOVE_DIALOGUE, true); - - TrackContainer track = this; - while (true) { - boolean accepted = true; - for (Component baseComponent : track.children()) { - if (!(baseComponent instanceof ObjectComponent component)) { - continue; - } - - long otherMin = component.object().start; - - long maxTime = component.object().start + component.object().duration; - long otherMaxTime = component.object().start + component.object().duration; - - if (maxTime >= otherMin && time <= otherMaxTime) { - accepted = false; - break; - } - } - - if (!accepted) { - int size = container.trackListContainer().container().children().size(); - if (track.index() >= size - 1) { - track = new TrackContainer(container); - container.trackListContainer().container().addChild(track); - container.screen().trackTabContainer().addChild(new TrackTabContainer(track)); - break; - } else { - track = (TrackContainer) container.trackListContainer().container().children().get(track.index() + 1); - } - } else { - break; - } - } - - track.addChild(new ObjectComponent(this.container.trackListContainer(), track, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(event.x() + dataComponent.offsetX(), 0))); }); return !event.dragData().isEmpty(); } - @Override - protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { - this.container.trackListContainer().selectionManager().prevX(Float.MAX_VALUE); - this.container.trackListContainer().selectionManager().prevX(Float.MAX_VALUE); - - return super.onComponentMouseUp(event, size); - } - - @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { - if (!event.buttons().contains(MouseButton.LEFT)) { - return true; - } - - if (this.container.trackListContainer().selectionManager().prevX() == Float.MAX_VALUE || this.container.trackListContainer().selectionManager().prevY() == Float.MAX_VALUE) { - this.container.trackListContainer().selectionManager().prevX(event.x()); - this.container.trackListContainer().selectionManager().prevY(event.y()); - return true; - } - - float delta = (event.x() - this.container.trackListContainer().selectionManager().prevX()); - this.container.trackListContainer().selectionManager().prevX(event.x()); - this.container.trackListContainer().selectionManager().prevY(event.y()); - - for (Component baseComponent : children()) { - if (!(baseComponent instanceof ObjectComponent component) || !this.container.trackListContainer().selectionManager().isSelected(component)) { - continue; - } - - component.object().start += (long) (delta / container().screen().oneMilSecondWidth()); - component.object().start = Math.max(0, component.object().start); - float newX = TimelineContainer.timestampToPosition( - component.object().start, - 0, - container().screen().oneMilSecondWidth(), - container.screen().scale() - ); - - component.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); - } - - return super.onComponentMouseMove(event, size); - } - @Override public Size computeIdealSize(Size constraints) { Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 13ba18ec..8dffd991 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -56,37 +56,11 @@ public void render(Renderer renderer, Size bounds) { this.selectionManager.render(renderer, timelineContainer.childBounds(this)); } - @Override - protected boolean onComponentDrop(DropEvent event, Size bounds) { - if (!container.children().isEmpty()) { - return super.onComponentDrop(event, bounds); - } -// if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < 60f) { -// final TrackContainer component = new TrackContainer(timelineContainer); -// container.addChild(component); -// -// timelineContainer.screen().trackTabContainer().addChild(new TrackTabContainer(component)); -// -// long duration = TimeCompiler.compileTime(event.dragData()); -// if (duration == Long.MAX_VALUE) { -// duration = 1000L; -// } -// add(component, new ObjectOrEvent(0, duration, event.dragData(), RenderLayer.ABOVE_DIALOGUE, true)); -// } - - return super.onComponentDrop(event, bounds); - } - @Override public Size computeIdealSize(Size constraints) { return constraints; } - private void add(TrackContainer container, ObjectOrEvent object) { - float newX = TimelineContainer.timestampToPosition(object.start, 0, timelineContainer.screen().oneMilSecondWidth(), timelineContainer.screen().scale()); - container.addChild(new ObjectComponent(this, container, object), c -> c.layoutOptions(new AbsoluteLayoutOptions(newX, 0))); - } - public void recalculateObjectPosition() { for (Component component : container.children()) { if (!(component instanceof TrackContainer track)) { @@ -144,7 +118,7 @@ protected boolean onComponentMouseUp(MouseButtonEvent event, Size bounds) { @Override protected boolean onComponentKeyUp(KeyEvent event) { if (event.key() == Key.DELETE) { - selectionManager.objects().forEach(component -> component.parentTrack().removeChild(component)); + selectionManager.objects().forEach(component -> ((Container)component.parent()).removeChild(component)); selectionManager().objects().clear(); } diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java new file mode 100644 index 00000000..cc5efea5 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java @@ -0,0 +1,89 @@ +package oxy.bascenario.utils.drag; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.api.render.RenderLayer; +import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.object.ObjectComponent; +import oxy.bascenario.editor.containers.object.ObjectOrEvent; +import oxy.bascenario.editor.containers.track.TrackContainer; +import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; +import oxy.bascenario.utils.NameUtils; +import oxy.bascenario.utils.TimeCompiler; + +@AllArgsConstructor +@Accessors(fluent = true) +public class FakeObjectComponent extends Component { + private final TimelineContainer timelineContainer; + + @Getter + private final Object object; + private final long duration; + + @Override + public void render(Renderer renderer, Size bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); + renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, Color.fromRGB(145, 218, 255).darker()); + + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + } + + @Override + public Size computeIdealSize(Size constraints) { + float widthToAMil = timelineContainer.screen().oneSecondWidth() / 1000f; + return new Size(widthToAMil * duration, 60); + } + + public void handle(TrackContainer trackContainer, float x) { + long time = (long) (x / trackContainer.container().screen().oneMilSecondWidth()); + long duration = TimeCompiler.compileTime(object()); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } + + final ObjectOrEvent object = new ObjectOrEvent(time, duration, object(), RenderLayer.ABOVE_DIALOGUE, true); + + TrackContainer track = trackContainer; + while (true) { + boolean accepted = true; + for (Component baseComponent : track.children()) { + if (!(baseComponent instanceof ObjectComponent component)) { + continue; + } + + long otherMin = component.object().start; + + long maxTime = component.object().start + component.object().duration; + long otherMaxTime = component.object().start + component.object().duration; + + if (maxTime >= otherMin && time <= otherMaxTime) { + accepted = false; + break; + } + } + + if (!accepted) { + int size = trackContainer.container().trackListContainer().container().children().size(); + if (track.index() >= size - 1) { + track = new TrackContainer(trackContainer.container()); + trackContainer.container().trackListContainer().container().addChild(track); + trackContainer.container().screen().trackTabContainer().addChild(new TrackTabContainer(track)); + break; + } else { + track = (TrackContainer) trackContainer.container().trackListContainer().container().children().get(track.index() + 1); + } + } else { + break; + } + } + + track.addChild(new ObjectComponent(trackContainer.container().trackListContainer(), object), c -> c.layoutOptions(new AbsoluteLayoutOptions(x, 0))); + } +} diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java new file mode 100644 index 00000000..d56874bc --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java @@ -0,0 +1,122 @@ +package oxy.bascenario.utils.drag; + +import lombok.RequiredArgsConstructor; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.dragdrop.DropEvent; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.containers.TimelineContainer; +import oxy.bascenario.editor.containers.object.ObjectComponent; +import oxy.bascenario.editor.containers.track.TrackContainer; +import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; +import oxy.bascenario.utils.Launcher; +import oxy.bascenario.utils.NameUtils; + +import java.util.List; + +@RequiredArgsConstructor +public class SecondaryDragComponent extends Component { + private final TimelineContainer timelineContainer; + + private final float mouseX, mouseY; + private final List list; + + @Override + public void render(Renderer renderer, Size size) { + for (Data data : list) { + renderer.translate(mouseX + data.offsetX, mouseY + data.offsetY, () -> { + float width = timelineContainer.screen().oneMilSecondWidth() * data.object.object().duration; + + renderer.fillRect(0, 0, width, 60, Color.fromRGB(30, 30, 30)); + renderer.outlineRect(0, 0, width, 60, 2, Color.fromRGB(145, 218, 255).darker()); + + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(data.object.object().object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + }); + } + } + + + @Override + public Size computeIdealSize(Size size) { + return size; + } + + public record Data(ObjectComponent object, float offsetX, float offsetY) { + public void handle(final TrackContainer container, float x) { + final float dataX = Math.max(0, x + offsetX); + long time = Math.max(0, (long) (dataX / container.container().screen().oneMilSecondWidth())); + + TrackContainer track = container; + while (true) { + boolean accepted = true; + for (Component baseComponent : track.children()) { + if (!(baseComponent instanceof ObjectComponent component) || component == object) { + continue; + } + + long otherMin = component.object().start; + + long maxTime = component.object().start + component.object().duration; + long otherMaxTime = component.object().start + component.object().duration; + + if (maxTime >= otherMin && time <= otherMaxTime) { + accepted = false; + break; + } + } + + if (!accepted) { + int size = container.container().trackListContainer().container().children().size(); + if (track.index() >= size - 1) { + track = new TrackContainer(container.container()); + container.container().trackListContainer().container().addChild(track); + container.container().screen().trackTabContainer().addChild(new TrackTabContainer(track)); + break; + } else { + track = (TrackContainer) container.container().trackListContainer().container().children().get(track.index() + 1); + } + } else { + break; + } + } + + object.dragging(false); + + object.layoutOptions(new AbsoluteLayoutOptions(dataX, 0)); + object.object().start = time; + if (track == object.parent()) { + return; + } + ((Container)object.parent()).removeChild(object); + track.addChild(object); + } + } + + public void handle(final TrackContainer container, float x) { + int offset = 0; + for (Data data : list) { + TrackContainer trackContainer = container; + if (offset > 0) { + int newIndex = trackContainer.index() + offset; + + int size = container.container().trackListContainer().container().children().size(); + if (newIndex >= size) { + trackContainer = new TrackContainer(container.container()); + container.container().trackListContainer().container().addChild(trackContainer); + container.container().screen().trackTabContainer().addChild(new TrackTabContainer(trackContainer)); + } else { + trackContainer = (TrackContainer) container.container().trackListContainer().container().children().get(newIndex); + } + } + + data.handle(trackContainer, x); + + offset++; + } + } +} From d9f87d5be2295cc50801a14380b5032c7a7ba1c0 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 12 Jul 2026 23:25:05 +0700 Subject: [PATCH 57/71] Fix selection manager. --- .../editor/containers/selection/SelectionManager.java | 11 ++++------- .../editor/containers/track/TrackListContainer.java | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java index a9e10cd3..da7d6f66 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -5,6 +5,8 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.math.Rectangle; import oxy.bascenario.editor.containers.object.ObjectComponent; @@ -46,13 +48,8 @@ public void addIfIntersects(ObjectComponent component, Rectangle rectangle) { float minX = Math.min(x, x1), minY = Math.min(y, y1); float maxX = Math.max(x, x1), maxY = Math.max(y, y1); - minX += this.range.x(); - maxX += this.range.x(); - - minY += this.range.y(); - maxY += this.range.y(); - - if (maxX >= rectangle.x() && minX <= rectangle.maxX() && maxY >= rectangle.y() && minY <= rectangle.maxY()) { + if (maxX >= rectangle.x() && minX <= rectangle.maxX() && maxY >= rectangle.y() && minY <= rectangle.maxY() + + ((Container)component.parent()).parent().childBounds((Component) component.parent()).y()) { objects.add(component); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 8dffd991..63d2db5d 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -49,8 +49,8 @@ public void render(Renderer renderer, Size bounds) { recalculateObjectPosition(); this.requestLayoutRecalculation(); } - prevWidth = bounds.width(); + super.render(renderer, bounds); this.selectionManager.render(renderer, timelineContainer.childBounds(this)); From 688913f560ba4b57ac642f779f1ad1819935058f Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sun, 12 Jul 2026 23:25:58 +0700 Subject: [PATCH 58/71] Minor code cleanup. --- .../editor/containers/selection/SelectionManager.java | 6 +----- .../editor/containers/track/TrackListContainer.java | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java index da7d6f66..c5a7d4fe 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -25,14 +25,10 @@ public boolean isSelected(ObjectComponent component) { private float x, y; private float x1, y1; - private Rectangle range = Rectangle.EMPTY; - - public void render(Renderer renderer, Rectangle range) { + public void render(Renderer renderer) { if (x == 0 && y == 0) { return; } - this.range = range; - float minX = Math.min(x, x1), minY = Math.min(y, y1); float maxX = Math.max(x, x1), maxY = Math.max(y, y1); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 63d2db5d..7374da3a 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -53,7 +53,7 @@ public void render(Renderer renderer, Size bounds) { super.render(renderer, bounds); - this.selectionManager.render(renderer, timelineContainer.childBounds(this)); + this.selectionManager.render(renderer); } @Override From 47a813c5156a5827e85413c378111f82c692cc46 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Mon, 13 Jul 2026 20:09:21 +0700 Subject: [PATCH 59/71] Minor improvements. --- .../editor/containers/AOContainer.java | 5 +- .../editor/containers/ao/AOTabContainer.java | 22 +++++-- .../utils/components/TabButton.java | 63 +++++++++++++++++++ .../utils/drag/SecondaryDragComponent.java | 5 +- 4 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/utils/components/TabButton.java diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index 4645563d..de89d6de 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -30,8 +30,11 @@ public void tab(Tab tab) { case Object -> new AoObjectTab(screen); default -> null; }; - if (currentTab != null) { + final DecoratedContainer container = new DecoratedContainer(new SolidColor(), currentTab); + container.innerPadding(new Padding(0, 0, 10, 0)); + currentTab = container; + this.addChild(currentTab, c -> c.layoutOptions(BorderPosition.CENTER)); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java index 9587c3db..f7a21bda 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java @@ -1,12 +1,12 @@ package oxy.bascenario.editor.containers.ao; -import net.lenni0451.rivet.component.container.Button; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.list.HorizontalListLayout; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.containers.AOContainer; +import oxy.bascenario.utils.components.TabButton; public class AOTabContainer extends ScrollContainer { public AOTabContainer(AOContainer parent) { @@ -16,6 +16,8 @@ public AOTabContainer(AOContainer parent) { for (AOContainer.Tab tab : AOContainer.Tab.values()) { add(parent, container, tab); } + + this.barWidth().set(0f); } @Override @@ -24,8 +26,20 @@ public Size computeIdealSize(Size constraints) { } private void add(AOContainer parent, Container container, AOContainer.Tab targetTab) { - final Label label = new Label(targetTab.name()).scale(0.5f); - final Button button = new Button(label, ignored -> parent.tab(targetTab)); + final TabButton button = new TabButton(targetTab.name(), tb -> { + parent.tab(targetTab); + + for (Component component : container.children()) { + if (component instanceof TabButton tb1) { + tb1.line(false); + } + } + tb.line(true); + }); + if (targetTab == AOContainer.Tab.Media) { + button.line(true); + } + container.addChild(button); } } diff --git a/editor/src/main/java/oxy/bascenario/utils/components/TabButton.java b/editor/src/main/java/oxy/bascenario/utils/components/TabButton.java new file mode 100644 index 00000000..5810dc6d --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/utils/components/TabButton.java @@ -0,0 +1,63 @@ +package oxy.bascenario.utils.components; + +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; + +@RequiredArgsConstructor +@Accessors(fluent = true, chain = true, makeFinal = true) +public class TabButton extends Component { + @Setter + private boolean line; + private ShapedText shapedText; + + private final String text; + private final ClickListener clickListener; + + @Override + protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { + if (event.button() == MouseButton.LEFT) { + clickListener.onClick(this); + return true; + } + + return false; + } + + @Override + public void render(final Renderer renderer, final Size size) { + if (shapedText == null) { + shapedText = this.rivet().backend().font().derive(20).shapeText(this.text, Color.WHITE); + } + + float x = TextOrigin.Horizontal.VISUAL_CENTER.position(size.width()); + float y = TextOrigin.Vertical.LOGICAL_TOP.position(size.height()); + renderer.text(shapedText, x, y, TextOrigin.Horizontal.VISUAL_CENTER, TextOrigin.Vertical.LOGICAL_TOP); + + if (line) { + renderer.fillRect(0, shapedText.logicalBounds().height(), size.width(), 1, Color.WHITE); + } + } + + @Override + public Size computeIdealSize(final Size constraints) { + if (shapedText == null) { + shapedText = this.rivet().backend().font().derive(20).shapeText(this.text, Color.WHITE); + } + + return new Size(shapedText.visualBounds().width() + 10f, shapedText.logicalBounds().height() + 10); + } + + @FunctionalInterface + public interface ClickListener { + void onClick(final TabButton button); + } +} diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java index d56874bc..44b632b1 100644 --- a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java +++ b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java @@ -5,16 +5,13 @@ import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.dragdrop.DropEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.object.ObjectComponent; import oxy.bascenario.editor.containers.track.TrackContainer; import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; -import oxy.bascenario.utils.Launcher; import oxy.bascenario.utils.NameUtils; import java.util.List; @@ -61,7 +58,7 @@ public void handle(final TrackContainer container, float x) { long otherMin = component.object().start; - long maxTime = component.object().start + component.object().duration; + long maxTime = object.object().start + object.object().duration; long otherMaxTime = component.object().start + component.object().duration; if (maxTime >= otherMin && time <= otherMaxTime) { From f156a54a9a9d8ba8f81cd58cbfdd848459e68de7 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Mon, 13 Jul 2026 20:26:27 +0700 Subject: [PATCH 60/71] Fixed dragging, minor improvements. --- .../containers/object/ObjectComponent.java | 2 +- .../selection/SelectionManager.java | 4 +++- .../utils/drag/SecondaryDragComponent.java | 24 ++++++++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java index 30eb1b31..d6fe98d8 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java @@ -51,7 +51,7 @@ public void render(Renderer renderer, Size bounds) { renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - this.parent.selectionManager().addIfIntersects(this, parent().childBounds(this)); + this.parent.selectionManager().addOrRemove(this, parent().childBounds(this)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java index c5a7d4fe..308b4fc6 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java @@ -36,7 +36,7 @@ public void render(Renderer renderer) { renderer.outlineRect(minX, minY, maxX - minX, maxY - minY, 2, Color.WHITE); } - public void addIfIntersects(ObjectComponent component, Rectangle rectangle) { + public void addOrRemove(ObjectComponent component, Rectangle rectangle) { if (x == 0 && y == 0) { return; } @@ -47,6 +47,8 @@ public void addIfIntersects(ObjectComponent component, Rectangle rectangle) { if (maxX >= rectangle.x() && minX <= rectangle.maxX() && maxY >= rectangle.y() && minY <= rectangle.maxY() + ((Container)component.parent()).parent().childBounds((Component) component.parent()).y()) { objects.add(component); + } else { + objects.remove(component); } } } diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java index 44b632b1..20f16ea8 100644 --- a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java +++ b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java @@ -14,7 +14,7 @@ import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; import oxy.bascenario.utils.NameUtils; -import java.util.List; +import java.util.*; @RequiredArgsConstructor public class SecondaryDragComponent extends Component { @@ -86,18 +86,28 @@ public void handle(final TrackContainer container, float x) { object.layoutOptions(new AbsoluteLayoutOptions(dataX, 0)); object.object().start = time; - if (track == object.parent()) { - return; - } - ((Container)object.parent()).removeChild(object); track.addChild(object); } } public void handle(final TrackContainer container, float x) { - int offset = 0; + if (list.isEmpty()) { + return; + } + + list.sort(Comparator.comparingInt(data -> ((TrackContainer)data.object.parent()).index())); + + int firstIndex = ((TrackContainer)list.getFirst().object.parent()).index(); + + final Map tempMap = new HashMap<>(); + for (Data data : list) { // Remove first. + tempMap.put(data, ((TrackContainer)data.object.parent()).index()); + ((Container)data.object.parent()).removeChild(data.object); + } + for (Data data : list) { TrackContainer trackContainer = container; + int offset = tempMap.get(data) - firstIndex; if (offset > 0) { int newIndex = trackContainer.index() + offset; @@ -112,8 +122,6 @@ public void handle(final TrackContainer container, float x) { } data.handle(trackContainer, x); - - offset++; } } } From 44c0f05b2ab13609d78975fb31ff3151c4c23eb6 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Mon, 13 Jul 2026 20:36:49 +0700 Subject: [PATCH 61/71] Move text object into Objects tab. --- .../editor/containers/AOContainer.java | 4 +- .../editor/containers/ao/tab/AoObjectTab.java | 29 ++++++++ .../editor/containers/ao/tab/AoTextTab.java | 59 ---------------- .../utils/components/TextWithNameNoImage.java | 70 +++++++++++++++++++ 4 files changed, 100 insertions(+), 62 deletions(-) delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java create mode 100644 editor/src/main/java/oxy/bascenario/utils/components/TextWithNameNoImage.java diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index de89d6de..7573dfb4 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -15,7 +15,6 @@ import oxy.bascenario.editor.containers.ao.AOTabContainer; import oxy.bascenario.editor.containers.ao.tab.AoMediaTab; import oxy.bascenario.editor.containers.ao.tab.AoObjectTab; -import oxy.bascenario.editor.containers.ao.tab.AoTextTab; // Object & Actions container @Accessors(fluent = true) @@ -26,7 +25,6 @@ public class AOContainer extends Container { public void tab(Tab tab) { this.removeChild(currentTab); currentTab = switch (tab) { - case Text -> new AoTextTab(screen); case Object -> new AoObjectTab(screen); default -> null; }; @@ -66,6 +64,6 @@ public void render(Renderer renderer, Size bounds) { } public enum Tab { - Media, Text, Object, Transition, Actions + Media, Object, Transition, Actions } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java index 0c248160..99ec29ae 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -1,19 +1,28 @@ package oxy.bascenario.editor.containers.ao.tab; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Size; import oxy.bascenario.api.render.elements.LocationInfo; import oxy.bascenario.api.render.elements.Preview; import oxy.bascenario.api.render.elements.emoticon.Emoticon; import oxy.bascenario.api.render.elements.emoticon.EmoticonType; +import oxy.bascenario.api.render.elements.text.AnimatedText; +import oxy.bascenario.api.render.elements.text.Text; +import oxy.bascenario.api.render.elements.text.TextSegment; import oxy.bascenario.api.render.elements.text.font.FontType; import oxy.bascenario.editor.ScenarioEditorScreen; +import oxy.bascenario.utils.components.TextWithNameNoImage; +import oxy.bascenario.utils.components.TypingLabel; import oxy.bascenario.utils.drag.FakeObjectComponent; import oxy.bascenario.utils.TimeCompiler; import oxy.bascenario.utils.components.TextWithName; +import java.util.List; + public class AoObjectTab extends ScrollContainer { public AoObjectTab(ScenarioEditorScreen screen) { final Container container = new Container(new VerticalListLayout(10, false)); @@ -22,6 +31,8 @@ public AoObjectTab(ScenarioEditorScreen screen) { add(screen, container, "Preview", "assets/base/uis/editor/objects/preview.png", new Preview(FontType.NotoSans, "Title", "Subtitle", null)); add(screen, container, "Location Info", "assets/base/uis/editor/objects/location-info.png", new LocationInfo(FontType.NotoSans, "Location A", 1000, 200)); add(screen, container, "Emoticon", "assets/base/uis/editor/objects/emoticon.png", new Emoticon(100, EmoticonType.ANGRY, true)); + add(screen, container, "Text", new Label("Default Text").scale(0.6f), new Text(List.of(TextSegment.builder().text("Text").build()), 42)); + add(screen, container, "Typing Text", new TypingLabel("Typing Text").scale(0.6f), new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42)); } @Override @@ -29,6 +40,24 @@ public Size computeIdealSize(Size constraints) { return new Size(constraints.width(), Math.max(0, constraints.height() - 40)); } + private void add(ScenarioEditorScreen screen, Container container, String name, Component c, Object object) { + final TextWithNameNoImage component = new TextWithNameNoImage(name, c); + + long duration = TimeCompiler.compileTime(object); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } + + long fDuration = duration; + component.mouseDownListener().add((ignored, bounds) -> { + FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration); + rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); + return true; + }); + + container.addChild(component); + } + private void add(ScenarioEditorScreen screen, Container container, String name, String path, Object object) { final TextWithName component = new TextWithName(name, path); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java deleted file mode 100644 index 0e6fa3d1..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoTextTab.java +++ /dev/null @@ -1,59 +0,0 @@ -package oxy.bascenario.editor.containers.ao.tab; - -import net.lenni0451.rivet.component.container.Button; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.component.impl.Label; -import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Size; -import oxy.bascenario.api.render.elements.text.AnimatedText; -import oxy.bascenario.api.render.elements.text.Text; -import oxy.bascenario.api.render.elements.text.TextSegment; -import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.utils.drag.FakeObjectComponent; -import oxy.bascenario.utils.components.TypingLabel; - -import java.util.List; - -public class AoTextTab extends ScrollContainer { - public AoTextTab(ScenarioEditorScreen screen) { - final Container container = new Container(new VerticalListLayout(10, false)); - super(container); - - final Button defaultText = new Button(new Label("Default Text").scale(0.7f), ignored -> {}); - defaultText.mouseDownListener().add((ignored, size) -> { - final Object object = new Text(List.of(TextSegment.builder().text("Default Text").build()), 42); - FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L); - - rivet().dragAndDropManager().startDrag( - ghost, - ghost, - 0, - -30 / 2f - ); - return true; - }); - - container.addChild(defaultText); - - final Button typingText = new Button(new TypingLabel("Typing Text").scale(0.7f), ignored -> {}); - typingText.mouseDownListener().add((ignored, bounds) -> { - final Object object = new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42); - FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, 1000L); - - rivet().dragAndDropManager().startDrag( - ghost, - ghost, - 0, - -60 / 2f - ); - return true; - }); - container.addChild(typingText); - } - - @Override - public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), Math.max(0, constraints.height() - 40)); - } -} diff --git a/editor/src/main/java/oxy/bascenario/utils/components/TextWithNameNoImage.java b/editor/src/main/java/oxy/bascenario/utils/components/TextWithNameNoImage.java new file mode 100644 index 00000000..79e62d69 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/utils/components/TextWithNameNoImage.java @@ -0,0 +1,70 @@ +package oxy.bascenario.utils.components; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.Texture; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.backend.thingl.ThinGLTexture; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.Parent; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.Base; + +import java.util.List; + +public class TextWithNameNoImage extends Component { + private final String name; + private final Component component; + + public TextWithNameNoImage(String name, Component component) { + this.name = name; + this.component = component; + } + + @Override + public void render(Renderer renderer, Size bounds) { + if (component.rivet() == null) { + component.setRivet(rivet(), new Parent() { + @Override + public void requestLayoutRecalculation() { + } + + @Override + public Size contentSize() { + return Size.EMPTY; + } + + @Override + public List children() { + return List.of(); + } + + @Override + public Rectangle absoluteBounds() { + return new Rectangle(Size.EMPTY); + } + + @Override + public Rectangle childBounds(Component component) { + return new Rectangle(Size.EMPTY); + } + }); + } + + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(25, 25, 25)); + component.render(renderer, bounds); + + ShapedText text = this.rivet().backend().font().shapeText(name, Color.WHITE); + renderer.scale(0.4f, () -> + renderer.text(text, + (277 / 2f - text.visualBounds().width() * .4f / 2f) / .4f, 160 / .4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP + )); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(255 + 20, 141 + 40); + } +} From f6f720df2b00d5af9407d1fa2e5106496db27b2e Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Mon, 13 Jul 2026 21:18:26 +0700 Subject: [PATCH 62/71] Added circle and rectangle. --- .../editor/containers/AOContainer.java | 2 +- .../editor/containers/ao/tab/AoObjectTab.java | 6 ++++++ .../assets/base/uis/editor/objects/circle.png | Bin 0 -> 2884 bytes .../assets/base/uis/editor/objects/rectangle.png | Bin 0 -> 710 bytes 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 editor/src/main/resources/assets/base/uis/editor/objects/circle.png create mode 100644 editor/src/main/resources/assets/base/uis/editor/objects/rectangle.png diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java index 7573dfb4..d530d989 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java @@ -64,6 +64,6 @@ public void render(Renderer renderer, Size bounds) { } public enum Tab { - Media, Object, Transition, Actions + Media, Spines, Object, Transition, Actions } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java index 99ec29ae..100a229a 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java @@ -1,5 +1,6 @@ package oxy.bascenario.editor.containers.ao.tab; +import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; @@ -10,6 +11,8 @@ import oxy.bascenario.api.render.elements.Preview; import oxy.bascenario.api.render.elements.emoticon.Emoticon; import oxy.bascenario.api.render.elements.emoticon.EmoticonType; +import oxy.bascenario.api.render.elements.shape.Circle; +import oxy.bascenario.api.render.elements.shape.Rectangle; import oxy.bascenario.api.render.elements.text.AnimatedText; import oxy.bascenario.api.render.elements.text.Text; import oxy.bascenario.api.render.elements.text.TextSegment; @@ -33,6 +36,9 @@ public AoObjectTab(ScenarioEditorScreen screen) { add(screen, container, "Emoticon", "assets/base/uis/editor/objects/emoticon.png", new Emoticon(100, EmoticonType.ANGRY, true)); add(screen, container, "Text", new Label("Default Text").scale(0.6f), new Text(List.of(TextSegment.builder().text("Text").build()), 42)); add(screen, container, "Typing Text", new TypingLabel("Typing Text").scale(0.6f), new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42)); + + add(screen, container, "Circle", "assets/base/uis/editor/objects/circle.png", new Circle(5, Color.WHITE, false)); + add(screen, container, "Rectangle", "assets/base/uis/editor/objects/rectangle.png", new Rectangle(500, 500, Color.WHITE, false)); } @Override diff --git a/editor/src/main/resources/assets/base/uis/editor/objects/circle.png b/editor/src/main/resources/assets/base/uis/editor/objects/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..dbff9d9480fda17745f3d192b8372b7aacf24078 GIT binary patch literal 2884 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 literal 0 HcmV?d00001 diff --git a/editor/src/main/resources/assets/base/uis/editor/objects/rectangle.png b/editor/src/main/resources/assets/base/uis/editor/objects/rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..83e22daa48794d29310c37600d4488989a17df0d GIT binary patch literal 710 zcmeAS@N?(olHy`uVBq!ia0vp^|ADxdgAGVlJX!-J85oO$+?^QKos)S9a~60+7BevL9Ry*<9TT(P0tF>YTq8GK0mLfNVoXCI&$u z9RF8WQV0J5OE3=IrGvTeH!!nRyt+yD%jB2O2`kczmsmp2MF7zi*s&i+@;*7UyN z$l0pZx+PnzL(tx(Q-0AA{=i4=8Xujf{804zBcNK> jAvEHJQGyN5X;`;aZc!%7iH4NZE+9cqS3j3^P6 Date: Tue, 14 Jul 2026 23:38:48 +0700 Subject: [PATCH 63/71] Move track tab container into timeline container. --- .../java/oxy/bascenario/utils/Launcher.java | 2 +- core/src/test/java/DefaultRivetTest.java | 50 +++---------------- .../editor/ScenarioEditorScreen.java | 23 ++++----- .../editor/containers/TimelineContainer.java | 21 ++++++-- .../inspector/InspectorContainer.java | 19 +++++++ .../track/tab/TrackTabContainer.java | 2 +- .../track/tab/TrackTabListContainer.java | 6 ++- .../utils/drag/FakeObjectComponent.java | 2 +- .../utils/drag/SecondaryDragComponent.java | 4 +- 9 files changed, 61 insertions(+), 68 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/inspector/InspectorContainer.java diff --git a/core/src/main/java/oxy/bascenario/utils/Launcher.java b/core/src/main/java/oxy/bascenario/utils/Launcher.java index 1126b338..0869782b 100644 --- a/core/src/main/java/oxy/bascenario/utils/Launcher.java +++ b/core/src/main/java/oxy/bascenario/utils/Launcher.java @@ -23,7 +23,7 @@ public static void launch(Screen screen, boolean fullScreen, boolean bypass) { } Lwjgl3ApplicationConfiguration configuration = new Lwjgl3ApplicationConfiguration(); - configuration.setTitle("Blue Archive Scenario Engine."); + configuration.setTitle("Blue Archive Scenario Engine"); configuration.setWindowedMode(1280, 720); configuration.setWindowSizeLimits(1280, 720, -1, -1); diff --git a/core/src/test/java/DefaultRivetTest.java b/core/src/test/java/DefaultRivetTest.java index 6d22d4f3..ecac0c54 100644 --- a/core/src/test/java/DefaultRivetTest.java +++ b/core/src/test/java/DefaultRivetTest.java @@ -9,6 +9,7 @@ import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.component.impl.FormattedLabel; import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; @@ -42,40 +43,21 @@ public static void main(String[] args) { @Override protected void init(final Rivet rivet) { Container everything = new Container(BorderLayout.INSTANCE); - everything.addChild(new Timeline().layoutOptions(BorderPosition.TOP)); - { - Container elements = new Container(AbsoluteLayout.INSTANCE); - int extra = 0; - for (int i = 0; i < 20; i++) { - elements.addChild(new TimelineElement(1).layoutOptions(new AbsoluteLayoutOptions(i * ONE_SECOND + extra, 0))); - extra += ONE_SECOND; - } - everything.addChild(elements.layoutOptions(BorderPosition.CENTER)); - } - rivet.root().addChild(new ScrollContainer(everything, true, true) { - @Override - public void render(Renderer renderer, Size size) { - System.out.println(size); - super.render(renderer, size); - } - + everything.addChild(new SolidColor(Color.RED) { @Override public Size computeIdealSize(Size constraints) { - System.out.println(constraints); - return super.computeIdealSize(constraints); + return new Size(100, 20); } - }); - } + }.layoutOptions(BorderPosition.LEFT)); + everything.addChild(new Timeline().layoutOptions(BorderPosition.TOP)); + rivet.root().addChild(new ScrollContainer(everything, true, true)); + } private static class Timeline extends Component { @Override public void render(final Renderer renderer, final Size size) { renderer.fillRect(0, 0, size.width(), size.height(), Color.LIGHT_GRAY); - for (int i = 0; i < size.width() / ONE_SECOND; i++) { - renderer.fillRect(i * ONE_SECOND, 0, 1, size.height(), Color.BLACK); - renderer.text(this.rivet().backend().font().shapeText(String.valueOf(i), Color.RED), i * ONE_SECOND, size.height() / 2F, TextOrigin.Horizontal.VISUAL_CENTER, TextOrigin.Vertical.VISUAL_CENTER); - } } @Override @@ -84,24 +66,6 @@ public Size computeIdealSize(final Size constraints) { } } - private static class TimelineElement extends Component { - private final int length; - - private TimelineElement(int length) { - this.length = length; - } - - @Override - public void render(final Renderer renderer, final Size size) { - renderer.fillRect(0, 0, size.width(), size.height(), Color.BLUE); - } - - @Override - public Size computeIdealSize(final Size constraints) { - return new Size(ONE_SECOND * this.length, 50); - } - } - static { if (System.getProperty("os.name").contains("Linux")) { GLFW.glfwInitHint(GLFW.GLFW_PLATFORM, GLFW.GLFW_PLATFORM_X11); diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index d1f0d00f..0faf2d4d 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -5,26 +5,22 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; -import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.impl.SolidColor; -import net.lenni0451.rivet.input.keyboard.KeyEvent; import net.lenni0451.rivet.layout.anchor.AnchorLayout; import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Rectangle; import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.Scenario; import oxy.bascenario.editor.containers.AOContainer; import oxy.bascenario.editor.containers.TimelineContainer; import oxy.bascenario.editor.containers.TimelineTabContainer; +import oxy.bascenario.editor.containers.inspector.InspectorContainer; import oxy.bascenario.editor.containers.track.tab.TrackTabListContainer; import oxy.bascenario.utils.ExtendableScreen; import oxy.bascenario.utils.TimeUtils; import oxy.bascenario.utils.thingl.ThinGLUtils; -import java.util.function.Predicate; - @Accessors(fluent = true) @Getter @Setter @@ -64,7 +60,7 @@ public void scale(float scale) { private TimelineTabContainer timelineTabContainer; private TimelineContainer timelineContainer; - private Container trackTabContainer; +// private Container trackTabContainer; public ScenarioEditorScreen(Scenario scenario) { this.scenario = scenario.toBuilder(); @@ -90,7 +86,7 @@ public void init(Rivet rivet) { Container container = new Container(AnchorLayout.INSTANCE); container.addChild(new AOContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.005f).withAnchorMinY(0.02f).withAnchorMaxX(0.20f).withAnchorMaxY(0.6f)); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.005f).withAnchorMinY(0.02f).withAnchorMaxX(0.2f).withAnchorMaxY(0.6f)); }); // preview @@ -99,18 +95,17 @@ public void init(Rivet rivet) { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.02f).withAnchorMaxX(0.8f).withAnchorMaxY(0.55f)); }); - container.addChild(new SolidColor(), c -> { -// c.color(Color.fromRGB(35, 35, 35)); - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.06f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); + container.addChild(new InspectorContainer(), c -> { + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.02f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); }); container.addChild(timelineContainer = new TimelineContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.15525f).withAnchorMinY(0.61f).withAnchorMaxX(1).withAnchorMaxY(1)); + c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0).withAnchorMinY(0.61f).withAnchorMaxX(1).withAnchorMaxY(1)); }); - container.addChild(new TrackTabListContainer(trackTabContainer = new Container(new VerticalListLayout(3, false)), timelineContainer), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0).withAnchorMinY(0.61f).withAnchorMaxX(0.15525f).withAnchorMaxY(1)); - }); +// container.addChild(new TrackTabListContainer(new Container(new VerticalListLayout(3, false)), timelineContainer), c -> { +// c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0).withAnchorMinY(0.61f).withAnchorMaxX(0.15525f).withAnchorMaxY(1)); +// }); container.addChild(timelineTabContainer = new TimelineTabContainer(this), c -> { c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.55f).withAnchorMaxX(0.8f).withAnchorMaxY(0.6f)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java index c210ca72..ebbc7e38 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java @@ -4,13 +4,18 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; +import net.lenni0451.rivet.layout.dock.DockLayout; +import net.lenni0451.rivet.layout.dock.DockPosition; +import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.ScenarioEditorScreen; import oxy.bascenario.editor.containers.timeline.TimelineTimeSection; import oxy.bascenario.editor.containers.track.TrackListContainer; +import oxy.bascenario.editor.containers.track.tab.TrackTabListContainer; @Accessors(fluent = true) public class TimelineContainer extends Container { @@ -20,12 +25,17 @@ public class TimelineContainer extends Container { @Getter private final ScenarioEditorScreen screen; + + @Getter + private final Container trackTabListContainer; + private final Component timelineTimeSection; public TimelineContainer(ScenarioEditorScreen screen) { - super(BorderLayout.INSTANCE); + super(new DockLayout(0)); this.screen = screen; - - this.addChild(new TimelineTimeSection(this), c -> c.layoutOptions(BorderPosition.TOP)); - this.addChild(trackListContainer = new TrackListContainer(this), c -> c.layoutOptions(BorderPosition.CENTER)); + this.addChild(new TrackTabListContainer(trackTabListContainer = new Container(new VerticalListLayout(3, false)), + this), c -> c.layoutOptions(DockPosition.LEFT)); + this.addChild(timelineTimeSection = new TimelineTimeSection(this), c -> c.layoutOptions(DockPosition.TOP)); + this.addChild(trackListContainer = new TrackListContainer(this), c -> c.layoutOptions(DockPosition.CENTER)); } @Override @@ -37,7 +47,8 @@ public void render(Renderer renderer, Size bounds) { } private void drawTimelineCursor(Renderer renderer, Size bounds) { - final float cursorX = screen.timestamp() * (screen.oneSecondWidth() / 1000f) - trackListContainer.scrollX(); + float offsetX = this.childBounds(timelineTimeSection).x(); + final float cursorX = offsetX + screen.timestamp() * (screen.oneSecondWidth() / 1000f) - trackListContainer.scrollX(); renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); renderer.fillRect(cursorX, 0, 2, bounds.height(), TIMELINE_CURSOR_COLOR.withAlphaF(0.8f)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/inspector/InspectorContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/inspector/InspectorContainer.java new file mode 100644 index 00000000..b4aebc8a --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/inspector/InspectorContainer.java @@ -0,0 +1,19 @@ +package oxy.bascenario.editor.containers.inspector; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Size; + +public class InspectorContainer extends Container { + public InspectorContainer() { + super(new VerticalListLayout(5, true)); + } + + @Override + public void render(Renderer renderer, Size size) { + renderer.fillRect(0, 0, size.width(), size.height(), Color.fromRGB(35, 35, 35)); + super.render(renderer, size); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java index 7c35f20e..06d007a5 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java @@ -24,7 +24,7 @@ public TrackTabContainer(TrackContainer container) { return; } - this.container.container().screen().trackTabContainer().removeChild(this); + this.container.container().trackTabListContainer().removeChild(this); this.container.container().trackListContainer().container().removeChild(container); }); trash.hoverColor().set(Color.fromRGB(145, 218, 255)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java index 1f697655..52c3f210 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java @@ -21,7 +21,6 @@ public class TrackTabListContainer extends Container { public TrackTabListContainer(Container container, TimelineContainer timelineContainer) { super(BorderLayout.INSTANCE); - // Just to take space. this.addChild(new UpperContainer(timelineContainer, container), c -> c.layoutOptions(BorderPosition.TOP)); this.scrollContainer = new ScrollContainer(container, false, true) { @@ -36,6 +35,11 @@ public void render(Renderer renderer, Size size) { this.addChild(this.scrollContainer, c -> c.layoutOptions(BorderPosition.CENTER)); } + @Override + public Size computeIdealSize(Size constraints) { + return constraints.withWidth(constraints.width() * 0.15525f); + } + @Override public void render(Renderer renderer, Size bounds) { renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java index cc5efea5..38b86734 100644 --- a/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java @@ -74,7 +74,7 @@ public void handle(TrackContainer trackContainer, float x) { if (track.index() >= size - 1) { track = new TrackContainer(trackContainer.container()); trackContainer.container().trackListContainer().container().addChild(track); - trackContainer.container().screen().trackTabContainer().addChild(new TrackTabContainer(track)); + trackContainer.container().trackTabListContainer().addChild(new TrackTabContainer(track)); break; } else { track = (TrackContainer) trackContainer.container().trackListContainer().container().children().get(track.index() + 1); diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java index 20f16ea8..f9a4849c 100644 --- a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java +++ b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java @@ -72,7 +72,7 @@ public void handle(final TrackContainer container, float x) { if (track.index() >= size - 1) { track = new TrackContainer(container.container()); container.container().trackListContainer().container().addChild(track); - container.container().screen().trackTabContainer().addChild(new TrackTabContainer(track)); + container.container().trackTabListContainer().addChild(new TrackTabContainer(track)); break; } else { track = (TrackContainer) container.container().trackListContainer().container().children().get(track.index() + 1); @@ -115,7 +115,7 @@ public void handle(final TrackContainer container, float x) { if (newIndex >= size) { trackContainer = new TrackContainer(container.container()); container.container().trackListContainer().container().addChild(trackContainer); - container.container().screen().trackTabContainer().addChild(new TrackTabContainer(trackContainer)); + container.container().trackTabListContainer().addChild(new TrackTabContainer(trackContainer)); } else { trackContainer = (TrackContainer) container.container().trackListContainer().container().children().get(newIndex); } From d54494fd63d9143aee66fc7a97243f9a0277fba5 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Wed, 15 Jul 2026 19:48:27 +0700 Subject: [PATCH 64/71] Delete everything, rework the UI from scratch. --- core/build.gradle.kts | 2 + .../bascenario/managers/AssetsManager.java | 2 +- .../managers/other/TextureAsset.java | 8 +- .../java/oxy/bascenario/utils/Launcher.java | 2 +- .../oxy/bascenario/editor/EditorValues.java | 40 +++++ .../editor/ScenarioEditorScreen.java | 112 ++---------- .../editor/containers/AOContainer.java | 69 -------- .../editor/containers/DockBarContainer.java | 16 ++ .../editor/containers/GlobalContainer.java | 14 ++ .../editor/containers/TimelineContainer.java | 65 ------- .../containers/TimelineTabContainer.java | 65 ------- .../editor/containers/ao/AOTabContainer.java | 45 ----- .../editor/containers/ao/tab/AoMediaTab.java | 11 -- .../editor/containers/ao/tab/AoObjectTab.java | 84 --------- .../inspector/InspectorContainer.java | 19 --- .../containers/object/ObjectComponent.java | 113 ------------- .../containers/object/ObjectOrEvent.java | 13 -- .../selection/SelectionManager.java | 54 ------ .../timeline/TimelineContainer.java | 33 ++++ .../containers/timeline/TimelineDockBar.java | 16 ++ .../timeline/TimelineTimeSection.java | 63 ------- .../containers/track/TrackContainer.java | 68 -------- .../containers/track/TrackListContainer.java | 127 -------------- .../track/tab/TrackTabContainer.java | 46 ----- .../track/tab/TrackTabListContainer.java | 72 -------- .../utils/drag/FakeObjectComponent.java | 95 +++++------ .../utils/drag/SecondaryDragComponent.java | 160 +++++++++--------- editor/src/test/java/ScenarioEditorTest.java | 2 +- gradle/libs.versions.toml | 3 + 29 files changed, 274 insertions(+), 1145 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/EditorValues.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/DockBarContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/inspector/InspectorContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectOrEvent.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java delete mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index f7b12b2c..369f9fe5 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -40,6 +40,8 @@ dependencies { api(libs.jcraft.jorbis) api(libs.javazoom.jlayer) + api(libs.weisj.jsvg) + listOf("natives-windows", "natives-windows-arm64", "natives-linux", "natives-linux-arm64").forEach { api("org.lwjgl:lwjgl:$lwjgl:$it") api("org.lwjgl:lwjgl-glfw:$lwjgl:$it") diff --git a/core/src/main/java/oxy/bascenario/managers/AssetsManager.java b/core/src/main/java/oxy/bascenario/managers/AssetsManager.java index a8eea2b4..6f1838a3 100644 --- a/core/src/main/java/oxy/bascenario/managers/AssetsManager.java +++ b/core/src/main/java/oxy/bascenario/managers/AssetsManager.java @@ -91,7 +91,7 @@ public Texture2D texture(String scenario, FileInfo info) { public Texture2D texture(String scenario, FileInfo info, boolean filter) { try { - return ((TextureAsset)assets(scenario, info, AssetType.TEXTURE).asset()).get(filter); + return ((TextureAsset)assets(scenario, info, AssetType.TEXTURE).asset()).get(info.path().endsWith(".svg"), filter); } catch (Exception ignored) { if (INVALID_TEXTURE_KEY == Integer.MIN_VALUE) { FileInfo invalidTexture = new FileInfo("assets/base/invalid.png", false, true); diff --git a/core/src/main/java/oxy/bascenario/managers/other/TextureAsset.java b/core/src/main/java/oxy/bascenario/managers/other/TextureAsset.java index ba91766b..59868667 100644 --- a/core/src/main/java/oxy/bascenario/managers/other/TextureAsset.java +++ b/core/src/main/java/oxy/bascenario/managers/other/TextureAsset.java @@ -2,7 +2,9 @@ import lombok.RequiredArgsConstructor; import net.raphimc.thingl.gl.resource.image.texture.impl.Texture2D; +import net.raphimc.thingl.resource.image.impl.AwtSvgByteImage2D; import org.lwjgl.opengl.GL20; +import oxy.bascenario.api.managers.other.AssetType; @RequiredArgsConstructor public class TextureAsset { @@ -10,8 +12,12 @@ public class TextureAsset { private Texture2D texture2D; public Texture2D get(boolean filter) { + return get(false, filter); + } + + public Texture2D get(boolean svg, boolean filter) { if (this.texture2D == null) { - this.texture2D = Texture2D.fromImage(bytes); + this.texture2D = svg ? Texture2D.fromImage(new AwtSvgByteImage2D(bytes, 1)) : Texture2D.fromImage(bytes); if (filter) { this.texture2D.setFilter(GL20.GL_NEAREST); } diff --git a/core/src/main/java/oxy/bascenario/utils/Launcher.java b/core/src/main/java/oxy/bascenario/utils/Launcher.java index 0869782b..9d2e37c5 100644 --- a/core/src/main/java/oxy/bascenario/utils/Launcher.java +++ b/core/src/main/java/oxy/bascenario/utils/Launcher.java @@ -25,7 +25,7 @@ public static void launch(Screen screen, boolean fullScreen, boolean bypass) { Lwjgl3ApplicationConfiguration configuration = new Lwjgl3ApplicationConfiguration(); configuration.setTitle("Blue Archive Scenario Engine"); configuration.setWindowedMode(1280, 720); - configuration.setWindowSizeLimits(1280, 720, -1, -1); +// configuration.setWindowSizeLimits(1280, 720, -1, -1); WINDOW = new EngineRenderer(screen, fullScreen); new Lwjgl3Application(WINDOW, configuration); diff --git a/editor/src/main/java/oxy/bascenario/editor/EditorValues.java b/editor/src/main/java/oxy/bascenario/editor/EditorValues.java new file mode 100644 index 00000000..d700df09 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/EditorValues.java @@ -0,0 +1,40 @@ +package oxy.bascenario.editor; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Accessors(fluent = true) +@Getter +@Setter +public class EditorValues { + @Getter + @Setter + private static EditorValues instance; + + private boolean playing; + private long timestamp = 0; + private float scale = 1; + private float scroll; + + private static final float ONE_SECOND_WIDTH = 128; + + public float oneSecondWidth() { + return ONE_SECOND_WIDTH * scale; + } + public float oneMilSecondWidth() { + return (ONE_SECOND_WIDTH / 1000f) * scale; + } + + private long since = 0; + public void tick() { + if (since == 0) { + since = System.currentTimeMillis(); + } + if (playing) { + timestamp += System.currentTimeMillis() - since; + } + since = System.currentTimeMillis(); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 0faf2d4d..26e7ffa6 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -1,123 +1,47 @@ package oxy.bascenario.editor; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.DecoratedContainer; import net.lenni0451.rivet.component.impl.SolidColor; -import net.lenni0451.rivet.layout.anchor.AnchorLayout; -import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; -import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.layout.dock.DockLayout; +import net.lenni0451.rivet.layout.dock.DockPosition; +import net.lenni0451.rivet.math.Padding; import net.raphimc.thingl.ThinGL; import oxy.bascenario.api.Scenario; -import oxy.bascenario.editor.containers.AOContainer; -import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.editor.containers.TimelineTabContainer; -import oxy.bascenario.editor.containers.inspector.InspectorContainer; -import oxy.bascenario.editor.containers.track.tab.TrackTabListContainer; +import oxy.bascenario.editor.containers.DockBarContainer; +import oxy.bascenario.editor.containers.GlobalContainer; import oxy.bascenario.utils.ExtendableScreen; -import oxy.bascenario.utils.TimeUtils; import oxy.bascenario.utils.thingl.ThinGLUtils; -@Accessors(fluent = true) -@Getter -@Setter public class ScenarioEditorScreen extends ExtendableScreen { private final Scenario.Builder scenario; - private static final float ONE_SECOND_WIDTH = 128; - public float oneSecondWidth() { - return ONE_SECOND_WIDTH * scale; - } - public float oneMilSecondWidth() { - return (ONE_SECOND_WIDTH / 1000f) * scale; - } -// public static final long DEFAULT_MAX_TIME = 15000; - - private boolean playing; - private long timestamp = 1000; - public void timestamp(long timestamp) { - this.timestamp = timestamp; - playing(false); - } - - public void playing(boolean playing) { - this.playing = playing; - if (timelineTabContainer != null) { - timelineTabContainer.playButton().state(playing); - } - } - - private float scale = 1; - - public void scale(float scale) { - this.scale = scale; - timelineContainer.trackListContainer().recalculateObjectPosition(); - timelineContainer.trackListContainer().requestLayoutRecalculation(); - } - - private TimelineTabContainer timelineTabContainer; - private TimelineContainer timelineContainer; -// private Container trackTabContainer; + public ScenarioEditorScreen(Scenario.Builder builder) { + EditorValues.instance(new EditorValues()); - public ScenarioEditorScreen(Scenario scenario) { - this.scenario = scenario.toBuilder(); - TimeUtils.fakeTimeMillis = System.currentTimeMillis(); + this.scenario = builder; } - private long since = 0; @Override public void renderBehindRivet() { - if (since == 0) { - since = System.currentTimeMillis(); - } - if (playing) { - timestamp += System.currentTimeMillis() - since; - } - since = System.currentTimeMillis(); + EditorValues.instance().tick(); - ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(22, 22, 22)); + ThinGL.renderer2D().filledRectangle(ThinGLUtils.GLOBAL_RENDER_STACK, 0, 0, ThinGL.windowInterface().getFramebufferWidth(), ThinGL.windowInterface().getFramebufferHeight(), Color.fromRGB(24, 24, 24)); } @Override public void init(Rivet rivet) { - Container container = new Container(AnchorLayout.INSTANCE); + Container mainContainer = new Container(new DockLayout()); + rivet.root().addChild(mainContainer); - container.addChild(new AOContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.005f).withAnchorMinY(0.02f).withAnchorMaxX(0.2f).withAnchorMaxY(0.6f)); - }); - - // preview - container.addChild(new SolidColor(), c -> { - c.color(Color.BLACK); - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.02f).withAnchorMaxX(0.8f).withAnchorMaxY(0.55f)); - }); - - container.addChild(new InspectorContainer(), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.805f).withAnchorMinY(0.02f).withAnchorMaxX(0.99f).withAnchorMaxY(0.6f)); - }); + // Dock bar (File, Edit, ...) + mainContainer.addChild(new DockBarContainer(), c -> c.layoutOptions(DockPosition.TOP)); - container.addChild(timelineContainer = new TimelineContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0).withAnchorMinY(0.61f).withAnchorMaxX(1).withAnchorMaxY(1)); + mainContainer.addChild(new DecoratedContainer(new SolidColor(), new GlobalContainer()), c -> { + c.layoutOptions(DockPosition.CENTER); + c.innerPadding(new Padding(2, 8, 2, 24)); }); - -// container.addChild(new TrackTabListContainer(new Container(new VerticalListLayout(3, false)), timelineContainer), c -> { -// c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0).withAnchorMinY(0.61f).withAnchorMaxX(0.15525f).withAnchorMaxY(1)); -// }); - - container.addChild(timelineTabContainer = new TimelineTabContainer(this), c -> { - c.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.205f).withAnchorMinY(0.55f).withAnchorMaxX(0.8f).withAnchorMaxY(0.6f)); - }); - -// RivetUtil.dropDownList(container, "Timeline", AnchorLayoutOptions.EMPTY.withAnchorMinX(0.015f).withAnchorMinY(0.01f), -// List.of( -// new Pair<>("Play", () -> {}), -// new Pair<>("Pause", () -> {}) -// ) -// ); - - rivet.root().addChild(container); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java deleted file mode 100644 index d530d989..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/AOContainer.java +++ /dev/null @@ -1,69 +0,0 @@ -package oxy.bascenario.editor.containers; - -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.DecoratedContainer; -import net.lenni0451.rivet.component.impl.SolidColor; -import net.lenni0451.rivet.layout.border.BorderLayout; -import net.lenni0451.rivet.layout.border.BorderPosition; -import net.lenni0451.rivet.math.Padding; -import net.lenni0451.rivet.math.Size; -import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.editor.containers.ao.AOTabContainer; -import oxy.bascenario.editor.containers.ao.tab.AoMediaTab; -import oxy.bascenario.editor.containers.ao.tab.AoObjectTab; - -// Object & Actions container -@Accessors(fluent = true) -public class AOContainer extends Container { - private final ScenarioEditorScreen screen; - private Component currentTab; - - public void tab(Tab tab) { - this.removeChild(currentTab); - currentTab = switch (tab) { - case Object -> new AoObjectTab(screen); - default -> null; - }; - if (currentTab != null) { - final DecoratedContainer container = new DecoratedContainer(new SolidColor(), currentTab); - container.innerPadding(new Padding(0, 0, 10, 0)); - currentTab = container; - - this.addChild(currentTab, c -> c.layoutOptions(BorderPosition.CENTER)); - } - } - - public AOContainer(ScenarioEditorScreen screen) { - super(BorderLayout.INSTANCE); - this.screen = screen; - - // Just to take space. - this.addChild(new Component() { - @Override - public Size computeIdealSize(Size size) { - return new Size(20, 20); - } - }, c -> c.layoutOptions(BorderPosition.LEFT)); - - this.addChild(new DecoratedContainer(new SolidColor(), new AOTabContainer(this)), c -> { - c.layoutOptions(BorderPosition.TOP); - c.innerPadding(new Padding(20, 10, 20, 0)); - }); - - this.addChild(new AoMediaTab(), c -> c.layoutOptions(BorderPosition.CENTER)); - } - - @Override - public void render(Renderer renderer, Size bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); - super.render(renderer, bounds); - } - - public enum Tab { - Media, Spines, Object, Transition, Actions - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/DockBarContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/DockBarContainer.java new file mode 100644 index 00000000..d4e34ceb --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/DockBarContainer.java @@ -0,0 +1,16 @@ +package oxy.bascenario.editor.containers; + +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.list.HorizontalListLayout; +import net.lenni0451.rivet.math.Size; + +public class DockBarContainer extends Container { + public DockBarContainer() { + super(new HorizontalListLayout(15, true)); + } + + @Override + public Size computeIdealSize(Size constraints) { + return constraints.withHeight(25); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java new file mode 100644 index 00000000..452d2d63 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java @@ -0,0 +1,14 @@ +package oxy.bascenario.editor.containers; + +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.dock.DockLayout; +import net.lenni0451.rivet.layout.dock.DockPosition; +import oxy.bascenario.editor.containers.timeline.TimelineContainer; + +public class GlobalContainer extends Container { + public GlobalContainer() { + super(new DockLayout()); + + addChild(new TimelineContainer(), c -> c.layoutOptions(DockPosition.TOP)); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java deleted file mode 100644 index ebbc7e38..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineContainer.java +++ /dev/null @@ -1,65 +0,0 @@ -package oxy.bascenario.editor.containers; - -import lombok.Getter; -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.layout.border.BorderLayout; -import net.lenni0451.rivet.layout.border.BorderPosition; -import net.lenni0451.rivet.layout.dock.DockLayout; -import net.lenni0451.rivet.layout.dock.DockPosition; -import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Size; -import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.editor.containers.timeline.TimelineTimeSection; -import oxy.bascenario.editor.containers.track.TrackListContainer; -import oxy.bascenario.editor.containers.track.tab.TrackTabListContainer; - -@Accessors(fluent = true) -public class TimelineContainer extends Container { - private static final Color TIMELINE_CURSOR_COLOR = Color.fromRGB(145, 218, 255); - @Getter - private final TrackListContainer trackListContainer; - - @Getter - private final ScenarioEditorScreen screen; - - @Getter - private final Container trackTabListContainer; - private final Component timelineTimeSection; - public TimelineContainer(ScenarioEditorScreen screen) { - super(new DockLayout(0)); - this.screen = screen; - this.addChild(new TrackTabListContainer(trackTabListContainer = new Container(new VerticalListLayout(3, false)), - this), c -> c.layoutOptions(DockPosition.LEFT)); - this.addChild(timelineTimeSection = new TimelineTimeSection(this), c -> c.layoutOptions(DockPosition.TOP)); - this.addChild(trackListContainer = new TrackListContainer(this), c -> c.layoutOptions(DockPosition.CENTER)); - } - - @Override - public void render(Renderer renderer, Size bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35).darker()); - renderer.fillRect(0, 0, bounds.width(), 35, Color.fromRGB(35, 35, 35)); - super.render(renderer, bounds); - drawTimelineCursor(renderer, bounds); - } - - private void drawTimelineCursor(Renderer renderer, Size bounds) { - float offsetX = this.childBounds(timelineTimeSection).x(); - final float cursorX = offsetX + screen.timestamp() * (screen.oneSecondWidth() / 1000f) - trackListContainer.scrollX(); - - renderer.fillTriangle(cursorX + 1.25f - 10, 0, cursorX + 1.25f, 10, cursorX + 1.25f + 10, 0, TIMELINE_CURSOR_COLOR); - renderer.fillRect(cursorX, 0, 2, bounds.height(), TIMELINE_CURSOR_COLOR.withAlphaF(0.8f)); - } - - @Override - public Size computeIdealSize(Size constraints) { - return constraints; - } - - public static float timestampToPosition(long timestamp, float offsetX, float size, float scale) { - return offsetX + (timestamp * size * scale); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java deleted file mode 100644 index 5038974f..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/TimelineTabContainer.java +++ /dev/null @@ -1,65 +0,0 @@ -package oxy.bascenario.editor.containers; - -import lombok.Getter; -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.impl.Label; -import net.lenni0451.rivet.component.impl.slider.Slider; -import net.lenni0451.rivet.layout.anchor.AnchorLayout; -import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; -import net.lenni0451.rivet.math.Size; -import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.utils.components.PlayButton; - -@Accessors(fluent = true) -public class TimelineTabContainer extends Container { - private final ScenarioEditorScreen screen; - - private final Label timeLabel; - - @Getter - private final PlayButton playButton; - public TimelineTabContainer(final ScenarioEditorScreen screen) { - super(AnchorLayout.INSTANCE); - - final Slider slider = new Slider(0.5, 5, 0.01, 1); - slider.barHeight().set(5f); - slider.thumbWidth().set(20f); - slider.thumbHeight().set(20f); - slider.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.78f).withAnchorMaxX(0.98f).withAnchorMinY(0.5f).withPivotY(0.5f)); - slider.showTooltip().set(false); - - slider.valueChangeListener().add(d -> screen.scale(d.floatValue())); - - this.addChild(slider); - - timeLabel = new Label(""); - timeLabel.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.5F).withAnchorMinY(0.5f).pivot(0.5f, 0.5f)); - timeLabel.scale(.5f); - - this.addChild(timeLabel); - - playButton = new PlayButton(screen.playing(), screen::playing); - playButton.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.01f).withAnchorMinY(0.5f).withPivotY(0.5f)); - this.addChild(playButton); - - this.screen = screen; - } - - @Override - public void render(Renderer renderer, Size bounds) { - timeLabel.text(format(screen.timestamp())); - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); - super.render(renderer, bounds); - } - - private static String format(long ms) { - long millis = ms % 1000; - long second = (ms / 1000) % 60; - long minute = (ms / (1000 * 60)) % 60; - long hour = (ms / (1000 * 60 * 60)) % 24; - return (hour < 10 ? "0" + hour : hour) + ":" + (minute < 10 ? "0" + minute : minute) + ":" + (second < 10 ? "0" + second : second) + ":" + (millis < 10 ? "0" + millis : millis); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java deleted file mode 100644 index f7a21bda..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/AOTabContainer.java +++ /dev/null @@ -1,45 +0,0 @@ -package oxy.bascenario.editor.containers.ao; - -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.layout.list.HorizontalListLayout; -import net.lenni0451.rivet.math.Size; -import oxy.bascenario.editor.containers.AOContainer; -import oxy.bascenario.utils.components.TabButton; - -public class AOTabContainer extends ScrollContainer { - public AOTabContainer(AOContainer parent) { - final Container container = new Container(new HorizontalListLayout(2, false)); - super(container, true, false); - - for (AOContainer.Tab tab : AOContainer.Tab.values()) { - add(parent, container, tab); - } - - this.barWidth().set(0f); - } - - @Override - public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), 40); - } - - private void add(AOContainer parent, Container container, AOContainer.Tab targetTab) { - final TabButton button = new TabButton(targetTab.name(), tb -> { - parent.tab(targetTab); - - for (Component component : container.children()) { - if (component instanceof TabButton tb1) { - tb1.line(false); - } - } - tb.line(true); - }); - if (targetTab == AOContainer.Tab.Media) { - button.line(true); - } - - container.addChild(button); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java deleted file mode 100644 index ed19fa36..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoMediaTab.java +++ /dev/null @@ -1,11 +0,0 @@ -package oxy.bascenario.editor.containers.ao.tab; - -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.layout.list.VerticalListLayout; - -public class AoMediaTab extends ScrollContainer { - public AoMediaTab() { - super(new Container(new VerticalListLayout(10, false))); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java b/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java deleted file mode 100644 index 100a229a..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/ao/tab/AoObjectTab.java +++ /dev/null @@ -1,84 +0,0 @@ -package oxy.bascenario.editor.containers.ao.tab; - -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.component.impl.Label; -import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Size; -import oxy.bascenario.api.render.elements.LocationInfo; -import oxy.bascenario.api.render.elements.Preview; -import oxy.bascenario.api.render.elements.emoticon.Emoticon; -import oxy.bascenario.api.render.elements.emoticon.EmoticonType; -import oxy.bascenario.api.render.elements.shape.Circle; -import oxy.bascenario.api.render.elements.shape.Rectangle; -import oxy.bascenario.api.render.elements.text.AnimatedText; -import oxy.bascenario.api.render.elements.text.Text; -import oxy.bascenario.api.render.elements.text.TextSegment; -import oxy.bascenario.api.render.elements.text.font.FontType; -import oxy.bascenario.editor.ScenarioEditorScreen; -import oxy.bascenario.utils.components.TextWithNameNoImage; -import oxy.bascenario.utils.components.TypingLabel; -import oxy.bascenario.utils.drag.FakeObjectComponent; -import oxy.bascenario.utils.TimeCompiler; -import oxy.bascenario.utils.components.TextWithName; - -import java.util.List; - -public class AoObjectTab extends ScrollContainer { - public AoObjectTab(ScenarioEditorScreen screen) { - final Container container = new Container(new VerticalListLayout(10, false)); - super(container); - - add(screen, container, "Preview", "assets/base/uis/editor/objects/preview.png", new Preview(FontType.NotoSans, "Title", "Subtitle", null)); - add(screen, container, "Location Info", "assets/base/uis/editor/objects/location-info.png", new LocationInfo(FontType.NotoSans, "Location A", 1000, 200)); - add(screen, container, "Emoticon", "assets/base/uis/editor/objects/emoticon.png", new Emoticon(100, EmoticonType.ANGRY, true)); - add(screen, container, "Text", new Label("Default Text").scale(0.6f), new Text(List.of(TextSegment.builder().text("Text").build()), 42)); - add(screen, container, "Typing Text", new TypingLabel("Typing Text").scale(0.6f), new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42)); - - add(screen, container, "Circle", "assets/base/uis/editor/objects/circle.png", new Circle(5, Color.WHITE, false)); - add(screen, container, "Rectangle", "assets/base/uis/editor/objects/rectangle.png", new Rectangle(500, 500, Color.WHITE, false)); - } - - @Override - public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), Math.max(0, constraints.height() - 40)); - } - - private void add(ScenarioEditorScreen screen, Container container, String name, Component c, Object object) { - final TextWithNameNoImage component = new TextWithNameNoImage(name, c); - - long duration = TimeCompiler.compileTime(object); - if (duration == Long.MAX_VALUE) { - duration = 1000L; - } - - long fDuration = duration; - component.mouseDownListener().add((ignored, bounds) -> { - FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration); - rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); - return true; - }); - - container.addChild(component); - } - - private void add(ScenarioEditorScreen screen, Container container, String name, String path, Object object) { - final TextWithName component = new TextWithName(name, path); - - long duration = TimeCompiler.compileTime(object); - if (duration == Long.MAX_VALUE) { - duration = 1000L; - } - - long fDuration = duration; - component.mouseDownListener().add((ignored, bounds) -> { - FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration); - rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); - return true; - }); - - container.addChild(component); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/inspector/InspectorContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/inspector/InspectorContainer.java deleted file mode 100644 index b4aebc8a..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/inspector/InspectorContainer.java +++ /dev/null @@ -1,19 +0,0 @@ -package oxy.bascenario.editor.containers.inspector; - -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Size; - -public class InspectorContainer extends Container { - public InspectorContainer() { - super(new VerticalListLayout(5, true)); - } - - @Override - public void render(Renderer renderer, Size size) { - renderer.fillRect(0, 0, size.width(), size.height(), Color.fromRGB(35, 35, 35)); - super.render(renderer, size); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java deleted file mode 100644 index d6fe98d8..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectComponent.java +++ /dev/null @@ -1,113 +0,0 @@ -package oxy.bascenario.editor.containers.object; - -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.input.keyboard.ModifierKey; -import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.input.mouse.MouseButtonEvent; -import net.lenni0451.rivet.input.mouse.MouseMoveEvent; -import net.lenni0451.rivet.math.Rectangle; -import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.containers.track.TrackListContainer; -import oxy.bascenario.utils.Launcher; -import oxy.bascenario.utils.NameUtils; -import oxy.bascenario.utils.drag.SecondaryDragComponent; - -import java.util.ArrayList; -import java.util.List; - -@Accessors(fluent = true) -public class ObjectComponent extends Component { - private static final Color OBJECT_COLOR = Color.fromRGB(145, 218, 255).darker(); - - private final TrackListContainer parent; - @Getter - private final ObjectOrEvent object; - - @Setter - private boolean dragging; - - public ObjectComponent(TrackListContainer parent, ObjectOrEvent object) { - this.parent = parent; - this.object = object; - } - - @Override - public void render(Renderer renderer, Size bounds) { - if (dragging) { - if (!rivet().dragAndDropManager().isDragging()) { - dragging = false; - } - return; - } - - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); - renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); - - renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - - this.parent.selectionManager().addOrRemove(this, parent().childBounds(this)); - } - - @Override - protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { - if (this.parent.selectionManager().isSelected(this)) { - return true; - } - - if (!event.modifiers().contains(ModifierKey.CONTROL)) { - this.parent.selectionManager().objects().clear(); - } - - this.parent.selectionManager().objects().add(this); - return true; - } - - @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { - if (!event.buttons().contains(MouseButton.LEFT)) { - return false; - } - - if (rivet().dragAndDropManager().isDragging()) { - return true; - } - - Rectangle clickedBounds = parent().childBounds(this); - - final List list = new ArrayList<>(); - for (ObjectComponent component : parent.selectionManager().objects()) { - Rectangle bounds = component.parent().childBounds(component); - float x = -event.x() + (bounds.x() - clickedBounds.x()); - float y = -event.y(); - if (component.parent() != parent()) { - y += (component.parent().absoluteBounds().y() - parent().absoluteBounds().y()); - } - - list.add(new SecondaryDragComponent.Data(component, x, y)); - component.dragging(true); - } - - // Ehhh using mouseX and mouseY from EngineRenderer is a bit hacky here... - final SecondaryDragComponent dragComponent = new SecondaryDragComponent(parent.timelineContainer(), (float) Launcher.WINDOW.mouseX, (float) Launcher.WINDOW.mouseY, list); - - rivet().dragAndDropManager().startDrag( - dragComponent, - dragComponent, - (float) -Launcher.WINDOW.mouseX, (float) -Launcher.WINDOW.mouseY - ); - - return true; - } - - @Override - public Size computeIdealSize(Size constraints) { - float widthToAMil = parent.timelineContainer().screen().oneSecondWidth() / 1000f; - return new Size(widthToAMil * object.duration, constraints.height()); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectOrEvent.java b/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectOrEvent.java deleted file mode 100644 index 8fe9a338..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/object/ObjectOrEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package oxy.bascenario.editor.containers.object; - -import lombok.AllArgsConstructor; -import oxy.bascenario.api.render.RenderLayer; - -@AllArgsConstructor -public class ObjectOrEvent { - public long start, duration; - - public Object object; - public RenderLayer layer; - public boolean requireWait; -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java deleted file mode 100644 index 308b4fc6..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/selection/SelectionManager.java +++ /dev/null @@ -1,54 +0,0 @@ -package oxy.bascenario.editor.containers.selection; - -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.math.Rectangle; -import oxy.bascenario.editor.containers.object.ObjectComponent; - -import java.util.HashSet; -import java.util.Set; - -@Accessors(fluent = true) -@Setter -public class SelectionManager { - @Getter - private Set objects = new HashSet<>(); - public boolean isSelected(ObjectComponent component) { - return objects.contains(component); - } - - private float x, y; - private float x1, y1; - - public void render(Renderer renderer) { - if (x == 0 && y == 0) { - return; - } - float minX = Math.min(x, x1), minY = Math.min(y, y1); - float maxX = Math.max(x, x1), maxY = Math.max(y, y1); - - renderer.fillRect(minX, minY, maxX - minX, maxY - minY, Color.WHITE.withAlphaF(0.4f)); - renderer.outlineRect(minX, minY, maxX - minX, maxY - minY, 2, Color.WHITE); - } - - public void addOrRemove(ObjectComponent component, Rectangle rectangle) { - if (x == 0 && y == 0) { - return; - } - - float minX = Math.min(x, x1), minY = Math.min(y, y1); - float maxX = Math.max(x, x1), maxY = Math.max(y, y1); - - if (maxX >= rectangle.x() && minX <= rectangle.maxX() && maxY >= rectangle.y() && minY <= rectangle.maxY() + - ((Container)component.parent()).parent().childBounds((Component) component.parent()).y()) { - objects.add(component); - } else { - objects.remove(component); - } - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java new file mode 100644 index 00000000..821061c3 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java @@ -0,0 +1,33 @@ +package oxy.bascenario.editor.containers.timeline; + +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.dock.DockLayout; +import net.lenni0451.rivet.layout.dock.DockPosition; +import net.lenni0451.rivet.math.Size; + +@Accessors(fluent = true) +public class TimelineContainer extends Container { + @Setter + private float relativeScale = 0.4f; + + public TimelineContainer() { + super(new DockLayout(0)); + + this.addChild(new TimelineDockBar(), c -> c.layoutOptions(DockPosition.TOP)); + } + + @Override + public void render(Renderer renderer, Size size) { + renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(48, 48, 48)); + super.render(renderer, size); + } + + @Override + public Size computeIdealSize(Size constraints) { + return constraints.withHeight(constraints.height() * relativeScale); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java new file mode 100644 index 00000000..22ae8b32 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java @@ -0,0 +1,16 @@ +package oxy.bascenario.editor.containers.timeline; + +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.list.HorizontalListLayout; +import net.lenni0451.rivet.math.Size; + +public class TimelineDockBar extends Container { + public TimelineDockBar() { + super(new HorizontalListLayout(12, true)); + } + + @Override + public Size computeIdealSize(Size constraints) { + return constraints.withHeight(25); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java deleted file mode 100644 index 5cf99dca..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeSection.java +++ /dev/null @@ -1,63 +0,0 @@ -package oxy.bascenario.editor.containers.timeline; - -import lombok.RequiredArgsConstructor; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.backend.text.ShapedText; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.input.mouse.MouseButtonEvent; -import net.lenni0451.rivet.input.mouse.MouseMoveEvent; -import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.containers.TimelineContainer; - -@RequiredArgsConstructor -public class TimelineTimeSection extends Component { - private final TimelineContainer parent; - - @Override - public void render(Renderer renderer, Size size) { - float x = -parent.trackListContainer().scrollX(); - while (!(x >= size.width())) { - renderer.fillRect(x, 0, 1, 10, Color.WHITE); - - float time = (x + parent.trackListContainer().scrollX()) / parent.screen().oneSecondWidth(); - - time = Math.round(time * 100.0f) / 100.0f; - ShapedText text = this.rivet().backend().font().shapeText(time + "s", Color.WHITE); - float currentX = x; - - renderer.scale(0.4f, () -> renderer.text(text, currentX / 0.4f, (25 / 35f) * size.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - - x += parent.screen().oneSecondWidth(); - } - } - - @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { - if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { - float x = parent.trackListContainer().scrollX() + event.x() - 1.25f; - long newTimestamp = (long) (x / (parent.screen().oneMilSecondWidth())); - parent.screen().timestamp(Math.max(0, newTimestamp)); - } - - return false; - } - - @Override - protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { - if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { - float x = parent.trackListContainer().scrollX() + event.x() - 1.25f; - long newTimestamp = (long) (x / (parent.screen().oneMilSecondWidth())); - parent.screen().timestamp(Math.max(0, newTimestamp)); - } - - return super.onComponentMouseDown(event, bounds); - } - - @Override - public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), Math.min(35, constraints.height())); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java deleted file mode 100644 index 718e7666..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackContainer.java +++ /dev/null @@ -1,68 +0,0 @@ -package oxy.bascenario.editor.containers.track; - -import lombok.Getter; -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.dragdrop.DropEvent; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.math.Size; -import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.utils.drag.FakeObjectComponent; -import oxy.bascenario.utils.drag.SecondaryDragComponent; - -@Accessors(fluent = true) -public class TrackContainer extends Container { - @Getter - private final TimelineContainer container; - - @Getter - private float height = 60f; - - public TrackContainer(TimelineContainer container) { - super(AbsoluteLayout.INSTANCE); - this.container = container; - - // So the user can scroll more by default. - this.addChild(new Component() { - @Override - public Size computeIdealSize(Size constraints) { - return new Size(1.0e-16f, 1.0e-16f); - } - }, c -> c.layoutOptions(new AbsoluteLayoutOptions(3000, 0, 5f, 5f))); - } - - @Override - public void render(Renderer renderer, Size size) { - renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(40, 40, 40)); - super.render(renderer, size); - } - - @Override - protected boolean onComponentDrop(DropEvent event, Size bounds) { - container.screen().playing(false); - - event.dragData().forEach(data -> { - if (data instanceof FakeObjectComponent component) { - component.handle(this, event.x()); - } else if (data instanceof SecondaryDragComponent component) { - component.handle(this, event.x()); - } - }); - - return !event.dragData().isEmpty(); - } - - @Override - public Size computeIdealSize(Size constraints) { - Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); - return new Size(idealSize.width(), height); - } - - public int index() { - return container.trackListContainer().container().children().indexOf(this); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java deleted file mode 100644 index 7374da3a..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ /dev/null @@ -1,127 +0,0 @@ -package oxy.bascenario.editor.containers.track; - -import lombok.Getter; -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.backend.text.ShapedText; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.dragdrop.DropEvent; -import net.lenni0451.rivet.input.keyboard.Key; -import net.lenni0451.rivet.input.keyboard.KeyEvent; -import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.input.mouse.MouseButtonEvent; -import net.lenni0451.rivet.input.mouse.MouseMoveEvent; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.layout.list.VerticalListLayout; -import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.editor.containers.object.ObjectComponent; -import oxy.bascenario.editor.containers.object.ObjectOrEvent; -import oxy.bascenario.editor.containers.selection.SelectionManager; -import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; -import oxy.bascenario.utils.TimeCompiler; - -@Accessors(fluent = true) -public class TrackListContainer extends ScrollContainer { - @Getter - private final TimelineContainer timelineContainer; - @Getter - private final Container container; - - @Getter - private final SelectionManager selectionManager = new SelectionManager(); - - public TrackListContainer(TimelineContainer timelineContainer) { - super(container = new Container(new VerticalListLayout(3, false)), true, true); - this.timelineContainer = timelineContainer; - } - - private float prevWidth; - - @Override - public void render(Renderer renderer, Size bounds) { - if (prevWidth != bounds.width()) { - recalculateObjectPosition(); - this.requestLayoutRecalculation(); - } - prevWidth = bounds.width(); - - super.render(renderer, bounds); - - this.selectionManager.render(renderer); - } - - @Override - public Size computeIdealSize(Size constraints) { - return constraints; - } - - public void recalculateObjectPosition() { - for (Component component : container.children()) { - if (!(component instanceof TrackContainer track)) { - continue; - } - - for (Component child : track.children()) { - if (!(child instanceof ObjectComponent object)) { - continue; - } - - float newX = TimelineContainer.timestampToPosition(object.object().start, 0, timelineContainer.screen().oneMilSecondWidth(), timelineContainer.screen().scale()); - child.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); - } - } - } - - @Override - protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { - if (event.buttons().contains(MouseButton.LEFT)) { - this.selectionManager.x1(event.x()); - this.selectionManager.y1(event.y()); - this.rivet().focusedComponent(this); - } - - return super.onComponentMouseMove(event, bounds); - } - - @Override - protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { - if (!super.onComponentMouseDown(event, bounds) && event.button() == MouseButton.LEFT) { - this.selectionManager.x(event.x()); - this.selectionManager.y(event.y()); - - this.selectionManager.x1(event.x()); - this.selectionManager.y1(event.y()); - - this.selectionManager.objects().clear(); - - this.rivet().focusedComponent(this); - } - return true; - } - - @Override - protected boolean onComponentMouseUp(MouseButtonEvent event, Size bounds) { - if (event.button() == MouseButton.LEFT) { - this.selectionManager.x(0); - this.selectionManager.y(0); - } - - return super.onComponentMouseUp(event, bounds); - } - - @Override - protected boolean onComponentKeyUp(KeyEvent event) { - if (event.key() == Key.DELETE) { - selectionManager.objects().forEach(component -> ((Container)component.parent()).removeChild(component)); - selectionManager().objects().clear(); - } - - return false; - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java deleted file mode 100644 index 06d007a5..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabContainer.java +++ /dev/null @@ -1,46 +0,0 @@ -package oxy.bascenario.editor.containers.track.tab; - -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.Base; -import oxy.bascenario.editor.containers.track.TrackContainer; -import oxy.bascenario.utils.components.ButtonImage; - -public class TrackTabContainer extends Container { - private final TrackContainer container; - - public TrackTabContainer(TrackContainer container) { - super(AbsoluteLayout.INSTANCE); - this.container = container; - - final ButtonImage trash = new ButtonImage(Base.instance().assetsManager().texture("assets/base/uis/editor/trash_18.png"), event -> { - if (event.button() != MouseButton.LEFT) { - return; - } - - this.container.container().trackTabListContainer().removeChild(this); - this.container.container().trackListContainer().container().removeChild(container); - }); - trash.hoverColor().set(Color.fromRGB(145, 218, 255)); - this.addChild(trash, image -> image.layoutOptions(new AbsoluteLayoutOptions(5, 35))); - } - - @Override - public void render(Renderer renderer, Size bounds) { -// renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.RED); - renderer.scale(0.5f, () -> renderer.text(this.rivet().backend().font().shapeText("Track " + container.index(), Color.WHITE), 10, 10, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - - super.render(renderer, bounds); - } - - @Override - public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), container.height()); - } -} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java deleted file mode 100644 index 52c3f210..00000000 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/tab/TrackTabListContainer.java +++ /dev/null @@ -1,72 +0,0 @@ -package oxy.bascenario.editor.containers.track.tab; - -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.input.mouse.MouseButton; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.layout.border.BorderLayout; -import net.lenni0451.rivet.layout.border.BorderPosition; -import net.lenni0451.rivet.math.Size; -import oxy.bascenario.Base; -import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.editor.containers.track.TrackContainer; -import oxy.bascenario.utils.components.ButtonImage; - -public class TrackTabListContainer extends Container { - private final ScrollContainer scrollContainer; - - public TrackTabListContainer(Container container, TimelineContainer timelineContainer) { - super(BorderLayout.INSTANCE); - - this.addChild(new UpperContainer(timelineContainer, container), c -> c.layoutOptions(BorderPosition.TOP)); - - this.scrollContainer = new ScrollContainer(container, false, true) { - @Override - public void render(Renderer renderer, Size size) { - this.scrollY(timelineContainer.trackListContainer().scrollY(), true); - super.render(renderer, size); - } - }; - this.scrollContainer.barWidth().set(0f); - - this.addChild(this.scrollContainer, c -> c.layoutOptions(BorderPosition.CENTER)); - } - - @Override - public Size computeIdealSize(Size constraints) { - return constraints.withWidth(constraints.width() * 0.15525f); - } - - @Override - public void render(Renderer renderer, Size bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(35, 35, 35)); - super.render(renderer, bounds); - } - - private static class UpperContainer extends Container { - public UpperContainer(TimelineContainer timelineContainer, Container container) { - super(AbsoluteLayout.INSTANCE); - - final ButtonImage add = new ButtonImage(Base.instance().assetsManager().texture("assets/base/uis/editor/add_24.png"), event -> { - if (event.button() != MouseButton.LEFT) { - return; - } - - final TrackContainer component = new TrackContainer(timelineContainer); - timelineContainer.trackListContainer().container().addChild(component); - container.addChild(new TrackTabContainer(component)); - }); - add.hoverColor().set(Color.fromRGB(145, 218, 255)); - - this.addChild(add, c -> c.layoutOptions(new AbsoluteLayoutOptions(5, 5))); - } - - @Override - public Size computeIdealSize(Size constraints) { - return new Size(constraints.width(), 35); - } - } -} diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java index 38b86734..5ffb084c 100644 --- a/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java @@ -6,17 +6,10 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.api.render.RenderLayer; -import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.editor.containers.object.ObjectComponent; -import oxy.bascenario.editor.containers.object.ObjectOrEvent; -import oxy.bascenario.editor.containers.track.TrackContainer; -import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; +import oxy.bascenario.editor.containers.oldstuff.track.TrackContainer; import oxy.bascenario.utils.NameUtils; -import oxy.bascenario.utils.TimeCompiler; @AllArgsConstructor @Accessors(fluent = true) @@ -42,48 +35,48 @@ public Size computeIdealSize(Size constraints) { } public void handle(TrackContainer trackContainer, float x) { - long time = (long) (x / trackContainer.container().screen().oneMilSecondWidth()); - long duration = TimeCompiler.compileTime(object()); - if (duration == Long.MAX_VALUE) { - duration = 1000L; - } - - final ObjectOrEvent object = new ObjectOrEvent(time, duration, object(), RenderLayer.ABOVE_DIALOGUE, true); - - TrackContainer track = trackContainer; - while (true) { - boolean accepted = true; - for (Component baseComponent : track.children()) { - if (!(baseComponent instanceof ObjectComponent component)) { - continue; - } - - long otherMin = component.object().start; - - long maxTime = component.object().start + component.object().duration; - long otherMaxTime = component.object().start + component.object().duration; - - if (maxTime >= otherMin && time <= otherMaxTime) { - accepted = false; - break; - } - } - - if (!accepted) { - int size = trackContainer.container().trackListContainer().container().children().size(); - if (track.index() >= size - 1) { - track = new TrackContainer(trackContainer.container()); - trackContainer.container().trackListContainer().container().addChild(track); - trackContainer.container().trackTabListContainer().addChild(new TrackTabContainer(track)); - break; - } else { - track = (TrackContainer) trackContainer.container().trackListContainer().container().children().get(track.index() + 1); - } - } else { - break; - } - } - - track.addChild(new ObjectComponent(trackContainer.container().trackListContainer(), object), c -> c.layoutOptions(new AbsoluteLayoutOptions(x, 0))); +// long time = (long) (x / trackContainer.container().screen().oneMilSecondWidth()); +// long duration = TimeCompiler.compileTime(object()); +// if (duration == Long.MAX_VALUE) { +// duration = 1000L; +// } +// +// final ObjectOrEvent object = new ObjectOrEvent(time, duration, object(), RenderLayer.ABOVE_DIALOGUE, true); +// +// TrackContainer track = trackContainer; +// while (true) { +// boolean accepted = true; +// for (Component baseComponent : track.children()) { +// if (!(baseComponent instanceof ObjectComponent component)) { +// continue; +// } +// +// long otherMin = component.object().start; +// +// long maxTime = component.object().start + component.object().duration; +// long otherMaxTime = component.object().start + component.object().duration; +// +// if (maxTime >= otherMin && time <= otherMaxTime) { +// accepted = false; +// break; +// } +// } +// +// if (!accepted) { +// int size = trackContainer.container().trackListContainer().container().children().size(); +// if (track.index() >= size - 1) { +// track = new TrackContainer(trackContainer.container()); +// trackContainer.container().trackListContainer().container().addChild(track); +// trackContainer.container().trackTabListContainer().addChild(new TrackTabContainer(track)); +// break; +// } else { +// track = (TrackContainer) trackContainer.container().trackListContainer().container().children().get(track.index() + 1); +// } +// } else { +// break; +// } +// } +// +// track.addChild(new ObjectComponent(trackContainer.container().trackListContainer(), object), c -> c.layoutOptions(new AbsoluteLayoutOptions(x, 0))); } } diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java index f9a4849c..7874e54d 100644 --- a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java +++ b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java @@ -4,14 +4,10 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.containers.TimelineContainer; -import oxy.bascenario.editor.containers.object.ObjectComponent; -import oxy.bascenario.editor.containers.track.TrackContainer; -import oxy.bascenario.editor.containers.track.tab.TrackTabContainer; +import oxy.bascenario.editor.containers.oldstuff.object.ObjectComponent; +import oxy.bascenario.editor.containers.oldstuff.track.TrackContainer; import oxy.bascenario.utils.NameUtils; import java.util.*; @@ -44,84 +40,84 @@ public Size computeIdealSize(Size size) { } public record Data(ObjectComponent object, float offsetX, float offsetY) { - public void handle(final TrackContainer container, float x) { - final float dataX = Math.max(0, x + offsetX); - long time = Math.max(0, (long) (dataX / container.container().screen().oneMilSecondWidth())); - - TrackContainer track = container; - while (true) { - boolean accepted = true; - for (Component baseComponent : track.children()) { - if (!(baseComponent instanceof ObjectComponent component) || component == object) { - continue; - } - - long otherMin = component.object().start; - - long maxTime = object.object().start + object.object().duration; - long otherMaxTime = component.object().start + component.object().duration; - - if (maxTime >= otherMin && time <= otherMaxTime) { - accepted = false; - break; - } - } - - if (!accepted) { - int size = container.container().trackListContainer().container().children().size(); - if (track.index() >= size - 1) { - track = new TrackContainer(container.container()); - container.container().trackListContainer().container().addChild(track); - container.container().trackTabListContainer().addChild(new TrackTabContainer(track)); - break; - } else { - track = (TrackContainer) container.container().trackListContainer().container().children().get(track.index() + 1); - } - } else { - break; - } - } - - object.dragging(false); - - object.layoutOptions(new AbsoluteLayoutOptions(dataX, 0)); - object.object().start = time; - track.addChild(object); - } +// public void handle(final TrackContainer container, float x) { +// final float dataX = Math.max(0, x + offsetX); +// long time = Math.max(0, (long) (dataX / container.container().screen().oneMilSecondWidth())); +// +// TrackContainer track = container; +// while (true) { +// boolean accepted = true; +// for (Component baseComponent : track.children()) { +// if (!(baseComponent instanceof ObjectComponent component) || component == object) { +// continue; +// } +// +// long otherMin = component.object().start; +// +// long maxTime = object.object().start + object.object().duration; +// long otherMaxTime = component.object().start + component.object().duration; +// +// if (maxTime >= otherMin && time <= otherMaxTime) { +// accepted = false; +// break; +// } +// } +// +// if (!accepted) { +// int size = container.container().trackListContainer().container().children().size(); +// if (track.index() >= size - 1) { +// track = new TrackContainer(container.container()); +// container.container().trackListContainer().container().addChild(track); +// container.container().trackTabListContainer().addChild(new TrackTabContainer(track)); +// break; +// } else { +// track = (TrackContainer) container.container().trackListContainer().container().children().get(track.index() + 1); +// } +// } else { +// break; +// } +// } +// +// object.dragging(false); +// +// object.layoutOptions(new AbsoluteLayoutOptions(dataX, 0)); +// object.object().start = time; +// track.addChild(object); +// } } public void handle(final TrackContainer container, float x) { - if (list.isEmpty()) { - return; - } - - list.sort(Comparator.comparingInt(data -> ((TrackContainer)data.object.parent()).index())); - - int firstIndex = ((TrackContainer)list.getFirst().object.parent()).index(); - - final Map tempMap = new HashMap<>(); - for (Data data : list) { // Remove first. - tempMap.put(data, ((TrackContainer)data.object.parent()).index()); - ((Container)data.object.parent()).removeChild(data.object); - } - - for (Data data : list) { - TrackContainer trackContainer = container; - int offset = tempMap.get(data) - firstIndex; - if (offset > 0) { - int newIndex = trackContainer.index() + offset; - - int size = container.container().trackListContainer().container().children().size(); - if (newIndex >= size) { - trackContainer = new TrackContainer(container.container()); - container.container().trackListContainer().container().addChild(trackContainer); - container.container().trackTabListContainer().addChild(new TrackTabContainer(trackContainer)); - } else { - trackContainer = (TrackContainer) container.container().trackListContainer().container().children().get(newIndex); - } - } - - data.handle(trackContainer, x); - } +// if (list.isEmpty()) { +// return; +// } +// +// list.sort(Comparator.comparingInt(data -> ((TrackContainer)data.object.parent()).index())); +// +// int firstIndex = ((TrackContainer)list.getFirst().object.parent()).index(); +// +// final Map tempMap = new HashMap<>(); +// for (Data data : list) { // Remove first. +// tempMap.put(data, ((TrackContainer)data.object.parent()).index()); +// ((Container)data.object.parent()).removeChild(data.object); +// } +// +// for (Data data : list) { +// TrackContainer trackContainer = container; +// int offset = tempMap.get(data) - firstIndex; +// if (offset > 0) { +// int newIndex = trackContainer.index() + offset; +// +// int size = container.container().trackListContainer().container().children().size(); +// if (newIndex >= size) { +// trackContainer = new TrackContainer(container.container()); +// container.container().trackListContainer().container().addChild(trackContainer); +// container.container().trackTabListContainer().addChild(new TrackTabContainer(trackContainer)); +// } else { +// trackContainer = (TrackContainer) container.container().trackListContainer().container().children().get(newIndex); +// } +// } +// +// data.handle(trackContainer, x); +// } } } diff --git a/editor/src/test/java/ScenarioEditorTest.java b/editor/src/test/java/ScenarioEditorTest.java index 9223035f..9a36512a 100644 --- a/editor/src/test/java/ScenarioEditorTest.java +++ b/editor/src/test/java/ScenarioEditorTest.java @@ -7,6 +7,6 @@ public static void main(String[] args) { final Scenario.Builder scenario = new Scenario.Builder(); scenario.name("Scenario Editor Test"); - Launcher.launch(new ScenarioEditorScreen(scenario.build()), false); + Launcher.launch(new ScenarioEditorScreen(scenario), false); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c4d80d1a..7c9139e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" rivet = "eff65bbbb5" +jsvg = "2.1.0" [libraries] zt-zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zt-zip" } @@ -53,5 +54,7 @@ unnamed-mocha = { module = "team.unnamed:mocha", version.ref = "mocha" } jcraft-jorbis = { module = "org.jcraft:jorbis", version.ref = "jorbis" } javazoom-jlayer = { module = "javazoom:jlayer", version.ref = "jlayer" } +weisj-jsvg = { module = "com.github.weisj:jsvg", version.ref = "jsvg" } + #netty-buffer = { module = "io.netty:netty-buffer", version.ref = "netty" } #jthink-jaudiotagger = { module = "net.jthink:jaudiotagger", version.ref = "jaudiotagger" } From d8b9a2e7991ba2e386b753d7de3badcd3b6fb0ed Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Wed, 15 Jul 2026 20:32:42 +0700 Subject: [PATCH 65/71] More stuff. --- .../editor/containers/GlobalContainer.java | 74 ++++++++++- .../sequencer/VideoSequencerContainer.java | 24 ++++ .../timeline/TimelineContainer.java | 15 ++- .../containers/timeline/TimelineDockBar.java | 3 + .../timeline/TimelineTimeControl.java | 16 +++ .../components/TimelineDockExpandButton.java | 19 +++ .../utils/drag/FakeObjectComponent.java | 82 ------------ .../utils/drag/SecondaryDragComponent.java | 123 ------------------ 8 files changed, 148 insertions(+), 208 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/sequencer/VideoSequencerContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java create mode 100644 editor/src/main/java/oxy/bascenario/utils/components/TimelineDockExpandButton.java delete mode 100644 editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java delete mode 100644 editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java index 452d2d63..e6ba5c03 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java @@ -1,14 +1,86 @@ package oxy.bascenario.editor.containers; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.layout.dock.DockLayout; import net.lenni0451.rivet.layout.dock.DockPosition; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.raphimc.thingl.ThinGL; +import net.raphimc.thingl.implementation.window.GLFWWindowInterface; +import org.lwjgl.glfw.GLFW; import oxy.bascenario.editor.containers.timeline.TimelineContainer; public class GlobalContainer extends Container { + private final TimelineContainer timeline; + public GlobalContainer() { super(new DockLayout()); - addChild(new TimelineContainer(), c -> c.layoutOptions(DockPosition.TOP)); + addChild(timeline = new TimelineContainer(), c -> c.layoutOptions(DockPosition.BOTTOM)); + } + + float prevMouseY; + boolean nearTimeline, nearTimelineAndMouseDown; + @Override + public void render(Renderer renderer, Size size) { + super.render(renderer, size); + + if (nearTimelineAndMouseDown) { + Rectangle bounds = this.childBounds(timeline); + renderer.fillRect(bounds.x(), bounds.y() - 5, bounds.width(), 2, Color.fromRGB(115, 115, 115)); + } + } + + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { + super.onComponentMouseMove(event, size); + + Rectangle timelineBounds = this.childBounds(timeline); + boolean nearTimeline = Math.abs(event.y() - timelineBounds.y()) < 15; + if (nearTimeline && !this.nearTimeline) { + GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_VRESIZE_CURSOR)); + } else if (!nearTimeline && this.nearTimeline && !nearTimelineAndMouseDown) { + GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR)); + } + this.nearTimeline = nearTimeline; + if (!nearTimelineAndMouseDown) { + this.nearTimelineAndMouseDown = event.isHeld(MouseButton.LEFT) && nearTimeline; + } + + if (nearTimelineAndMouseDown && prevMouseY != Float.MAX_VALUE) { + float newHeight = timelineBounds.height() - (event.y() - prevMouseY); + float ratio = newHeight / size.height(); + timeline.relativeScale(ratio); + timeline.requestLayoutRecalculation(); + } + + prevMouseY = event.y(); + if (!nearTimeline || !nearTimelineAndMouseDown) { + prevMouseY = Float.MAX_VALUE; + } + + return true; + } + + @Override + protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { + this.nearTimelineAndMouseDown = false; + return super.onComponentMouseUp(event, size); + } + + @Override + protected boolean onComponentMouseDown(MouseButtonEvent event, Size size) { + this.nearTimelineAndMouseDown = event.button() == MouseButton.LEFT && nearTimeline; + if (!nearTimelineAndMouseDown) { + prevMouseY = Float.MAX_VALUE; + } + + super.onComponentMouseDown(event, size); + return true; } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/VideoSequencerContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/VideoSequencerContainer.java new file mode 100644 index 00000000..2c4da918 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/VideoSequencerContainer.java @@ -0,0 +1,24 @@ +package oxy.bascenario.editor.containers.sequencer; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.border.BorderLayout; +import net.lenni0451.rivet.math.Size; + +public class VideoSequencerContainer extends Container { + public VideoSequencerContainer() { + super(BorderLayout.INSTANCE); + } + + @Override + public void render(Renderer renderer, Size size) { + renderer.fillRect(0, 0, size.width(), size.height(), Color.fromRGB(30, 30, 30)); + super.render(renderer, size); + } + + @Override + public Size computeIdealSize(Size constraints) { + return constraints; + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java index 821061c3..4eff3631 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java @@ -4,24 +4,35 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.ComboBox; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.DecoratedContainer; +import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.layout.dock.DockLayout; import net.lenni0451.rivet.layout.dock.DockPosition; +import net.lenni0451.rivet.math.Padding; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.containers.sequencer.VideoSequencerContainer; @Accessors(fluent = true) public class TimelineContainer extends Container { @Setter - private float relativeScale = 0.4f; + private float relativeScale = 0.34f; public TimelineContainer() { super(new DockLayout(0)); - this.addChild(new TimelineDockBar(), c -> c.layoutOptions(DockPosition.TOP)); + this.addChild(new DecoratedContainer(new SolidColor(), new TimelineDockBar()), c -> { + c.layoutOptions(DockPosition.TOP); + c.innerPadding(new Padding(8, 2, 0, 0)); + }); + this.addChild(new TimelineTimeControl(), c -> c.layoutOptions(DockPosition.BOTTOM)); + this.addChild(new VideoSequencerContainer(), c -> c.layoutOptions(DockPosition.CENTER)); } @Override public void render(Renderer renderer, Size size) { + renderer.outlineRoundedRect(0, 0, size.width(), size.height(), 5, 1, Color.fromRGB(60, 60, 60)); renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(48, 48, 48)); super.render(renderer, size); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java index 22ae8b32..1aace333 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java @@ -3,10 +3,13 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.list.HorizontalListLayout; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.utils.components.TimelineDockExpandButton; public class TimelineDockBar extends Container { public TimelineDockBar() { super(new HorizontalListLayout(12, true)); + + addChild(new TimelineDockExpandButton()); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java new file mode 100644 index 00000000..d7fb1157 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java @@ -0,0 +1,16 @@ +package oxy.bascenario.editor.containers.timeline; + +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.math.Size; + +public class TimelineTimeControl extends Container { + public TimelineTimeControl() { + super(AnchorLayout.INSTANCE); + } + + @Override + public Size computeIdealSize(Size constraints) { + return constraints.withHeight(25); + } +} diff --git a/editor/src/main/java/oxy/bascenario/utils/components/TimelineDockExpandButton.java b/editor/src/main/java/oxy/bascenario/utils/components/TimelineDockExpandButton.java new file mode 100644 index 00000000..d9f7ce0f --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/utils/components/TimelineDockExpandButton.java @@ -0,0 +1,19 @@ +package oxy.bascenario.utils.components; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.math.Size; + +public class TimelineDockExpandButton extends Component { + @Override + public void render(Renderer renderer, Size size) { + renderer.outlineRoundedRect(0, 0, size.width(), size.height(), 5, 1, Color.fromRGB(60, 60, 60)); + renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(40, 40, 40)); + } + + @Override + public Size computeIdealSize(Size size) { + return new Size(32, 18); + } +} diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java deleted file mode 100644 index 5ffb084c..00000000 --- a/editor/src/main/java/oxy/bascenario/utils/drag/FakeObjectComponent.java +++ /dev/null @@ -1,82 +0,0 @@ -package oxy.bascenario.utils.drag; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.experimental.Accessors; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.containers.oldstuff.track.TrackContainer; -import oxy.bascenario.utils.NameUtils; - -@AllArgsConstructor -@Accessors(fluent = true) -public class FakeObjectComponent extends Component { - private final TimelineContainer timelineContainer; - - @Getter - private final Object object; - private final long duration; - - @Override - public void render(Renderer renderer, Size bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); - renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, Color.fromRGB(145, 218, 255).darker()); - - renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - } - - @Override - public Size computeIdealSize(Size constraints) { - float widthToAMil = timelineContainer.screen().oneSecondWidth() / 1000f; - return new Size(widthToAMil * duration, 60); - } - - public void handle(TrackContainer trackContainer, float x) { -// long time = (long) (x / trackContainer.container().screen().oneMilSecondWidth()); -// long duration = TimeCompiler.compileTime(object()); -// if (duration == Long.MAX_VALUE) { -// duration = 1000L; -// } -// -// final ObjectOrEvent object = new ObjectOrEvent(time, duration, object(), RenderLayer.ABOVE_DIALOGUE, true); -// -// TrackContainer track = trackContainer; -// while (true) { -// boolean accepted = true; -// for (Component baseComponent : track.children()) { -// if (!(baseComponent instanceof ObjectComponent component)) { -// continue; -// } -// -// long otherMin = component.object().start; -// -// long maxTime = component.object().start + component.object().duration; -// long otherMaxTime = component.object().start + component.object().duration; -// -// if (maxTime >= otherMin && time <= otherMaxTime) { -// accepted = false; -// break; -// } -// } -// -// if (!accepted) { -// int size = trackContainer.container().trackListContainer().container().children().size(); -// if (track.index() >= size - 1) { -// track = new TrackContainer(trackContainer.container()); -// trackContainer.container().trackListContainer().container().addChild(track); -// trackContainer.container().trackTabListContainer().addChild(new TrackTabContainer(track)); -// break; -// } else { -// track = (TrackContainer) trackContainer.container().trackListContainer().container().children().get(track.index() + 1); -// } -// } else { -// break; -// } -// } -// -// track.addChild(new ObjectComponent(trackContainer.container().trackListContainer(), object), c -> c.layoutOptions(new AbsoluteLayoutOptions(x, 0))); - } -} diff --git a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java deleted file mode 100644 index 7874e54d..00000000 --- a/editor/src/main/java/oxy/bascenario/utils/drag/SecondaryDragComponent.java +++ /dev/null @@ -1,123 +0,0 @@ -package oxy.bascenario.utils.drag; - -import lombok.RequiredArgsConstructor; -import net.lenni0451.commons.color.Color; -import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextOrigin; -import oxy.bascenario.editor.containers.oldstuff.object.ObjectComponent; -import oxy.bascenario.editor.containers.oldstuff.track.TrackContainer; -import oxy.bascenario.utils.NameUtils; - -import java.util.*; - -@RequiredArgsConstructor -public class SecondaryDragComponent extends Component { - private final TimelineContainer timelineContainer; - - private final float mouseX, mouseY; - private final List list; - - @Override - public void render(Renderer renderer, Size size) { - for (Data data : list) { - renderer.translate(mouseX + data.offsetX, mouseY + data.offsetY, () -> { - float width = timelineContainer.screen().oneMilSecondWidth() * data.object.object().duration; - - renderer.fillRect(0, 0, width, 60, Color.fromRGB(30, 30, 30)); - renderer.outlineRect(0, 0, width, 60, 2, Color.fromRGB(145, 218, 255).darker()); - - renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(data.object.object().object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); - }); - } - } - - - @Override - public Size computeIdealSize(Size size) { - return size; - } - - public record Data(ObjectComponent object, float offsetX, float offsetY) { -// public void handle(final TrackContainer container, float x) { -// final float dataX = Math.max(0, x + offsetX); -// long time = Math.max(0, (long) (dataX / container.container().screen().oneMilSecondWidth())); -// -// TrackContainer track = container; -// while (true) { -// boolean accepted = true; -// for (Component baseComponent : track.children()) { -// if (!(baseComponent instanceof ObjectComponent component) || component == object) { -// continue; -// } -// -// long otherMin = component.object().start; -// -// long maxTime = object.object().start + object.object().duration; -// long otherMaxTime = component.object().start + component.object().duration; -// -// if (maxTime >= otherMin && time <= otherMaxTime) { -// accepted = false; -// break; -// } -// } -// -// if (!accepted) { -// int size = container.container().trackListContainer().container().children().size(); -// if (track.index() >= size - 1) { -// track = new TrackContainer(container.container()); -// container.container().trackListContainer().container().addChild(track); -// container.container().trackTabListContainer().addChild(new TrackTabContainer(track)); -// break; -// } else { -// track = (TrackContainer) container.container().trackListContainer().container().children().get(track.index() + 1); -// } -// } else { -// break; -// } -// } -// -// object.dragging(false); -// -// object.layoutOptions(new AbsoluteLayoutOptions(dataX, 0)); -// object.object().start = time; -// track.addChild(object); -// } - } - - public void handle(final TrackContainer container, float x) { -// if (list.isEmpty()) { -// return; -// } -// -// list.sort(Comparator.comparingInt(data -> ((TrackContainer)data.object.parent()).index())); -// -// int firstIndex = ((TrackContainer)list.getFirst().object.parent()).index(); -// -// final Map tempMap = new HashMap<>(); -// for (Data data : list) { // Remove first. -// tempMap.put(data, ((TrackContainer)data.object.parent()).index()); -// ((Container)data.object.parent()).removeChild(data.object); -// } -// -// for (Data data : list) { -// TrackContainer trackContainer = container; -// int offset = tempMap.get(data) - firstIndex; -// if (offset > 0) { -// int newIndex = trackContainer.index() + offset; -// -// int size = container.container().trackListContainer().container().children().size(); -// if (newIndex >= size) { -// trackContainer = new TrackContainer(container.container()); -// container.container().trackListContainer().container().addChild(trackContainer); -// container.container().trackTabListContainer().addChild(new TrackTabContainer(trackContainer)); -// } else { -// trackContainer = (TrackContainer) container.container().trackListContainer().container().children().get(newIndex); -// } -// } -// -// data.handle(trackContainer, x); -// } - } -} From 449d4fb765741896e14c7697d17e0fa811cc9c29 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Wed, 15 Jul 2026 23:52:18 +0700 Subject: [PATCH 66/71] More stuff. --- .../oxy/bascenario/editor/EditorValues.java | 8 +- .../editor/ScenarioEditorScreen.java | 2 +- .../editor/containers/GlobalContainer.java | 2 +- .../sequencer/SequencerTimeSection.java | 69 ++++++++ .../sequencer/VideoSequencerContainer.java | 70 +++++++- .../timeline/TimelineContainer.java | 5 +- .../containers/timeline/TimelineDockBar.java | 4 +- .../containers/track/TrackListContainer.java | 154 ++++++++++++++++++ .../track/TrackSelectionManager.java | 54 ++++++ .../track/TrackTabListContainer.java | 68 ++++++++ .../track/component/ObjectComponent.java | 114 +++++++++++++ .../track/component/TrackContainer.java | 56 +++++++ .../track/component/TrackTabContainer.java | 45 +++++ .../editor/drag/FakeObjectComponent.java | 86 ++++++++++ .../editor/drag/SecondaryDragComponent.java | 124 ++++++++++++++ .../editor/object/ObjectOrEvent.java | 13 ++ .../components/TimelineDockExpandButton.java | 36 +++- .../assets/base/uis/editor/add_24.png | Bin 661 -> 0 bytes .../uis/editor/icons/blender_icon_action.svg | 1 + .../uis/editor/icons/blender_icon_add.svg | 1 + .../icons/blender_icon_downarrow_hlt.svg | 1 + .../editor/icons/blender_icon_sequence.svg | 1 + .../uis/editor/icons/blender_icon_trash.svg | 1 + .../assets/base/uis/editor/pause_18.png | Bin 519 -> 0 bytes .../assets/base/uis/editor/play_18.png | Bin 582 -> 0 bytes .../assets/base/uis/editor/trash_18.png | Bin 703 -> 0 bytes 26 files changed, 903 insertions(+), 12 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/TrackSelectionManager.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/TrackTabListContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/component/ObjectComponent.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackTabContainer.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java create mode 100644 editor/src/main/java/oxy/bascenario/editor/object/ObjectOrEvent.java delete mode 100644 editor/src/main/resources/assets/base/uis/editor/add_24.png create mode 100644 editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_action.svg create mode 100644 editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_add.svg create mode 100644 editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_downarrow_hlt.svg create mode 100644 editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_sequence.svg create mode 100644 editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_trash.svg delete mode 100644 editor/src/main/resources/assets/base/uis/editor/pause_18.png delete mode 100644 editor/src/main/resources/assets/base/uis/editor/play_18.png delete mode 100644 editor/src/main/resources/assets/base/uis/editor/trash_18.png diff --git a/editor/src/main/java/oxy/bascenario/editor/EditorValues.java b/editor/src/main/java/oxy/bascenario/editor/EditorValues.java index d700df09..77dbe798 100644 --- a/editor/src/main/java/oxy/bascenario/editor/EditorValues.java +++ b/editor/src/main/java/oxy/bascenario/editor/EditorValues.java @@ -13,8 +13,10 @@ public class EditorValues { @Setter private static EditorValues instance; + private TimelineType type = TimelineType.Sequencer; + private boolean playing; - private long timestamp = 0; + private long timestamp = 1000L; private float scale = 1; private float scroll; @@ -37,4 +39,8 @@ public void tick() { } since = System.currentTimeMillis(); } + + public enum TimelineType { + Dope_Sheet, Sequencer + } } diff --git a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java index 26e7ffa6..29ceb49a 100644 --- a/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java +++ b/editor/src/main/java/oxy/bascenario/editor/ScenarioEditorScreen.java @@ -41,7 +41,7 @@ public void init(Rivet rivet) { mainContainer.addChild(new DecoratedContainer(new SolidColor(), new GlobalContainer()), c -> { c.layoutOptions(DockPosition.CENTER); - c.innerPadding(new Padding(2, 8, 2, 24)); + c.innerPadding(new Padding(2, 8, 2, 10)); }); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java index e6ba5c03..88a3454f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java @@ -41,7 +41,7 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { super.onComponentMouseMove(event, size); Rectangle timelineBounds = this.childBounds(timeline); - boolean nearTimeline = Math.abs(event.y() - timelineBounds.y()) < 15; + boolean nearTimeline = Math.abs(event.y() - timelineBounds.y()) < 8; if (nearTimeline && !this.nearTimeline) { GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_VRESIZE_CURSOR)); } else if (!nearTimeline && this.nearTimeline && !nearTimelineAndMouseDown) { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java new file mode 100644 index 00000000..78fe7868 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java @@ -0,0 +1,69 @@ +package oxy.bascenario.editor.containers.sequencer; + +import lombok.RequiredArgsConstructor; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.EditorValues; + +@RequiredArgsConstructor +public class SequencerTimeSection extends Component { + @Override + public void render(Renderer renderer, Size size) { + renderer.fillRect(0, size.height() - 4, size.width(), 4, Color.fromRGB(108, 108, 210)); + + float x = -EditorValues.instance().scroll(); + while (!(x >= size.width())) { + long time = (long) ((x + EditorValues.instance().scroll()) / EditorValues.instance().oneMilSecondWidth()); + + ShapedText text = this.rivet().backend().font().shapeText(format(time), Color.GRAY); + float currentX = x; + + renderer.scale(0.38f, () -> renderer.text(text, currentX / 0.38f, (5 / 35f) * size.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + + x += EditorValues.instance().oneSecondWidth(); + } + } + + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { + if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height() && event.buttons().contains(MouseButton.LEFT)) { + float x = EditorValues.instance().scroll() + event.x() - 2.5f; + long newTimestamp = (long) (x / (EditorValues.instance().oneMilSecondWidth())); + + EditorValues.instance().timestamp(Math.max(0, newTimestamp)); + } + + return false; + } + + @Override + protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { + if (event.x() > 0 && event.x() < bounds.width() && event.y() > 0 && event.y() < bounds.height()) { + float x = EditorValues.instance().scroll() + event.x() - 2.5f; + long newTimestamp = (long) (x / (EditorValues.instance().oneMilSecondWidth())); + + EditorValues.instance().timestamp(Math.max(0, newTimestamp)); + } + + return super.onComponentMouseDown(event, bounds); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), 28); + } + + private static String format(long ms) { + long second = (ms / 1000) % 60; + long minute = (ms / (1000 * 60)) % 60; + long hour = (ms / (1000 * 60 * 60)) % 24; + return (hour < 10 ? "0" + hour : hour) + ":" + (minute < 10 ? "0" + minute : minute) + ":" + (second < 10 ? "0" + second : second); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/VideoSequencerContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/VideoSequencerContainer.java index 2c4da918..30af177e 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/VideoSequencerContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/VideoSequencerContainer.java @@ -1,24 +1,90 @@ package oxy.bascenario.editor.containers.sequencer; +import lombok.Getter; +import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.layout.border.BorderLayout; +import net.lenni0451.rivet.component.container.DecoratedContainer; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.component.impl.SolidColor; +import net.lenni0451.rivet.layout.dock.DockLayout; +import net.lenni0451.rivet.layout.dock.DockPosition; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Rectangle; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.containers.track.TrackListContainer; +import oxy.bascenario.editor.containers.track.TrackTabListContainer; +import oxy.bascenario.editor.containers.track.component.TrackContainer; +import oxy.bascenario.editor.containers.track.component.TrackTabContainer; +@Accessors(fluent = true) public class VideoSequencerContainer extends Container { + final Component sequenceTimeSection; + + final TrackTabListContainer trackTabListContainer; + @Getter + final ScrollContainer trackListContainer; + public VideoSequencerContainer() { - super(BorderLayout.INSTANCE); + super(new DockLayout()); + + final Container mouseContainer = new Container(new VerticalListLayout()) { + @Override + public Size computeIdealSize(Size constraints) { + return constraints.withWidth(55f); + } + }; + + this.addChild(new DecoratedContainer(new SolidColor(Color.fromRGB(48, 48, 48)), mouseContainer), c -> c.layoutOptions(DockPosition.LEFT)); + + this.addChild(trackTabListContainer = new TrackTabListContainer(this), c -> c.layoutOptions(DockPosition.LEFT)); + + this.addChild(sequenceTimeSection = new SequencerTimeSection(), c -> c.layoutOptions(DockPosition.TOP)); + + this.addChild(trackListContainer = new TrackListContainer(), c -> c.layoutOptions(DockPosition.CENTER)); } @Override public void render(Renderer renderer, Size size) { renderer.fillRect(0, 0, size.width(), size.height(), Color.fromRGB(30, 30, 30)); + super.render(renderer, size); + drawCursor(renderer, size); } @Override public Size computeIdealSize(Size constraints) { return constraints; } + + private static final Color SEQUENCER_CURSOR_COLOR = Color.fromRGB(71, 114, 179); + + private void drawCursor(Renderer renderer, Size size) { + Rectangle bounds = this.childBounds(trackListContainer); + + final float offsetX = bounds.x(); + final float cursorX = -2.5f + offsetX + EditorValues.instance().timestamp() * (EditorValues.instance().oneSecondWidth() / 1000f) - EditorValues.instance().scroll(); + + renderer.componentBounds(bounds.x(), 0, bounds.width(), this.absoluteBounds().height(), () -> { + renderer.fillRect(cursorX, 0, 5, size.height(), SEQUENCER_CURSOR_COLOR.withAlphaF(0.8f)); + renderer.outlineRect(cursorX, 0, 5, size.height(), 1, Color.BLACK); + + renderer.fillTriangle(cursorX + 2.5f - 10, 0, cursorX + 2.5f, 10, cursorX + 2.5f + 10, 0, SEQUENCER_CURSOR_COLOR); + }); + } + + public TrackContainer createTrack() { + final TrackContainer container = new TrackContainer(); + ((Container) trackListContainer.child()).addChild(container); + trackTabListContainer.container().addChild(new TrackTabContainer(this, container)); + return container; + } + + public void removeTrack(TrackContainer container, TrackTabContainer tabContainer) { + ((Container) trackListContainer.child()).removeChild(container); + trackTabListContainer.container().removeChild(tabContainer); + } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java index 4eff3631..ca278055 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java @@ -4,7 +4,6 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.container.ComboBox; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.DecoratedContainer; import net.lenni0451.rivet.component.impl.SolidColor; @@ -24,7 +23,7 @@ public TimelineContainer() { this.addChild(new DecoratedContainer(new SolidColor(), new TimelineDockBar()), c -> { c.layoutOptions(DockPosition.TOP); - c.innerPadding(new Padding(8, 2, 0, 0)); + c.innerPadding(new Padding(8, 3, 0, 0)); }); this.addChild(new TimelineTimeControl(), c -> c.layoutOptions(DockPosition.BOTTOM)); this.addChild(new VideoSequencerContainer(), c -> c.layoutOptions(DockPosition.CENTER)); @@ -32,9 +31,9 @@ public TimelineContainer() { @Override public void render(Renderer renderer, Size size) { - renderer.outlineRoundedRect(0, 0, size.width(), size.height(), 5, 1, Color.fromRGB(60, 60, 60)); renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(48, 48, 48)); super.render(renderer, size); + renderer.outlineRoundedRect(0, 0, size.width(), size.height(), 5, 1, Color.fromRGB(59, 59, 59)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java index 1aace333..ad67879f 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java @@ -7,13 +7,13 @@ public class TimelineDockBar extends Container { public TimelineDockBar() { - super(new HorizontalListLayout(12, true)); + super(new HorizontalListLayout(12, false)); addChild(new TimelineDockExpandButton()); } @Override public Size computeIdealSize(Size constraints) { - return constraints.withHeight(25); + return constraints.withHeight(24); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java new file mode 100644 index 00000000..47d918b3 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -0,0 +1,154 @@ +package oxy.bascenario.editor.containers.track; + +import lombok.Getter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.text.ShapedText; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.input.keyboard.Key; +import net.lenni0451.rivet.input.keyboard.KeyEvent; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.input.mouse.MouseScrollEvent; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.containers.track.component.ObjectComponent; +import oxy.bascenario.editor.containers.track.component.TrackContainer; + +@Accessors(fluent = true) +public class TrackListContainer extends ScrollContainer { + @Getter + private final Container container; + + @Getter + private final TrackSelectionManager selectionManager = new TrackSelectionManager(); + + public TrackListContainer() { + super(container = new Container(new VerticalListLayout(3, false)), true, true); + } + + private float prevWidth; + + @Override + public void render(Renderer renderer, Size size) { + if (prevWidth != size.width()) { + recalculateObjectPosition(); + this.requestLayoutRecalculation(); + } + prevWidth = size.width(); + + float y = -scrollY(); + int i = 0; + while (!(y >= size.height())) { + if (i % 2 == 0) { + renderer.fillRect(0, y, size.width(), 60, Color.fromRGB(24, 24, 24)); + } + + y += 60; + i++; + } + + drawTimestampLine(renderer, size); + + super.render(renderer, size); + + this.selectionManager.render(renderer); + EditorValues.instance().scroll(scrollX()); + } + + @Override + public Size computeIdealSize(Size constraints) { + return constraints; + } + + public void recalculateObjectPosition() { + for (Component component : container.children()) { + if (!(component instanceof TrackContainer track)) { + continue; + } + + for (Component child : track.children()) { + if (!(child instanceof ObjectComponent object)) { + continue; + } + + float newX = timestampToPosition(object.object().start, 0, EditorValues.instance().oneMilSecondWidth(), EditorValues.instance().scale()); + child.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); + } + } + } + + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Size bounds) { + if (event.buttons().contains(MouseButton.LEFT)) { + this.selectionManager.x1(event.x()); + this.selectionManager.y1(event.y()); + this.rivet().focusedComponent(this); + } + + return super.onComponentMouseMove(event, bounds); + } + + @Override + protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { + if (!super.onComponentMouseDown(event, bounds) && event.button() == MouseButton.LEFT) { + this.selectionManager.x(event.x()); + this.selectionManager.y(event.y()); + + this.selectionManager.x1(event.x()); + this.selectionManager.y1(event.y()); + + this.selectionManager.objects().clear(); + + this.rivet().focusedComponent(this); + } + return true; + } + + @Override + protected boolean onComponentMouseUp(MouseButtonEvent event, Size bounds) { + if (event.button() == MouseButton.LEFT) { + this.selectionManager.x(0); + this.selectionManager.y(0); + } + + return super.onComponentMouseUp(event, bounds); + } + + @Override + protected boolean onComponentKeyUp(KeyEvent event) { + if (event.key() == Key.DELETE) { + selectionManager.objects().forEach(component -> ((Container)component.parent()).removeChild(component)); + selectionManager().objects().clear(); + } + + return false; + } + + private static float timestampToPosition(long timestamp, float offsetX, float size, float scale) { + return offsetX + (timestamp * size * scale); + } + + @Override + protected boolean onComponentMouseScroll(MouseScrollEvent event, Size size) { + return true; + } + + private void drawTimestampLine(Renderer renderer, Size size) { + float x = -EditorValues.instance().scroll(); + while (!(x >= size.width())) { + float currentX = x - 0.5f; + + renderer.fillRect(currentX, 0, 1, size.height(), Color.fromRGB(48, 48, 48)); + + x += EditorValues.instance().oneSecondWidth(); + } + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackSelectionManager.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackSelectionManager.java new file mode 100644 index 00000000..ad97aba1 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackSelectionManager.java @@ -0,0 +1,54 @@ +package oxy.bascenario.editor.containers.track; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.math.Rectangle; +import oxy.bascenario.editor.containers.track.component.ObjectComponent; + +import java.util.HashSet; +import java.util.Set; + +@Accessors(fluent = true) +@Setter +public class TrackSelectionManager { + @Getter + private Set objects = new HashSet<>(); + public boolean isSelected(ObjectComponent component) { + return objects.contains(component); + } + + private float x, y; + private float x1, y1; + + public void render(Renderer renderer) { + if (x == 0 && y == 0) { + return; + } + float minX = Math.min(x, x1), minY = Math.min(y, y1); + float maxX = Math.max(x, x1), maxY = Math.max(y, y1); + + renderer.fillRect(minX, minY, maxX - minX, maxY - minY, Color.WHITE.withAlphaF(0.4f)); + renderer.outlineRect(minX, minY, maxX - minX, maxY - minY, 2, Color.WHITE); + } + + public void addOrRemove(ObjectComponent component, Rectangle rectangle) { + if (x == 0 && y == 0) { + return; + } + + float minX = Math.min(x, x1), minY = Math.min(y, y1); + float maxX = Math.max(x, x1), maxY = Math.max(y, y1); + + if (maxX >= rectangle.x() && minX <= rectangle.maxX() && maxY >= rectangle.y() && minY <= rectangle.maxY() + + ((Container)component.parent()).parent().childBounds((Component) component.parent()).y()) { + objects.add(component); + } else { + objects.remove(component); + } + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackTabListContainer.java new file mode 100644 index 00000000..80f588c3 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackTabListContainer.java @@ -0,0 +1,68 @@ +package oxy.bascenario.editor.containers.track; + +import lombok.Getter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.border.BorderLayout; +import net.lenni0451.rivet.layout.border.BorderPosition; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Size; +import oxy.bascenario.Base; +import oxy.bascenario.editor.containers.sequencer.VideoSequencerContainer; +import oxy.bascenario.utils.components.ButtonImage; + +@Accessors(fluent = true) +public class TrackTabListContainer extends Container { + @Getter + private final Container container; + + public TrackTabListContainer(VideoSequencerContainer sequencerContainer) { + super(BorderLayout.INSTANCE); + + this.addChild(new UpperContainer(sequencerContainer), c -> c.layoutOptions(BorderPosition.TOP)); + + final ScrollContainer scrollContainer = new ScrollContainer(container = new Container(new VerticalListLayout()), false, true) { + @Override + public void render(Renderer renderer, Size size) { + scrollY(sequencerContainer.trackListContainer().scrollY(), true); + super.render(renderer, size); + } + }; + scrollContainer.barWidth().set(0f); + + this.addChild(scrollContainer, c -> c.layoutOptions(BorderPosition.CENTER)); + } + + @Override + public Size computeIdealSize(Size constraints) { + return constraints.withWidth(160); + } + + private static class UpperContainer extends Container { + public UpperContainer(VideoSequencerContainer sequencerContainer) { + super(AbsoluteLayout.INSTANCE); + + final ButtonImage add = new ButtonImage(Base.instance().assetsManager().texture("assets/base/uis/editor/icons/blender_icon_add.svg"), event -> { + if (event.button() != MouseButton.LEFT) { + return; + } + + sequencerContainer.createTrack(); + }); + add.hoverColor().set(Color.fromRGB(145, 218, 255)); + + this.addChild(add, c -> c.layoutOptions(new AbsoluteLayoutOptions(5, 5))); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), 25); + } + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/ObjectComponent.java new file mode 100644 index 00000000..77066d0c --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/ObjectComponent.java @@ -0,0 +1,114 @@ +package oxy.bascenario.editor.containers.track.component; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.input.keyboard.ModifierKey; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.input.mouse.MouseButtonEvent; +import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.math.Rectangle; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.containers.track.TrackListContainer; +import oxy.bascenario.editor.drag.SecondaryDragComponent; +import oxy.bascenario.editor.object.ObjectOrEvent; +import oxy.bascenario.utils.Launcher; +import oxy.bascenario.utils.NameUtils; + +import java.util.ArrayList; +import java.util.List; + +@Accessors(fluent = true) +public class ObjectComponent extends Component { + private static final Color OBJECT_COLOR = Color.fromRGB(145, 218, 255).darker(); + + private final TrackListContainer parent; + @Getter + private final ObjectOrEvent object; + + @Setter + private boolean dragging; + + public ObjectComponent(TrackListContainer parent, ObjectOrEvent object) { + this.parent = parent; + this.object = object; + } + + @Override + public void render(Renderer renderer, Size bounds) { + if (dragging) { + if (!rivet().dragAndDropManager().isDragging()) { + dragging = false; + } + return; + } + + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); + renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); + + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + + this.parent.selectionManager().addOrRemove(this, parent().childBounds(this)); + } + + @Override + protected boolean onComponentMouseDown(MouseButtonEvent event, Size bounds) { + if (this.parent.selectionManager().isSelected(this)) { + return true; + } + + if (!event.modifiers().contains(ModifierKey.CONTROL)) { + this.parent.selectionManager().objects().clear(); + } + + this.parent.selectionManager().objects().add(this); + return true; + } + + @Override + protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { + if (!event.buttons().contains(MouseButton.LEFT)) { + return false; + } + + if (rivet().dragAndDropManager().isDragging()) { + return true; + } + + Rectangle clickedBounds = parent().childBounds(this); + + final List list = new ArrayList<>(); + for (ObjectComponent component : parent.selectionManager().objects()) { + Rectangle bounds = component.parent().childBounds(component); + float x = -event.x() + (bounds.x() - clickedBounds.x()); + float y = -event.y(); + if (component.parent() != parent()) { + y += (component.parent().absoluteBounds().y() - parent().absoluteBounds().y()); + } + + list.add(new SecondaryDragComponent.Data(component, x, y)); + component.dragging(true); + } + + // Ehhh using mouseX and mouseY from EngineRenderer is a bit hacky here... + final SecondaryDragComponent dragComponent = new SecondaryDragComponent((float) Launcher.WINDOW.mouseX, (float) Launcher.WINDOW.mouseY, list); + + rivet().dragAndDropManager().startDrag( + dragComponent, + dragComponent, + (float) -Launcher.WINDOW.mouseX, (float) -Launcher.WINDOW.mouseY + ); + + return true; + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(EditorValues.instance().oneMilSecondWidth() * object.duration, constraints.height()); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackContainer.java new file mode 100644 index 00000000..e5d7064b --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackContainer.java @@ -0,0 +1,56 @@ +package oxy.bascenario.editor.containers.track.component; + +import lombok.Getter; +import lombok.experimental.Accessors; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.dragdrop.DropEvent; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.drag.FakeObjectComponent; +import oxy.bascenario.editor.drag.SecondaryDragComponent; + +@Accessors(fluent = true) +public class TrackContainer extends Container { + @Getter + private float height = 60f; + + public TrackContainer() { + super(AbsoluteLayout.INSTANCE); + + // So the user can scroll more by default. + this.addChild(new Component() { + @Override + public Size computeIdealSize(Size constraints) { + return new Size(1.0e-16f, 1.0e-16f); + } + }, c -> c.layoutOptions(new AbsoluteLayoutOptions(3000, 0, 5f, 5f))); + } + + @Override + protected boolean onComponentDrop(DropEvent event, Size bounds) { + EditorValues.instance().playing(false); + + event.dragData().forEach(data -> { + if (data instanceof FakeObjectComponent component) { + component.handle(this, event.x()); + } else if (data instanceof SecondaryDragComponent component) { + component.handle(this, event.x()); + } + }); + + return !event.dragData().isEmpty(); + } + + @Override + public Size computeIdealSize(Size constraints) { + Size idealSize = super.computeIdealSize(new Size(constraints.width(), height)); + return new Size(idealSize.width(), height); + } + + public int index() { + return parent().children().indexOf(this); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackTabContainer.java new file mode 100644 index 00000000..153d1af0 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackTabContainer.java @@ -0,0 +1,45 @@ +package oxy.bascenario.editor.containers.track.component; + +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.input.mouse.MouseButton; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.Base; +import oxy.bascenario.editor.containers.sequencer.VideoSequencerContainer; +import oxy.bascenario.utils.components.ButtonImage; + +public class TrackTabContainer extends Container { + private final TrackContainer container; + + public TrackTabContainer(VideoSequencerContainer sequencerContainer, TrackContainer container) { + super(AbsoluteLayout.INSTANCE); + this.container = container; + + final ButtonImage trash = new ButtonImage(Base.instance().assetsManager().texture("assets/base/uis/editor/icons/blender_icon_trash.svg"), event -> { + if (event.button() != MouseButton.LEFT) { + return; + } + + sequencerContainer.removeTrack(container, this); + }); + trash.hoverColor().set(Color.fromRGB(145, 218, 255)); + this.addChild(trash, image -> image.layoutOptions(new AbsoluteLayoutOptions(5, 35))); + } + + @Override + public void render(Renderer renderer, Size bounds) { +// renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.RED); + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText("Track " + container.index(), Color.fromRGB(148, 148, 148)), 15, 25, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + + super.render(renderer, bounds); + } + + @Override + public Size computeIdealSize(Size constraints) { + return new Size(constraints.width(), container.height()); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java new file mode 100644 index 00000000..cd9aeeb6 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java @@ -0,0 +1,86 @@ +package oxy.bascenario.editor.drag; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.api.render.RenderLayer; +import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.containers.track.component.ObjectComponent; +import oxy.bascenario.editor.containers.track.component.TrackContainer; +import oxy.bascenario.editor.object.ObjectOrEvent; +import oxy.bascenario.utils.NameUtils; +import oxy.bascenario.utils.TimeCompiler; + +@AllArgsConstructor +@Accessors(fluent = true) +public class FakeObjectComponent extends Component { + @Getter + private final Object object; + private final long duration; + + @Override + public void render(Renderer renderer, Size bounds) { + renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); + renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, Color.fromRGB(145, 218, 255).darker()); + + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + } + + @Override + public Size computeIdealSize(Size constraints) { + float widthToAMil = EditorValues.instance().oneSecondWidth() / 1000f; + return new Size(widthToAMil * duration, 60); + } + + public void handle(TrackContainer trackContainer, float x) { +// long time = (long) (x / EditorValues.instance().oneMilSecondWidth()); +// long duration = TimeCompiler.compileTime(object()); +// if (duration == Long.MAX_VALUE) { +// duration = 1000L; +// } +// +// final ObjectOrEvent object = new ObjectOrEvent(time, duration, object(), RenderLayer.ABOVE_DIALOGUE, true); +// +// TrackContainer track = trackContainer; +// while (true) { +// boolean accepted = true; +// for (Component baseComponent : track.children()) { +// if (!(baseComponent instanceof ObjectComponent component)) { +// continue; +// } +// +// long otherMin = component.object().start; +// +// long maxTime = component.object().start + component.object().duration; +// long otherMaxTime = component.object().start + component.object().duration; +// +// if (maxTime >= otherMin && time <= otherMaxTime) { +// accepted = false; +// break; +// } +// } +// +// if (!accepted) { +// int size = trackContainer.container().trackListContainer().container().children().size(); +// if (track.index() >= size - 1) { +// track = new TrackContainer(); +// trackContainer.container().trackListContainer().container().addChild(track); +// trackContainer.container().trackTabListContainer().addChild(new TrackTabContainer(track)); +// break; +// } else { +// track = (TrackContainer) trackContainer.container().trackListContainer().container().children().get(track.index() + 1); +// } +// } else { +// break; +// } +// } +// +// track.addChild(new ObjectComponent(trackContainer.container().trackListContainer(), object), c -> c.layoutOptions(new AbsoluteLayoutOptions(x, 0))); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java new file mode 100644 index 00000000..4c118cab --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java @@ -0,0 +1,124 @@ +package oxy.bascenario.editor.drag; + +import lombok.RequiredArgsConstructor; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.containers.track.component.ObjectComponent; +import oxy.bascenario.editor.containers.track.component.TrackContainer; +import oxy.bascenario.utils.NameUtils; + +import java.util.*; + +@RequiredArgsConstructor +public class SecondaryDragComponent extends Component { + private final float mouseX, mouseY; + private final List list; + + @Override + public void render(Renderer renderer, Size size) { + for (Data data : list) { + renderer.translate(mouseX + data.offsetX, mouseY + data.offsetY, () -> { + float width = EditorValues.instance().oneMilSecondWidth() * data.object.object().duration; + + renderer.fillRect(0, 0, width, 60, Color.fromRGB(30, 30, 30)); + renderer.outlineRect(0, 0, width, 60, 2, Color.fromRGB(145, 218, 255).darker()); + + renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(data.object.object().object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + }); + } + } + + + @Override + public Size computeIdealSize(Size size) { + return size; + } + + public record Data(ObjectComponent object, float offsetX, float offsetY) { +// public void handle(final TrackContainer container, float x) { +// final float dataX = Math.max(0, x + offsetX); +// long time = Math.max(0, (long) (dataX / container.container().screen().oneMilSecondWidth())); +// +// TrackContainer track = container; +// while (true) { +// boolean accepted = true; +// for (Component baseComponent : track.children()) { +// if (!(baseComponent instanceof ObjectComponent component) || component == object) { +// continue; +// } +// +// long otherMin = component.object().start; +// +// long maxTime = object.object().start + object.object().duration; +// long otherMaxTime = component.object().start + component.object().duration; +// +// if (maxTime >= otherMin && time <= otherMaxTime) { +// accepted = false; +// break; +// } +// } +// +// if (!accepted) { +// int size = container.container().trackListContainer().container().children().size(); +// if (track.index() >= size - 1) { +// track = new TrackContainer(); +// container.container().trackListContainer().container().addChild(track); +// container.container().trackTabListContainer().addChild(new TrackTabContainer(track)); +// break; +// } else { +// track = (TrackContainer) container.container().trackListContainer().container().children().get(track.index() + 1); +// } +// } else { +// break; +// } +// } +// +// object.dragging(false); +// +// object.layoutOptions(new AbsoluteLayoutOptions(dataX, 0)); +// object.object().start = time; +// track.addChild(object); +// } + } + + public void handle(final TrackContainer container, float x) { +// if (list.isEmpty()) { +// return; +// } +// +// list.sort(Comparator.comparingInt(data -> ((TrackContainer)data.object.parent()).index())); +// +// int firstIndex = ((TrackContainer)list.getFirst().object.parent()).index(); +// +// final Map tempMap = new HashMap<>(); +// for (Data data : list) { // Remove first. +// tempMap.put(data, ((TrackContainer)data.object.parent()).index()); +// ((Container)data.object.parent()).removeChild(data.object); +// } +// +// for (Data data : list) { +// TrackContainer trackContainer = container; +// int offset = tempMap.get(data) - firstIndex; +// if (offset > 0) { +// int newIndex = trackContainer.index() + offset; +// +// int size = container.container().trackListContainer().container().children().size(); +// if (newIndex >= size) { +// trackContainer = new TrackContainer(); +// container.container().trackListContainer().container().addChild(trackContainer); +// container.container().trackTabListContainer().addChild(new TrackTabContainer(trackContainer)); +// } else { +// trackContainer = (TrackContainer) container.container().trackListContainer().container().children().get(newIndex); +// } +// } +// +// data.handle(trackContainer, x); +// } + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/object/ObjectOrEvent.java b/editor/src/main/java/oxy/bascenario/editor/object/ObjectOrEvent.java new file mode 100644 index 00000000..7a10e061 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/object/ObjectOrEvent.java @@ -0,0 +1,13 @@ +package oxy.bascenario.editor.object; + +import lombok.AllArgsConstructor; +import oxy.bascenario.api.render.RenderLayer; + +@AllArgsConstructor +public class ObjectOrEvent { + public long start, duration; + + public Object object; + public RenderLayer layer; + public boolean requireWait; +} diff --git a/editor/src/main/java/oxy/bascenario/utils/components/TimelineDockExpandButton.java b/editor/src/main/java/oxy/bascenario/utils/components/TimelineDockExpandButton.java index d9f7ce0f..821233d6 100644 --- a/editor/src/main/java/oxy/bascenario/utils/components/TimelineDockExpandButton.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/TimelineDockExpandButton.java @@ -2,18 +2,50 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.backend.thingl.ThinGLTexture; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.Base; +import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.utils.animation.math.ColorAnimations; public class TimelineDockExpandButton extends Component { + private final ColorAnimations backgroundColor = new ColorAnimations(Color.fromRGB(40, 40, 40)); + private final ThinGLTexture downTexture = new ThinGLTexture(Base.instance().assetsManager().texture("assets/base/uis/editor/icons/blender_icon_downarrow_hlt.svg")); + + private final ThinGLTexture sequenceTexture = new ThinGLTexture(Base.instance().assetsManager().texture("assets/base/uis/editor/icons/blender_icon_sequence.svg")); + private final ThinGLTexture actionTexture = new ThinGLTexture(Base.instance().assetsManager().texture("assets/base/uis/editor/icons/blender_icon_action.svg")); + @Override public void render(Renderer renderer, Size size) { + renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, backgroundColor.color()); renderer.outlineRoundedRect(0, 0, size.width(), size.height(), 5, 1, Color.fromRGB(60, 60, 60)); - renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(40, 40, 40)); + + renderer.image(downTexture, 112, 6, downTexture.width() * .7f, downTexture.height() * .7f, Color.WHITE); + + if (EditorValues.instance().type() == EditorValues.TimelineType.Sequencer) { + renderer.image(sequenceTexture, 5, 0, sequenceTexture.width() * .9f, sequenceTexture.height() * .9f, Color.WHITE); + } else { + renderer.image(actionTexture, 5, 3, actionTexture.width() * .9f, actionTexture.height() * .9f, Color.WHITE); + } + + String current = EditorValues.instance().type().name().replace("_", " "); + renderer.text(rivet().backend().font().derive(12).shapeText(current, Color.WHITE), 30, 11, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_CENTER); + } + + @Override + protected void onComponentMouseEnter() { + backgroundColor.set(Color.fromRGB(48, 48, 48), 500); + } + + @Override + protected void onComponentMouseLeave() { + backgroundColor.set(Color.fromRGB(40, 40, 40), 500); } @Override public Size computeIdealSize(Size size) { - return new Size(32, 18); + return new Size(130, 20); } } diff --git a/editor/src/main/resources/assets/base/uis/editor/add_24.png b/editor/src/main/resources/assets/base/uis/editor/add_24.png deleted file mode 100644 index f43920e13fba7e82f912d836e613fc3ba728f6a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 661 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=fpk6yGk#oj#~H|wEOCt}3C>R|DNig)We7;j%q!9Ja}7~2)icyH zG!kZXF99mrks9Hd=IN=$zyaj2GDtD9GB5&JUO+4jWrLif!N?32X9BVf8JQRafpioQ zXSTC|#dCmc5Rle&Heg%;F&AVf>jH>b(|~LcU;>)V z1XdYjX#r$Gbr~8MfMn0b{$F;hXy1Qe$T)hsIEHw5Z=Gi7eZ)Ybt$6K%#q9G=_bUD1 zFDbGRcm2P%*`nj#lJZCV0R<%&gk$n8qVKE!g+Tk&+} zoDDD|D15?4k%zfUHx3vIVCg!04;ReHUIzs diff --git a/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_action.svg b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_action.svg new file mode 100644 index 00000000..d96a38be --- /dev/null +++ b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_action.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_add.svg b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_add.svg new file mode 100644 index 00000000..86dab65f --- /dev/null +++ b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_downarrow_hlt.svg b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_downarrow_hlt.svg new file mode 100644 index 00000000..c2eed90c --- /dev/null +++ b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_downarrow_hlt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_sequence.svg b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_sequence.svg new file mode 100644 index 00000000..8530f0c1 --- /dev/null +++ b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_sequence.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_trash.svg b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_trash.svg new file mode 100644 index 00000000..1ab63b6f --- /dev/null +++ b/editor/src/main/resources/assets/base/uis/editor/icons/blender_icon_trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/src/main/resources/assets/base/uis/editor/pause_18.png b/editor/src/main/resources/assets/base/uis/editor/pause_18.png deleted file mode 100644 index 10e39c9440c903c52d26ba0a1debb8505d58c512..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 519 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*bKsp4385gfTA_`KW=8 z8VNJHmjD&*NR9AJ^YqkW-~e)18Kf9l85n^qFCdnNvO&(#U}Of1GXdF#j7$uIKspMD zGuv6f;yFMz2;>2cdBKdJ(QRd507_0^XJ7%UH83(ZU|aw(7i1^v0*G1DfNT(80-DSO zRvBbz0c1gS85$UXWQ+D3(49VC+Zo8*?CIhdV&VUGiX+z{0|D1$@msa7{^@&rtIoZ0 za-8n!lvI6;>1s;*bKsp4385gfTA_`KW=8 z8VNJHmjD&*NR9AJ^YqkW-~e)18Kf9l85n^qFCdnNvO&(#U}Of1GXdF#j7$uIKspMD zGuv6f;yFMz2;>2cdBKdJ(QRd507_0^XJ7%UH83(ZU|aw(7i1^v0*G1DfNT(80-DSO zRvBbz0c1gS85$UXWQ+D3(49VC+Zo9Gm5&x8cgI;Wo-TN*&@NrAd_@O)L{f9mtwhaZMn!k4E#1=cSYknzcss2&S qZE<*B!1az5iM{eYf*+DDzmiwjW&bZ~=7~g5NO-#XxvX!lvI6;>1s;*bKspeF8T-T+J_a%*OI#yLg7ec#$`gxH83GbB^GfvmTtgI0^$hh4 zjf5H9OMr@Yq(*qAd3tIwZ~!^13{s4&42(dQ7Z6KB*&yd=FfxP1nSg9VMkWS9ARPt7 zne8lK@f;u<1eAcrykJJq=(aL203|1|Gq3>F8WQV0J5OE3=IrGvX4xAJuV%1J{cG?*`6+rAr}5~C-2o`4HRiT&(Rs=*b%@X80f39 zr$IBXH|oWf5Suo42lb09k8xKpTomAZ^xV&JLPy8^t>5g@-_0`?H{bW<^u5jR=S($D zf2Ni>Yu5~$^X`G)9?s#3k~zFo=k&TSn_Q=_H@t1Yr#R>1cB!ojZ_=2SE)37jd@xV* z{&W@Bz%L8r4hqh+Fz2b>`gdZvgxq_nbH*LJZ#6d0`PXc{BgQf(r&1=B=i9-S&1N+R z-76ZM?Wc2UGz%DY7BTHk{a{|RC$nYl$3t>v<(D!!9^NeKO=ym*<&`<)6upqsa`T>_ zQJhtuOgFHVZx#KfkUp)bm-i84)UStYw;ke@xv=fY!RztOZ3}pl&d*#LBW!=GeH)Yb zM`_MG&cD}wyqBKqD-icsz03BFMaSN|ftS|{zUk|T-N6vPRW$6uqHRsJZ=xvJgcz!plzheAbZk`R#d-mr#)rHGF-^bYYCFFBp`=JI< O6nMJ&xvX Date: Thu, 16 Jul 2026 18:35:00 +0700 Subject: [PATCH 67/71] stuff. --- .../oxy/bascenario/editor/EditorValues.java | 6 +- .../editor/containers/GlobalContainer.java | 154 ++++++++++++++---- .../containers/assets/AssetsContainer.java | 27 +++ .../sequencer/SequencerTimeSection.java | 2 +- .../timeline/TimelineContainer.java | 10 +- .../containers/timeline/TimelineDockBar.java | 41 ++++- .../timeline/TimelineTimeControl.java | 26 +++ 7 files changed, 223 insertions(+), 43 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java diff --git a/editor/src/main/java/oxy/bascenario/editor/EditorValues.java b/editor/src/main/java/oxy/bascenario/editor/EditorValues.java index 77dbe798..8bd62189 100644 --- a/editor/src/main/java/oxy/bascenario/editor/EditorValues.java +++ b/editor/src/main/java/oxy/bascenario/editor/EditorValues.java @@ -1,6 +1,5 @@ package oxy.bascenario.editor; -import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -17,6 +16,11 @@ public class EditorValues { private boolean playing; private long timestamp = 1000L; + public void timestamp(long timestamp) { + this.timestamp = timestamp; + this.playing = false; + } + private float scale = 1; private float scroll; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java index 88a3454f..1c39ba19 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java @@ -1,11 +1,18 @@ package oxy.bascenario.editor.containers; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.input.keyboard.Key; +import net.lenni0451.rivet.input.keyboard.KeyEvent; import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; +import net.lenni0451.rivet.layout.Layout; import net.lenni0451.rivet.layout.dock.DockLayout; import net.lenni0451.rivet.layout.dock.DockPosition; import net.lenni0451.rivet.math.Rectangle; @@ -13,26 +20,52 @@ import net.raphimc.thingl.ThinGL; import net.raphimc.thingl.implementation.window.GLFWWindowInterface; import org.lwjgl.glfw.GLFW; +import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.containers.assets.AssetsContainer; import oxy.bascenario.editor.containers.timeline.TimelineContainer; -public class GlobalContainer extends Container { - private final TimelineContainer timeline; +import java.util.HashMap; +import java.util.Map; +public class GlobalContainer extends Container { public GlobalContainer() { - super(new DockLayout()); + super(new DockLayout(3)); - addChild(timeline = new TimelineContainer(), c -> c.layoutOptions(DockPosition.BOTTOM)); + addChild(new TimelineContainer(), c -> c.layoutOptions(DockPosition.BOTTOM)); + addChild(new AssetsContainer(), c -> c.layoutOptions(DockPosition.LEFT)); + + for (Component component : children()) { + resizableComponents.put((ResizeableContainer) component, new ResizeComponent( + component.layoutOptions() == DockPosition.BOTTOM || component.layoutOptions() == DockPosition.TOP ? + ResizeComponent.ResizeType.Vertical : ResizeComponent.ResizeType.Horizontal, + (DockPosition) component.layoutOptions() + )); + } } - float prevMouseY; - boolean nearTimeline, nearTimelineAndMouseDown; + final Map resizableComponents = new HashMap<>(); + @Override public void render(Renderer renderer, Size size) { super.render(renderer, size); - if (nearTimelineAndMouseDown) { - Rectangle bounds = this.childBounds(timeline); - renderer.fillRect(bounds.x(), bounds.y() - 5, bounds.width(), 2, Color.fromRGB(115, 115, 115)); + for (Map.Entry entry : resizableComponents.entrySet()) { + if (!entry.getValue().nearAndMouseDown) { + continue; + } + final ResizeComponent component = entry.getValue(); + + Rectangle bounds = this.childBounds(entry.getKey()); + + if (component.type == ResizeComponent.ResizeType.Horizontal) { + if (component.position == DockPosition.LEFT) { + renderer.fillRect(bounds.maxX() + 5, bounds.y(), 2, bounds.height(), Color.fromRGB(115, 115, 115)); + } else { + renderer.fillRect(bounds.x() + 5, bounds.y(), 2, bounds.height(), Color.fromRGB(115, 115, 115)); + } + } else { + renderer.fillRect(bounds.x(), bounds.y() - 5, bounds.width(), 2, Color.fromRGB(115, 115, 115)); + } } } @@ -40,28 +73,49 @@ public void render(Renderer renderer, Size size) { protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { super.onComponentMouseMove(event, size); - Rectangle timelineBounds = this.childBounds(timeline); - boolean nearTimeline = Math.abs(event.y() - timelineBounds.y()) < 8; - if (nearTimeline && !this.nearTimeline) { - GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_VRESIZE_CURSOR)); - } else if (!nearTimeline && this.nearTimeline && !nearTimelineAndMouseDown) { - GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR)); - } - this.nearTimeline = nearTimeline; - if (!nearTimelineAndMouseDown) { - this.nearTimelineAndMouseDown = event.isHeld(MouseButton.LEFT) && nearTimeline; - } + for (Map.Entry entry : resizableComponents.entrySet()) { + final ResizeComponent component = entry.getValue(); + Rectangle bounds = this.childBounds(entry.getKey()); - if (nearTimelineAndMouseDown && prevMouseY != Float.MAX_VALUE) { - float newHeight = timelineBounds.height() - (event.y() - prevMouseY); - float ratio = newHeight / size.height(); - timeline.relativeScale(ratio); - timeline.requestLayoutRecalculation(); - } + boolean near; + if (component.type == ResizeComponent.ResizeType.Vertical) { + near = Math.abs(event.y() - bounds.y()) < 4; + } else { + if (component.position == DockPosition.LEFT) { + near = Math.abs(event.x() - bounds.maxX()) < 4; + } else { + near = Math.abs(event.x() - bounds.x()) < 4; + } + } + + if (near && !component.near) { + int mouse = component.type == ResizeComponent.ResizeType.Vertical ? GLFW.GLFW_VRESIZE_CURSOR : GLFW.GLFW_HRESIZE_CURSOR; + GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(mouse)); + } else if (!near && component.near && !component.nearAndMouseDown) { + GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR)); + } + + component.near = near; + if (!component.nearAndMouseDown) { + component.nearAndMouseDown = event.isHeld(MouseButton.LEFT) && near; + } + + if (component.nearAndMouseDown && component.prevMouse != Float.MAX_VALUE) { + if (component.type == ResizeComponent.ResizeType.Vertical) { + float newHeight = bounds.height() - (event.y() - component.prevMouse); + entry.getKey().relativeScale = newHeight / size.height(); + entry.getKey().requestLayoutRecalculation(); + } else { + float newWidth = bounds.width() - (event.x() - component.prevMouse) * (component.position == DockPosition.LEFT ? -1 : 1); + entry.getKey().relativeScale = newWidth / size.width(); + entry.getKey().requestLayoutRecalculation(); + } + } - prevMouseY = event.y(); - if (!nearTimeline || !nearTimelineAndMouseDown) { - prevMouseY = Float.MAX_VALUE; + component.prevMouse = component.type == ResizeComponent.ResizeType.Vertical ? event.y() : event.x(); + if (!component.near || !component.nearAndMouseDown) { + component.prevMouse = Float.MAX_VALUE; + } } return true; @@ -69,18 +123,50 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { @Override protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { - this.nearTimelineAndMouseDown = false; + this.resizableComponents.values().forEach(c -> c.nearAndMouseDown = false); return super.onComponentMouseUp(event, size); } @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Size size) { - this.nearTimelineAndMouseDown = event.button() == MouseButton.LEFT && nearTimeline; - if (!nearTimelineAndMouseDown) { - prevMouseY = Float.MAX_VALUE; - } + this.resizableComponents.values().forEach(c -> { + c.nearAndMouseDown = event.button() == MouseButton.LEFT && c.near; + if (!c.nearAndMouseDown) { + c.prevMouse = Float.MAX_VALUE; + } + }); super.onComponentMouseDown(event, size); return true; } + + @Override + protected boolean onComponentKeyUp(KeyEvent event) { + if (event.key() == Key.SPACE) { + EditorValues.instance().playing(!EditorValues.instance().playing()); + } + + return super.onComponentKeyUp(event); + } + + @RequiredArgsConstructor + private static class ResizeComponent { + private final ResizeType type; + private final DockPosition position; + private boolean near, nearAndMouseDown; + private float prevMouse = Float.MAX_VALUE; + + public enum ResizeType { + Horizontal, Vertical + } + } + + @Accessors(fluent = true) + public static class ResizeableContainer extends Container { + @Setter + protected float relativeScale = 0.2f; + public ResizeableContainer(Layout layout) { + super(layout); + } + } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java new file mode 100644 index 00000000..0fc572fb --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java @@ -0,0 +1,27 @@ +package oxy.bascenario.editor.containers.assets; + +import lombok.experimental.Accessors; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.layout.dock.DockLayout; +import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.containers.GlobalContainer; + +@Accessors(fluent = true) +public class AssetsContainer extends GlobalContainer.ResizeableContainer { + public AssetsContainer() { + super(new DockLayout(0)); + } + + @Override + public void render(Renderer renderer, Size size) { + renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(48, 48, 48)); + super.render(renderer, size); + renderer.outlineRoundedRect(0, 0, size.width(), size.height(), 5, 1, Color.fromRGB(59, 59, 59)); + } + + @Override + public Size computeIdealSize(Size constraints) { + return constraints.withWidth(constraints.width() * relativeScale); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java index 78fe7868..006b43f6 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java @@ -25,7 +25,7 @@ public void render(Renderer renderer, Size size) { ShapedText text = this.rivet().backend().font().shapeText(format(time), Color.GRAY); float currentX = x; - renderer.scale(0.38f, () -> renderer.text(text, currentX / 0.38f, (5 / 35f) * size.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); + renderer.scale(0.3f, () -> renderer.text(text, currentX / 0.3f, (5 / 35f) * size.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); x += EditorValues.instance().oneSecondWidth(); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java index ca278055..d88ab7e0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineContainer.java @@ -1,23 +1,19 @@ package oxy.bascenario.editor.containers.timeline; -import lombok.Setter; import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; -import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.DecoratedContainer; import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.layout.dock.DockLayout; import net.lenni0451.rivet.layout.dock.DockPosition; import net.lenni0451.rivet.math.Padding; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.containers.GlobalContainer; import oxy.bascenario.editor.containers.sequencer.VideoSequencerContainer; @Accessors(fluent = true) -public class TimelineContainer extends Container { - @Setter - private float relativeScale = 0.34f; - +public class TimelineContainer extends GlobalContainer.ResizeableContainer { public TimelineContainer() { super(new DockLayout(0)); @@ -27,6 +23,8 @@ public TimelineContainer() { }); this.addChild(new TimelineTimeControl(), c -> c.layoutOptions(DockPosition.BOTTOM)); this.addChild(new VideoSequencerContainer(), c -> c.layoutOptions(DockPosition.CENTER)); + + relativeScale = 0.34f; } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java index ad67879f..961b3ca3 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java @@ -1,15 +1,54 @@ package oxy.bascenario.editor.containers.timeline; -import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.commons.color.Color; +import net.lenni0451.rivet.component.container.*; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.component.impl.SolidColor; +import net.lenni0451.rivet.layout.grid.GridAnchor; +import net.lenni0451.rivet.layout.grid.GridFill; +import net.lenni0451.rivet.layout.grid.GridLayoutOptions; import net.lenni0451.rivet.layout.list.HorizontalListLayout; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Padding; import net.lenni0451.rivet.math.Size; import oxy.bascenario.utils.components.TimelineDockExpandButton; +import static net.lenni0451.rivet.utils.MathUtils.roundMin; + public class TimelineDockBar extends Container { public TimelineDockBar() { super(new HorizontalListLayout(12, false)); addChild(new TimelineDockExpandButton()); + + { + Container container = new Container(new VerticalListLayout(5, true)); + final ComboBox box = new ComboBox("Select", new DecoratedContainer( + new SolidColor(Color.fromRGB(24, 24, 24)).cornerRadius(5), container)); + + Button button = box.button(); + ((Label)button.child()).scale(0.3f); + button.inactiveOutlineColor().set(Color.TRANSPARENT); + button.activeOutlineColor().set(Color.TRANSPARENT); + button.inactiveColor().set(Color.TRANSPARENT); + + box.arrowSize().set(0f); + box.layoutOptions(new GridLayoutOptions(0, 6).withAnchor(GridAnchor.LEFT).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + addChild(box); + + float textHeight = 11f; + Padding padding = new Padding(roundMin(textHeight / 3F, 0), roundMin(textHeight / 10F, 0), roundMin(textHeight / 3F, 0), roundMin(textHeight / 10F, 0)); + button.innerPadding().set(padding.withTop(3f)); + + container.addChild(new Button("All", c -> {}), b -> { + ((Label)b.child()).scale(0.3f); + b.inactiveOutlineColor().set(Color.TRANSPARENT); + b.activeOutlineColor().set(Color.TRANSPARENT); + b.inactiveColor().set(Color.TRANSPARENT); + }); +// container.addChild(new Button("None", c -> {})); +// container.addChild(new Button("Invert", c -> {})); + } } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java index d7fb1157..9bd4f4b9 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java @@ -1,16 +1,42 @@ package oxy.bascenario.editor.containers.timeline; +import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.anchor.AnchorLayout; +import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.math.Size; +import oxy.bascenario.editor.EditorValues; public class TimelineTimeControl extends Container { + private final Label timeLabel; + public TimelineTimeControl() { super(AnchorLayout.INSTANCE); + + timeLabel = new Label(""); + timeLabel.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.95f).withAnchorMinY(0.5f).pivot(0.5f, 0.5f)); + timeLabel.scale(.45f); + + this.addChild(timeLabel); + } + + @Override + public void render(Renderer renderer, Size size) { + timeLabel.text(format(EditorValues.instance().timestamp())); + super.render(renderer, size); } @Override public Size computeIdealSize(Size constraints) { return constraints.withHeight(25); } + + private String format(long ms) { + long millis = ms % 1000; + long second = (ms / 1000) % 60; + long minute = (ms / (1000 * 60)) % 60; + long hour = (ms / (1000 * 60 * 60)) % 24; + return (hour < 10 ? "0" + hour : hour) + ":" + (minute < 10 ? "0" + minute : minute) + ":" + (second < 10 ? "0" + second : second) + ":" + (millis < 10 ? "0" + millis : millis); + } } From a102ec3bc2f02e1b3108532882d13170a5df61f9 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Thu, 16 Jul 2026 19:15:30 +0700 Subject: [PATCH 68/71] Update Rivet. --- core/build.gradle.kts | 4 +- .../java/oxy/bascenario/utils/RivetUtil.java | 21 ---------- core/src/test/java/DefaultRivetTest.java | 14 +------ .../editor/containers/GlobalContainer.java | 27 +++--------- .../containers/assets/AssetsContainer.java | 4 ++ .../assets/object/ObjectContainer.java | 42 +++++++++++++++++++ .../containers/timeline/TimelineDockBar.java | 4 +- .../timeline/TimelineTimeControl.java | 4 +- .../containers/track/TrackListContainer.java | 4 +- .../track/TrackTabListContainer.java | 6 +-- .../track/component/TrackContainer.java | 4 +- .../track/component/TrackTabContainer.java | 4 +- .../editor/drag/FakeObjectComponent.java | 4 +- .../editor/drag/SecondaryDragComponent.java | 4 +- gradle/libs.versions.toml | 8 ++-- 15 files changed, 77 insertions(+), 77 deletions(-) create mode 100644 editor/src/main/java/oxy/bascenario/editor/containers/assets/object/ObjectContainer.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 369f9fe5..0ac5c464 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -59,5 +59,7 @@ dependencies { api("com.badlogicgames.gdx:gdx-platform:$libGdx:natives-desktop") api(libs.rivet.core) - api(libs.rivet.thingl.backend) + api(libs.rivet.thingl.backend) { + exclude("org.lwjgl") + } } diff --git a/core/src/main/java/oxy/bascenario/utils/RivetUtil.java b/core/src/main/java/oxy/bascenario/utils/RivetUtil.java index fdb377a3..616cc743 100644 --- a/core/src/main/java/oxy/bascenario/utils/RivetUtil.java +++ b/core/src/main/java/oxy/bascenario/utils/RivetUtil.java @@ -6,7 +6,6 @@ import net.lenni0451.rivet.component.container.ComboBox; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.impl.Label; -import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.theme.Theme; import oxy.bascenario.utils.math.Pair; @@ -22,24 +21,4 @@ protected void addValues(final Rivet rivet, final Values values) { } }); } - - public static void dropDownList(Container container, String name, AnchorLayoutOptions layout, List> lists) { - final Container buttons = new Container(new VerticalListLayout(1, true)); - - final ComboBox box = new ComboBox(new Label(name).scale(0.45f), buttons); - box.button().cornerRadius().set(0f); - box.arrowSize().set(0f); - box.layoutOptions(layout); - - for (Pair pair : lists) { - Button button = new Button(new Label(pair.left()).scale(0.45f), c -> { - box.close(); - pair.right().run(); - }); - button.cornerRadius().set(0f); - buttons.addChild(button); - } - - container.addChild(box); - } } diff --git a/core/src/test/java/DefaultRivetTest.java b/core/src/test/java/DefaultRivetTest.java index ecac0c54..a726c2e0 100644 --- a/core/src/test/java/DefaultRivetTest.java +++ b/core/src/test/java/DefaultRivetTest.java @@ -1,26 +1,14 @@ -import lombok.RequiredArgsConstructor; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.Rivet; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.backend.thingl.RivetThinGLApplication; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; -import net.lenni0451.rivet.component.impl.FormattedLabel; -import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.component.impl.SolidColor; -import net.lenni0451.rivet.input.mouse.MouseMoveEvent; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; -import net.lenni0451.rivet.layout.anchor.AnchorLayout; -import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; -import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Size; -import net.lenni0451.rivet.text.model.TextFormat; -import net.lenni0451.rivet.text.model.TextOrigin; import net.raphimc.thingl.resource.font.face.impl.FreeTypeFontFace; import net.raphimc.thingl.resource.font.instance.FontInstance; import net.raphimc.thingl.resource.font.instance.FontInstanceSet; @@ -42,7 +30,7 @@ public static void main(String[] args) { @Override protected void init(final Rivet rivet) { - Container everything = new Container(BorderLayout.INSTANCE); + Container everything = new Container(BorderLayout.DEFAULT); everything.addChild(new SolidColor(Color.RED) { @Override public Size computeIdealSize(Size constraints) { diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java index 1c39ba19..62bdfd87 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java @@ -88,11 +88,9 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { } } - if (near && !component.near) { + if (near && event.isHeld(MouseButton.LEFT)) { int mouse = component.type == ResizeComponent.ResizeType.Vertical ? GLFW.GLFW_VRESIZE_CURSOR : GLFW.GLFW_HRESIZE_CURSOR; GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(mouse)); - } else if (!near && component.near && !component.nearAndMouseDown) { - GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR)); } component.near = near; @@ -100,22 +98,15 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { component.nearAndMouseDown = event.isHeld(MouseButton.LEFT) && near; } - if (component.nearAndMouseDown && component.prevMouse != Float.MAX_VALUE) { + if (component.nearAndMouseDown) { if (component.type == ResizeComponent.ResizeType.Vertical) { - float newHeight = bounds.height() - (event.y() - component.prevMouse); - entry.getKey().relativeScale = newHeight / size.height(); + entry.getKey().relativeScale = ((size.height() - event.y()) - 7) / size.height(); entry.getKey().requestLayoutRecalculation(); } else { - float newWidth = bounds.width() - (event.x() - component.prevMouse) * (component.position == DockPosition.LEFT ? -1 : 1); - entry.getKey().relativeScale = newWidth / size.width(); + entry.getKey().relativeScale = (event.x() - 7) / size.width(); entry.getKey().requestLayoutRecalculation(); } } - - component.prevMouse = component.type == ResizeComponent.ResizeType.Vertical ? event.y() : event.x(); - if (!component.near || !component.nearAndMouseDown) { - component.prevMouse = Float.MAX_VALUE; - } } return true; @@ -124,18 +115,13 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { @Override protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { this.resizableComponents.values().forEach(c -> c.nearAndMouseDown = false); + GLFW.glfwSetCursor(((GLFWWindowInterface) ThinGL.windowInterface()).getWindowHandle(), GLFW.glfwCreateStandardCursor(GLFW.GLFW_ARROW_CURSOR)); return super.onComponentMouseUp(event, size); } @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Size size) { - this.resizableComponents.values().forEach(c -> { - c.nearAndMouseDown = event.button() == MouseButton.LEFT && c.near; - if (!c.nearAndMouseDown) { - c.prevMouse = Float.MAX_VALUE; - } - }); - + this.resizableComponents.values().forEach(c -> c.nearAndMouseDown = event.button() == MouseButton.LEFT && c.near); super.onComponentMouseDown(event, size); return true; } @@ -154,7 +140,6 @@ private static class ResizeComponent { private final ResizeType type; private final DockPosition position; private boolean near, nearAndMouseDown; - private float prevMouse = Float.MAX_VALUE; public enum ResizeType { Horizontal, Vertical diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java index 0fc572fb..d9c41c65 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java @@ -4,13 +4,17 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.layout.dock.DockLayout; +import net.lenni0451.rivet.layout.dock.DockPosition; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.containers.GlobalContainer; +import oxy.bascenario.editor.containers.assets.object.ObjectContainer; @Accessors(fluent = true) public class AssetsContainer extends GlobalContainer.ResizeableContainer { public AssetsContainer() { super(new DockLayout(0)); + +// addChild(new ObjectContainer(), c -> c.layoutOptions(DockPosition.CENTER)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/assets/object/ObjectContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/assets/object/ObjectContainer.java new file mode 100644 index 00000000..494b3e89 --- /dev/null +++ b/editor/src/main/java/oxy/bascenario/editor/containers/assets/object/ObjectContainer.java @@ -0,0 +1,42 @@ +package oxy.bascenario.editor.containers.assets.object; + +import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.layout.list.VerticalListLayout; +import oxy.bascenario.api.render.elements.LocationInfo; +import oxy.bascenario.api.render.elements.Preview; +import oxy.bascenario.api.render.elements.emoticon.Emoticon; +import oxy.bascenario.api.render.elements.emoticon.EmoticonType; +import oxy.bascenario.api.render.elements.text.font.FontType; +import oxy.bascenario.utils.TimeCompiler; +import oxy.bascenario.utils.components.TextWithName; + +public class ObjectContainer extends ScrollContainer { + public ObjectContainer() { + final Container container = new Container(new VerticalListLayout(10, false)); + super(container); + + add(container, "Preview", "assets/base/uis/editor/objects/preview.png", new Preview(FontType.NotoSans, "Title", "Subtitle", null)); + add(container, "Location Info", "assets/base/uis/editor/objects/location-info.png", new LocationInfo(FontType.NotoSans, "Location A", 1000, 200)); + add(container, "Emoticon", "assets/base/uis/editor/objects/emoticon.png", new Emoticon(100, EmoticonType.ANGRY, true)); + } + + private void add(Container container, String name, String path, Object object) { + final TextWithName component = new TextWithName(name, path); + + long duration = TimeCompiler.compileTime(object); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } + + long fDuration = duration; +// component.mouseDownListener().add((ignored, bounds) -> { +// FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration); +// rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); +// return true; +// }); + + container.addChild(component); + } +} diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java index 961b3ca3..ed91b4f1 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java @@ -6,7 +6,7 @@ import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.layout.grid.GridAnchor; import net.lenni0451.rivet.layout.grid.GridFill; -import net.lenni0451.rivet.layout.grid.GridLayoutOptions; +import net.lenni0451.rivet.layout.grid.GridOptions; import net.lenni0451.rivet.layout.list.HorizontalListLayout; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Padding; @@ -33,7 +33,7 @@ public TimelineDockBar() { button.inactiveColor().set(Color.TRANSPARENT); box.arrowSize().set(0f); - box.layoutOptions(new GridLayoutOptions(0, 6).withAnchor(GridAnchor.LEFT).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + box.layoutOptions(new GridOptions(0, 6).withAnchor(GridAnchor.LEFT).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); addChild(box); float textHeight = 11f; diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java index 9bd4f4b9..74be8f11 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineTimeControl.java @@ -4,7 +4,7 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.impl.Label; import net.lenni0451.rivet.layout.anchor.AnchorLayout; -import net.lenni0451.rivet.layout.anchor.AnchorLayoutOptions; +import net.lenni0451.rivet.layout.anchor.AnchorOptions; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.EditorValues; @@ -15,7 +15,7 @@ public TimelineTimeControl() { super(AnchorLayout.INSTANCE); timeLabel = new Label(""); - timeLabel.layoutOptions(AnchorLayoutOptions.EMPTY.withAnchorMinX(0.95f).withAnchorMinY(0.5f).pivot(0.5f, 0.5f)); + timeLabel.layoutOptions(AnchorOptions.EMPTY.withAnchorMinX(0.95f).withAnchorMinY(0.5f).pivot(0.5f, 0.5f)); timeLabel.scale(.45f); this.addChild(timeLabel); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java index 47d918b3..c8c4f61e 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackListContainer.java @@ -14,7 +14,7 @@ import net.lenni0451.rivet.input.mouse.MouseButtonEvent; import net.lenni0451.rivet.input.mouse.MouseMoveEvent; import net.lenni0451.rivet.input.mouse.MouseScrollEvent; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.absolute.AbsoluteOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; @@ -80,7 +80,7 @@ public void recalculateObjectPosition() { } float newX = timestampToPosition(object.object().start, 0, EditorValues.instance().oneMilSecondWidth(), EditorValues.instance().scale()); - child.layoutOptions(new AbsoluteLayoutOptions(newX, 0)); + child.layoutOptions(new AbsoluteOptions(newX, 0)); } } } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackTabListContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackTabListContainer.java index 80f588c3..d544e6e8 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackTabListContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/TrackTabListContainer.java @@ -8,7 +8,7 @@ import net.lenni0451.rivet.component.container.ScrollContainer; import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.absolute.AbsoluteOptions; import net.lenni0451.rivet.layout.border.BorderLayout; import net.lenni0451.rivet.layout.border.BorderPosition; import net.lenni0451.rivet.layout.list.VerticalListLayout; @@ -23,7 +23,7 @@ public class TrackTabListContainer extends Container { private final Container container; public TrackTabListContainer(VideoSequencerContainer sequencerContainer) { - super(BorderLayout.INSTANCE); + super(BorderLayout.DEFAULT); this.addChild(new UpperContainer(sequencerContainer), c -> c.layoutOptions(BorderPosition.TOP)); @@ -57,7 +57,7 @@ public UpperContainer(VideoSequencerContainer sequencerContainer) { }); add.hoverColor().set(Color.fromRGB(145, 218, 255)); - this.addChild(add, c -> c.layoutOptions(new AbsoluteLayoutOptions(5, 5))); + this.addChild(add, c -> c.layoutOptions(new AbsoluteOptions(5, 5))); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackContainer.java index e5d7064b..0c50bda6 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackContainer.java @@ -6,7 +6,7 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.dragdrop.DropEvent; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.absolute.AbsoluteOptions; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.EditorValues; import oxy.bascenario.editor.drag.FakeObjectComponent; @@ -26,7 +26,7 @@ public TrackContainer() { public Size computeIdealSize(Size constraints) { return new Size(1.0e-16f, 1.0e-16f); } - }, c -> c.layoutOptions(new AbsoluteLayoutOptions(3000, 0, 5f, 5f))); + }, c -> c.layoutOptions(new AbsoluteOptions(3000, 0, 5f, 5f))); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackTabContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackTabContainer.java index 153d1af0..07805242 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackTabContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/TrackTabContainer.java @@ -5,7 +5,7 @@ import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.input.mouse.MouseButton; import net.lenni0451.rivet.layout.absolute.AbsoluteLayout; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.absolute.AbsoluteOptions; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.Base; @@ -27,7 +27,7 @@ public TrackTabContainer(VideoSequencerContainer sequencerContainer, TrackContai sequencerContainer.removeTrack(container, this); }); trash.hoverColor().set(Color.fromRGB(145, 218, 255)); - this.addChild(trash, image -> image.layoutOptions(new AbsoluteLayoutOptions(5, 35))); + this.addChild(trash, image -> image.layoutOptions(new AbsoluteOptions(5, 35))); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java index cd9aeeb6..996b55de 100644 --- a/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java @@ -6,7 +6,7 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.absolute.AbsoluteOptions; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.api.render.RenderLayer; @@ -81,6 +81,6 @@ public void handle(TrackContainer trackContainer, float x) { // } // } // -// track.addChild(new ObjectComponent(trackContainer.container().trackListContainer(), object), c -> c.layoutOptions(new AbsoluteLayoutOptions(x, 0))); +// track.addChild(new ObjectComponent(trackContainer.container().trackListContainer(), object), c -> c.layoutOptions(new AbsoluteOptions(x, 0))); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java index 4c118cab..c18ca7d2 100644 --- a/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java @@ -5,7 +5,7 @@ import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; -import net.lenni0451.rivet.layout.absolute.AbsoluteLayoutOptions; +import net.lenni0451.rivet.layout.absolute.AbsoluteOptions; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.EditorValues; @@ -81,7 +81,7 @@ public record Data(ObjectComponent object, float offsetX, float offsetY) { // // object.dragging(false); // -// object.layoutOptions(new AbsoluteLayoutOptions(dataX, 0)); +// object.layoutOptions(new AbsoluteOptions(dataX, 0)); // object.object().start = time; // track.addChild(object); // } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c9139e4..1cf6320d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] lombok = "1.18.46" gson = "2.13.1" -lwjgl = "3.4.0" -lwjgl-sdl = "3.4.0" +lwjgl = "3.4.1" +lwjgl-sdl = "3.4.1" libgdx = "1.13.1" spine = "4.2.5" -thingl = "0.2.0-SNAPSHOT" +thingl = "0.2.0-20260630.205754-5" commons = "1.9.2" mocha = "3.0.1" configurate = "4.2.0" @@ -13,7 +13,7 @@ audiomixer = "2.3.0-SNAPSHOT" jorbis = "0.0.17" jlayer = "1.0.1" zt-zip = "1.17" -rivet = "eff65bbbb5" +rivet = "b40512f366" jsvg = "2.1.0" [libraries] From b26ccd6d03ce2bbef5bce4ecad30af4ced708724 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 17 Jul 2026 19:59:25 +0700 Subject: [PATCH 69/71] fix time section. --- .../java/oxy/bascenario/utils/RivetUtil.java | 44 +++++++++++++++++++ .../oxy/bascenario/utils/font/FontUtils.java | 2 +- .../containers/assets/AssetsContainer.java | 2 +- .../sequencer/SequencerTimeSection.java | 6 ++- .../containers/timeline/TimelineDockBar.java | 34 ++++---------- 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/oxy/bascenario/utils/RivetUtil.java b/core/src/main/java/oxy/bascenario/utils/RivetUtil.java index 616cc743..02e9fc17 100644 --- a/core/src/main/java/oxy/bascenario/utils/RivetUtil.java +++ b/core/src/main/java/oxy/bascenario/utils/RivetUtil.java @@ -5,13 +5,22 @@ import net.lenni0451.rivet.component.container.Button; import net.lenni0451.rivet.component.container.ComboBox; import net.lenni0451.rivet.component.container.Container; +import net.lenni0451.rivet.component.container.DecoratedContainer; import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.component.impl.SolidColor; +import net.lenni0451.rivet.layout.grid.GridAnchor; +import net.lenni0451.rivet.layout.grid.GridFill; +import net.lenni0451.rivet.layout.grid.GridOptions; import net.lenni0451.rivet.layout.list.VerticalListLayout; +import net.lenni0451.rivet.math.Padding; +import net.lenni0451.rivet.text.model.TextOrigin; import net.lenni0451.rivet.theme.Theme; import oxy.bascenario.utils.math.Pair; import java.util.List; +import static net.lenni0451.rivet.utils.MathUtils.roundMin; + public class RivetUtil { public static void setupTheme(Rivet rivet) { rivet.theme(new Theme() { @@ -21,4 +30,39 @@ protected void addValues(final Rivet rivet, final Values values) { } }); } + + public static ComboBox dropdown(String name, float width, List> options) { + Container container = new Container(new VerticalListLayout(5, true)); + final DecoratedContainer decoratedContainer = new DecoratedContainer( + new SolidColor(Color.fromRGB(24, 24, 24)).cornerRadius(5).outlineColor(Color.fromRGB(36, 36, 36)).outlineWidth(1f), container); + final ComboBox box = new ComboBox(name, decoratedContainer); + decoratedContainer.minSize(width, 0f); + + Button button = box.button(); + ((Label)button.child()).scale(0.3f); + button.inactiveOutlineColor().set(Color.TRANSPARENT); + button.activeOutlineColor().set(Color.TRANSPARENT); + button.inactiveColor().set(Color.TRANSPARENT); + + box.arrowSize().set(0f); + box.layoutOptions(new GridOptions(0, 6).withAnchor(GridAnchor.LEFT).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); + + float textHeight = 11f; + Padding padding = new Padding(roundMin(textHeight / 3F, 0), roundMin(textHeight / 10F, 0), roundMin(textHeight / 3F, 0), roundMin(textHeight / 10F, 0)); + button.innerPadding().set(padding.withTop(3f)); + + for (Pair option : options) { + container.addChild(new Button(option.left(), c -> option.right().run()), b -> { + ((Label)b.child()).scale(0.31f); + ((Label)b.child()).horizontalOrigin(TextOrigin.Horizontal.VISUAL_LEFT); + b.inactiveOutlineColor().set(Color.TRANSPARENT); + b.activeOutlineColor().set(Color.TRANSPARENT); + b.inactiveColor().set(Color.TRANSPARENT); + }); + } +// container.addChild(new Button("None", c -> {})); +// container.addChild(new Button("Invert", c -> {})); + + return box; + } } diff --git a/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java b/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java index e1e5d3de..3c32c80b 100644 --- a/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java +++ b/core/src/main/java/oxy/bascenario/utils/font/FontUtils.java @@ -39,7 +39,7 @@ public static FontInstance toFont(Scenario scenario, TextSegment segment) { } public static void loadFonts() { - // Editor UI + // UI (Rivet) loadFont("SFUIRegular", "/assets/base/fonts/rivet/SFUIText-Regular.ttf", 40); // Global diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java index d9c41c65..4940a618 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java @@ -14,7 +14,7 @@ public class AssetsContainer extends GlobalContainer.ResizeableContainer { public AssetsContainer() { super(new DockLayout(0)); -// addChild(new ObjectContainer(), c -> c.layoutOptions(DockPosition.CENTER)); + addChild(new ObjectContainer(), c -> c.layoutOptions(DockPosition.CENTER)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java index 006b43f6..78e7d1da 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/sequencer/SequencerTimeSection.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import net.lenni0451.commons.color.Color; +import net.lenni0451.commons.math.MathUtils; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.backend.text.ShapedText; import net.lenni0451.rivet.component.Component; @@ -20,9 +21,10 @@ public void render(Renderer renderer, Size size) { float x = -EditorValues.instance().scroll(); while (!(x >= size.width())) { - long time = (long) ((x + EditorValues.instance().scroll()) / EditorValues.instance().oneMilSecondWidth()); + float timeX = x + EditorValues.instance().scroll(); + float time = timeX / EditorValues.instance().oneMilSecondWidth(); - ShapedText text = this.rivet().backend().font().shapeText(format(time), Color.GRAY); + ShapedText text = this.rivet().backend().font().shapeText(format(MathUtils.ceilLong(time)), Color.GRAY); float currentX = x; renderer.scale(0.3f, () -> renderer.text(text, currentX / 0.3f, (5 / 35f) * size.height(), TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java index ed91b4f1..6c30ffc1 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/timeline/TimelineDockBar.java @@ -11,7 +11,13 @@ import net.lenni0451.rivet.layout.list.VerticalListLayout; import net.lenni0451.rivet.math.Padding; import net.lenni0451.rivet.math.Size; +import net.lenni0451.rivet.text.model.TextOrigin; +import oxy.bascenario.utils.RivetUtil; import oxy.bascenario.utils.components.TimelineDockExpandButton; +import oxy.bascenario.utils.math.Pair; + +import java.util.ArrayList; +import java.util.List; import static net.lenni0451.rivet.utils.MathUtils.roundMin; @@ -22,32 +28,8 @@ public TimelineDockBar() { addChild(new TimelineDockExpandButton()); { - Container container = new Container(new VerticalListLayout(5, true)); - final ComboBox box = new ComboBox("Select", new DecoratedContainer( - new SolidColor(Color.fromRGB(24, 24, 24)).cornerRadius(5), container)); - - Button button = box.button(); - ((Label)button.child()).scale(0.3f); - button.inactiveOutlineColor().set(Color.TRANSPARENT); - button.activeOutlineColor().set(Color.TRANSPARENT); - button.inactiveColor().set(Color.TRANSPARENT); - - box.arrowSize().set(0f); - box.layoutOptions(new GridOptions(0, 6).withAnchor(GridAnchor.LEFT).withWeightX(1).withFill(GridFill.HORIZONTAL).withColumnSpan(2)); - addChild(box); - - float textHeight = 11f; - Padding padding = new Padding(roundMin(textHeight / 3F, 0), roundMin(textHeight / 10F, 0), roundMin(textHeight / 3F, 0), roundMin(textHeight / 10F, 0)); - button.innerPadding().set(padding.withTop(3f)); - - container.addChild(new Button("All", c -> {}), b -> { - ((Label)b.child()).scale(0.3f); - b.inactiveOutlineColor().set(Color.TRANSPARENT); - b.activeOutlineColor().set(Color.TRANSPARENT); - b.inactiveColor().set(Color.TRANSPARENT); - }); -// container.addChild(new Button("None", c -> {})); -// container.addChild(new Button("Invert", c -> {})); + final List> options = new ArrayList<>(); + addChild(RivetUtil.dropdown("Add", 250f, options)); } } From 517b653f7741d0b55498f8e2e90b67e82fbb94dc Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 17 Jul 2026 20:01:24 +0700 Subject: [PATCH 70/71] fix resizing. --- .../oxy/bascenario/editor/containers/GlobalContainer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java index 62bdfd87..e98d6e0c 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java @@ -79,13 +79,15 @@ protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { boolean near; if (component.type == ResizeComponent.ResizeType.Vertical) { - near = Math.abs(event.y() - bounds.y()) < 4; + near = Math.abs(event.y() - bounds.y()) < 4 && bounds.contains(event.x(), bounds.y()); } else { if (component.position == DockPosition.LEFT) { near = Math.abs(event.x() - bounds.maxX()) < 4; } else { near = Math.abs(event.x() - bounds.x()) < 4; } + + near = near && bounds.contains(bounds.x(), event.y()); } if (near && event.isHeld(MouseButton.LEFT)) { From 7e05ce1dd1dfdf7115d82c2c04f4f615664a6d72 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 17 Jul 2026 22:31:35 +0700 Subject: [PATCH 71/71] Object tab, added back dropping and dragging. --- .../editor/containers/GlobalContainer.java | 6 +- .../containers/assets/AssetsContainer.java | 12 +- .../assets/object/ObjectContainer.java | 49 +++++- .../track/component/ObjectComponent.java | 6 +- .../editor/drag/FakeObjectComponent.java | 93 ++++++----- .../editor/drag/SecondaryDragComponent.java | 155 +++++++++--------- .../utils/components/TextWithName.java | 20 ++- .../utils/components/TextWithNameNoImage.java | 20 ++- .../assets/base/uis/editor/objects/circle.png | Bin 2884 -> 1999 bytes .../base/uis/editor/objects/emoticon.png | Bin 25778 -> 10892 bytes .../base/uis/editor/objects/location-info.png | Bin 6087 -> 2638 bytes .../base/uis/editor/objects/preview.png | Bin 54774 -> 27780 bytes 12 files changed, 216 insertions(+), 145 deletions(-) diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java index e98d6e0c..29ec804b 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/GlobalContainer.java @@ -72,6 +72,9 @@ public void render(Renderer renderer, Size size) { @Override protected boolean onComponentMouseMove(MouseMoveEvent event, Size size) { super.onComponentMouseMove(event, size); + if (rivet().dragAndDropManager().isDragging()) { + return true; + } for (Map.Entry entry : resizableComponents.entrySet()) { final ResizeComponent component = entry.getValue(); @@ -124,8 +127,7 @@ protected boolean onComponentMouseUp(MouseButtonEvent event, Size size) { @Override protected boolean onComponentMouseDown(MouseButtonEvent event, Size size) { this.resizableComponents.values().forEach(c -> c.nearAndMouseDown = event.button() == MouseButton.LEFT && c.near); - super.onComponentMouseDown(event, size); - return true; + return super.onComponentMouseDown(event, size); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java index 4940a618..56b14140 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/assets/AssetsContainer.java @@ -3,8 +3,11 @@ import lombok.experimental.Accessors; import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; +import net.lenni0451.rivet.component.container.DecoratedContainer; +import net.lenni0451.rivet.component.impl.SolidColor; import net.lenni0451.rivet.layout.dock.DockLayout; import net.lenni0451.rivet.layout.dock.DockPosition; +import net.lenni0451.rivet.math.Padding; import net.lenni0451.rivet.math.Size; import oxy.bascenario.editor.containers.GlobalContainer; import oxy.bascenario.editor.containers.assets.object.ObjectContainer; @@ -14,14 +17,17 @@ public class AssetsContainer extends GlobalContainer.ResizeableContainer { public AssetsContainer() { super(new DockLayout(0)); - addChild(new ObjectContainer(), c -> c.layoutOptions(DockPosition.CENTER)); + addChild(new DecoratedContainer(new SolidColor(), new ObjectContainer()), c -> { + c.layoutOptions(DockPosition.CENTER); + c.innerPadding(new Padding(10, 10, 0, 0)); + }); } @Override public void render(Renderer renderer, Size size) { - renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(48, 48, 48)); + renderer.fillRoundedRect(0, 0, size.width(), size.height(), 5, Color.fromRGB(40, 40, 40)); super.render(renderer, size); - renderer.outlineRoundedRect(0, 0, size.width(), size.height(), 5, 1, Color.fromRGB(59, 59, 59)); + renderer.outlineRoundedRect(0, 0, size.width(), size.height(), 5, 1, Color.fromRGB(53, 53, 53)); } @Override diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/assets/object/ObjectContainer.java b/editor/src/main/java/oxy/bascenario/editor/containers/assets/object/ObjectContainer.java index 494b3e89..4b42f177 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/assets/object/ObjectContainer.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/assets/object/ObjectContainer.java @@ -1,25 +1,62 @@ package oxy.bascenario.editor.containers.assets.object; +import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.component.Component; import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.component.container.ScrollContainer; +import net.lenni0451.rivet.component.impl.Label; +import net.lenni0451.rivet.layout.flow.HorizontalFlowLayout; import net.lenni0451.rivet.layout.list.VerticalListLayout; import oxy.bascenario.api.render.elements.LocationInfo; import oxy.bascenario.api.render.elements.Preview; import oxy.bascenario.api.render.elements.emoticon.Emoticon; import oxy.bascenario.api.render.elements.emoticon.EmoticonType; +import oxy.bascenario.api.render.elements.shape.Circle; +import oxy.bascenario.api.render.elements.shape.Rectangle; +import oxy.bascenario.api.render.elements.text.AnimatedText; +import oxy.bascenario.api.render.elements.text.Text; +import oxy.bascenario.api.render.elements.text.TextSegment; import oxy.bascenario.api.render.elements.text.font.FontType; +import oxy.bascenario.editor.drag.FakeObjectComponent; import oxy.bascenario.utils.TimeCompiler; import oxy.bascenario.utils.components.TextWithName; +import oxy.bascenario.utils.components.TextWithNameNoImage; +import oxy.bascenario.utils.components.TypingLabel; + +import java.util.List; public class ObjectContainer extends ScrollContainer { public ObjectContainer() { - final Container container = new Container(new VerticalListLayout(10, false)); + final Container container = new Container(new HorizontalFlowLayout(20, 15)); super(container); add(container, "Preview", "assets/base/uis/editor/objects/preview.png", new Preview(FontType.NotoSans, "Title", "Subtitle", null)); add(container, "Location Info", "assets/base/uis/editor/objects/location-info.png", new LocationInfo(FontType.NotoSans, "Location A", 1000, 200)); add(container, "Emoticon", "assets/base/uis/editor/objects/emoticon.png", new Emoticon(100, EmoticonType.ANGRY, true)); + + add(container, "Text", new Label("Text").scale(0.4f), new Text(List.of(TextSegment.builder().text("Text").build()), 42)); + add(container, "Typing Text", new TypingLabel("Typing Text").scale(0.4f), new AnimatedText(1, List.of(TextSegment.builder().text("Typing Text").build()), 42)); + + add(container, "Circle", "assets/base/uis/editor/objects/circle.png", new Circle(5, Color.WHITE, false)); + add(container, "Rectangle", "assets/base/uis/editor/objects/rectangle.png", new Rectangle(500, 500, Color.WHITE, false)); + } + + private void add(Container container, String name, Component c, Object object) { + final TextWithNameNoImage component = new TextWithNameNoImage(name, c); + + long duration = TimeCompiler.compileTime(object); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } + + long fDuration = duration; + component.mouseDownListener().add((ignored, bounds) -> { + FakeObjectComponent ghost = new FakeObjectComponent(object, fDuration); + rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); + return true; + }); + + container.addChild(component); } private void add(Container container, String name, String path, Object object) { @@ -31,11 +68,11 @@ private void add(Container container, String name, String path, Object object) { } long fDuration = duration; -// component.mouseDownListener().add((ignored, bounds) -> { -// FakeObjectComponent ghost = new FakeObjectComponent(screen.timelineContainer(), object, fDuration); -// rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); -// return true; -// }); + component.mouseDownListener().add((ignored, bounds) -> { + FakeObjectComponent ghost = new FakeObjectComponent(object, fDuration); + rivet().dragAndDropManager().startDrag(ghost, ghost, 0, -60 / 2f); + return true; + }); container.addChild(component); } diff --git a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/ObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/ObjectComponent.java index 77066d0c..0be033b0 100644 --- a/editor/src/main/java/oxy/bascenario/editor/containers/track/component/ObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/containers/track/component/ObjectComponent.java @@ -25,7 +25,7 @@ @Accessors(fluent = true) public class ObjectComponent extends Component { - private static final Color OBJECT_COLOR = Color.fromRGB(145, 218, 255).darker(); + public static final Color OBJECT_COLOR = Color.fromRGB(98, 130, 163).darker(); private final TrackListContainer parent; @Getter @@ -48,8 +48,8 @@ public void render(Renderer renderer, Size bounds) { return; } - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); - renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, parent.selectionManager().isSelected(this) ? Color.WHITE : OBJECT_COLOR); + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, OBJECT_COLOR); + renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, parent.selectionManager().isSelected(this) ? 2 : 1, Color.WHITE); renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object.object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); diff --git a/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java b/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java index 996b55de..89f770e7 100644 --- a/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/drag/FakeObjectComponent.java @@ -6,17 +6,22 @@ import net.lenni0451.commons.color.Color; import net.lenni0451.rivet.backend.render.Renderer; import net.lenni0451.rivet.component.Component; +import net.lenni0451.rivet.component.container.Container; import net.lenni0451.rivet.layout.absolute.AbsoluteOptions; import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.api.render.RenderLayer; import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.containers.sequencer.VideoSequencerContainer; +import oxy.bascenario.editor.containers.track.TrackListContainer; import oxy.bascenario.editor.containers.track.component.ObjectComponent; import oxy.bascenario.editor.containers.track.component.TrackContainer; import oxy.bascenario.editor.object.ObjectOrEvent; import oxy.bascenario.utils.NameUtils; import oxy.bascenario.utils.TimeCompiler; +import static oxy.bascenario.editor.containers.track.component.ObjectComponent.OBJECT_COLOR; + @AllArgsConstructor @Accessors(fluent = true) public class FakeObjectComponent extends Component { @@ -26,8 +31,8 @@ public class FakeObjectComponent extends Component { @Override public void render(Renderer renderer, Size bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(30, 30, 30)); - renderer.outlineRect(0, 0, bounds.width(), bounds.height(), 2, Color.fromRGB(145, 218, 255).darker()); + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, OBJECT_COLOR); + renderer.outlineRoundedRect(0, 0, bounds.width(), bounds.height(), 5, 1, Color.WHITE); renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); } @@ -39,48 +44,46 @@ public Size computeIdealSize(Size constraints) { } public void handle(TrackContainer trackContainer, float x) { -// long time = (long) (x / EditorValues.instance().oneMilSecondWidth()); -// long duration = TimeCompiler.compileTime(object()); -// if (duration == Long.MAX_VALUE) { -// duration = 1000L; -// } -// -// final ObjectOrEvent object = new ObjectOrEvent(time, duration, object(), RenderLayer.ABOVE_DIALOGUE, true); -// -// TrackContainer track = trackContainer; -// while (true) { -// boolean accepted = true; -// for (Component baseComponent : track.children()) { -// if (!(baseComponent instanceof ObjectComponent component)) { -// continue; -// } -// -// long otherMin = component.object().start; -// -// long maxTime = component.object().start + component.object().duration; -// long otherMaxTime = component.object().start + component.object().duration; -// -// if (maxTime >= otherMin && time <= otherMaxTime) { -// accepted = false; -// break; -// } -// } -// -// if (!accepted) { -// int size = trackContainer.container().trackListContainer().container().children().size(); -// if (track.index() >= size - 1) { -// track = new TrackContainer(); -// trackContainer.container().trackListContainer().container().addChild(track); -// trackContainer.container().trackTabListContainer().addChild(new TrackTabContainer(track)); -// break; -// } else { -// track = (TrackContainer) trackContainer.container().trackListContainer().container().children().get(track.index() + 1); -// } -// } else { -// break; -// } -// } -// -// track.addChild(new ObjectComponent(trackContainer.container().trackListContainer(), object), c -> c.layoutOptions(new AbsoluteOptions(x, 0))); + long time = (long) (x / EditorValues.instance().oneMilSecondWidth()); + long duration = TimeCompiler.compileTime(object()); + if (duration == Long.MAX_VALUE) { + duration = 1000L; + } + + final ObjectOrEvent object = new ObjectOrEvent(time, duration, object(), RenderLayer.ABOVE_DIALOGUE, true); + + TrackContainer track = trackContainer; + while (true) { + boolean accepted = true; + for (Component baseComponent : track.children()) { + if (!(baseComponent instanceof ObjectComponent component)) { + continue; + } + + long otherMin = component.object().start; + + long maxTime = component.object().start + component.object().duration; + long otherMaxTime = component.object().start + component.object().duration; + + if (maxTime >= otherMin && time <= otherMaxTime) { + accepted = false; + break; + } + } + + if (!accepted) { + int size = trackContainer.parent().children().size(); + if (track.index() >= size - 1) { + track = ((VideoSequencerContainer)((TrackListContainer)((Container)trackContainer.parent()).parent()).parent()).createTrack(); + break; + } else { + track = (TrackContainer) trackContainer.parent().children().get(track.index() + 1); + } + } else { + break; + } + } + + track.addChild(new ObjectComponent((TrackListContainer) ((Container)trackContainer.parent()).parent(), object), c -> c.layoutOptions(new AbsoluteOptions(x, 0))); } } diff --git a/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java b/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java index c18ca7d2..d977df45 100644 --- a/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java +++ b/editor/src/main/java/oxy/bascenario/editor/drag/SecondaryDragComponent.java @@ -9,12 +9,16 @@ import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.editor.EditorValues; +import oxy.bascenario.editor.containers.sequencer.VideoSequencerContainer; +import oxy.bascenario.editor.containers.track.TrackListContainer; import oxy.bascenario.editor.containers.track.component.ObjectComponent; import oxy.bascenario.editor.containers.track.component.TrackContainer; import oxy.bascenario.utils.NameUtils; import java.util.*; +import static oxy.bascenario.editor.containers.track.component.ObjectComponent.OBJECT_COLOR; + @RequiredArgsConstructor public class SecondaryDragComponent extends Component { private final float mouseX, mouseY; @@ -26,8 +30,8 @@ public void render(Renderer renderer, Size size) { renderer.translate(mouseX + data.offsetX, mouseY + data.offsetY, () -> { float width = EditorValues.instance().oneMilSecondWidth() * data.object.object().duration; - renderer.fillRect(0, 0, width, 60, Color.fromRGB(30, 30, 30)); - renderer.outlineRect(0, 0, width, 60, 2, Color.fromRGB(145, 218, 255).darker()); + renderer.fillRoundedRect(0, 0, width, 60, 5, OBJECT_COLOR); + renderer.outlineRoundedRect(0, 0, width, 60, 5, 1, Color.WHITE); renderer.scale(0.4f, () -> renderer.text(this.rivet().backend().font().shapeText(NameUtils.name(data.object.object().object), Color.WHITE), 20, 20, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.LOGICAL_TOP)); }); @@ -41,84 +45,79 @@ public Size computeIdealSize(Size size) { } public record Data(ObjectComponent object, float offsetX, float offsetY) { -// public void handle(final TrackContainer container, float x) { -// final float dataX = Math.max(0, x + offsetX); -// long time = Math.max(0, (long) (dataX / container.container().screen().oneMilSecondWidth())); -// -// TrackContainer track = container; -// while (true) { -// boolean accepted = true; -// for (Component baseComponent : track.children()) { -// if (!(baseComponent instanceof ObjectComponent component) || component == object) { -// continue; -// } -// -// long otherMin = component.object().start; -// -// long maxTime = object.object().start + object.object().duration; -// long otherMaxTime = component.object().start + component.object().duration; -// -// if (maxTime >= otherMin && time <= otherMaxTime) { -// accepted = false; -// break; -// } -// } -// -// if (!accepted) { -// int size = container.container().trackListContainer().container().children().size(); -// if (track.index() >= size - 1) { -// track = new TrackContainer(); -// container.container().trackListContainer().container().addChild(track); -// container.container().trackTabListContainer().addChild(new TrackTabContainer(track)); -// break; -// } else { -// track = (TrackContainer) container.container().trackListContainer().container().children().get(track.index() + 1); -// } -// } else { -// break; -// } -// } -// -// object.dragging(false); -// -// object.layoutOptions(new AbsoluteOptions(dataX, 0)); -// object.object().start = time; -// track.addChild(object); -// } + public void handle(final TrackContainer container, float x) { + final float dataX = Math.max(0, x + offsetX); + long time = Math.max(0, (long) (dataX / EditorValues.instance().oneMilSecondWidth())); + + TrackContainer track = container; + while (true) { + boolean accepted = true; + for (Component baseComponent : track.children()) { + if (!(baseComponent instanceof ObjectComponent component) || component == object) { + continue; + } + + long otherMin = component.object().start; + + long maxTime = object.object().start + object.object().duration; + long otherMaxTime = component.object().start + component.object().duration; + + if (maxTime >= otherMin && time <= otherMaxTime) { + accepted = false; + break; + } + } + + if (!accepted) { + int size = container.parent().children().size(); + if (track.index() >= size - 1) { + track = ((VideoSequencerContainer)((TrackListContainer)((Container)container.parent()).parent()).parent()).createTrack(); + } else { + track = (TrackContainer) container.parent().children().get(track.index() + 1); + } + } else { + break; + } + } + + object.dragging(false); + + object.layoutOptions(new AbsoluteOptions(dataX, 0)); + object.object().start = time; + track.addChild(object); + } } public void handle(final TrackContainer container, float x) { -// if (list.isEmpty()) { -// return; -// } -// -// list.sort(Comparator.comparingInt(data -> ((TrackContainer)data.object.parent()).index())); -// -// int firstIndex = ((TrackContainer)list.getFirst().object.parent()).index(); -// -// final Map tempMap = new HashMap<>(); -// for (Data data : list) { // Remove first. -// tempMap.put(data, ((TrackContainer)data.object.parent()).index()); -// ((Container)data.object.parent()).removeChild(data.object); -// } -// -// for (Data data : list) { -// TrackContainer trackContainer = container; -// int offset = tempMap.get(data) - firstIndex; -// if (offset > 0) { -// int newIndex = trackContainer.index() + offset; -// -// int size = container.container().trackListContainer().container().children().size(); -// if (newIndex >= size) { -// trackContainer = new TrackContainer(); -// container.container().trackListContainer().container().addChild(trackContainer); -// container.container().trackTabListContainer().addChild(new TrackTabContainer(trackContainer)); -// } else { -// trackContainer = (TrackContainer) container.container().trackListContainer().container().children().get(newIndex); -// } -// } -// -// data.handle(trackContainer, x); -// } + if (list.isEmpty()) { + return; + } + + list.sort(Comparator.comparingInt(data -> ((TrackContainer)data.object.parent()).index())); + + int firstIndex = ((TrackContainer)list.getFirst().object.parent()).index(); + + final Map tempMap = new HashMap<>(); + for (Data data : list) { // Remove first. + tempMap.put(data, ((TrackContainer)data.object.parent()).index()); + ((Container)data.object.parent()).removeChild(data.object); + } + + for (Data data : list) { + TrackContainer trackContainer = container; + int offset = tempMap.get(data) - firstIndex; + if (offset > 0) { + int newIndex = trackContainer.index() + offset; + + int size = trackContainer.parent().children().size(); + if (newIndex >= size) { + trackContainer = ((VideoSequencerContainer)((TrackListContainer)((Container)container.parent()).parent()).parent()).createTrack(); + } else { + trackContainer = (TrackContainer) container.parent().children().get(trackContainer.index() + 1); + } + } + + data.handle(trackContainer, x); + } } } diff --git a/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java b/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java index 4b440736..a414965e 100644 --- a/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/TextWithName.java @@ -9,10 +9,12 @@ import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.Base; +import oxy.bascenario.utils.animation.math.ColorAnimations; public class TextWithName extends Component { private final String name; private final Texture texture; + private final ColorAnimations backgroundColor = new ColorAnimations(Color.fromRGBA(63, 63, 63, 0)); public TextWithName(String name, String path) { this.name = name; @@ -21,18 +23,28 @@ public TextWithName(String name, String path) { @Override public void render(Renderer renderer, Size bounds) { - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(25, 25, 25)); - renderer.image(this.texture, 10, 10, 255, 141, Color.WHITE); + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, backgroundColor.color()); + renderer.image(this.texture, 5, 5, 96, 96, Color.WHITE); ShapedText text = this.rivet().backend().font().shapeText(name, Color.WHITE); renderer.scale(0.4f, () -> renderer.text(text, - (277 / 2f - text.visualBounds().width() * .4f / 2f) / .4f, 160 / .4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP + (102 / 2f - text.visualBounds().width() * .4f / 2f) / .4f, 108 / .4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP )); } + @Override + protected void onComponentMouseEnter() { + backgroundColor.set(Color.fromRGB(63, 63, 63), 200); + } + + @Override + protected void onComponentMouseLeave() { + backgroundColor.set(Color.fromRGBA(63, 63, 63, 0), 200); + } + @Override public Size computeIdealSize(Size constraints) { - return new Size(255 + 20, 141 + 40); + return new Size(102,128); } } diff --git a/editor/src/main/java/oxy/bascenario/utils/components/TextWithNameNoImage.java b/editor/src/main/java/oxy/bascenario/utils/components/TextWithNameNoImage.java index 79e62d69..0b93c5ba 100644 --- a/editor/src/main/java/oxy/bascenario/utils/components/TextWithNameNoImage.java +++ b/editor/src/main/java/oxy/bascenario/utils/components/TextWithNameNoImage.java @@ -11,12 +11,14 @@ import net.lenni0451.rivet.math.Size; import net.lenni0451.rivet.text.model.TextOrigin; import oxy.bascenario.Base; +import oxy.bascenario.utils.animation.math.ColorAnimations; import java.util.List; public class TextWithNameNoImage extends Component { private final String name; private final Component component; + private final ColorAnimations backgroundColor = new ColorAnimations(Color.fromRGBA(63, 63, 63, 0)); public TextWithNameNoImage(String name, Component component) { this.name = name; @@ -53,18 +55,28 @@ public Rectangle childBounds(Component component) { }); } - renderer.fillRect(0, 0, bounds.width(), bounds.height(), Color.fromRGB(25, 25, 25)); + renderer.fillRoundedRect(0, 0, bounds.width(), bounds.height(), 5, backgroundColor.color()); component.render(renderer, bounds); ShapedText text = this.rivet().backend().font().shapeText(name, Color.WHITE); renderer.scale(0.4f, () -> renderer.text(text, - (277 / 2f - text.visualBounds().width() * .4f / 2f) / .4f, 160 / .4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP - )); + (102 / 2f - text.visualBounds().width() * .4f / 2f) / .4f, 108 / .4f, TextOrigin.Horizontal.VISUAL_LEFT, TextOrigin.Vertical.VISUAL_TOP + )); + } + + @Override + protected void onComponentMouseEnter() { + backgroundColor.set(Color.fromRGB(63, 63, 63), 200); + } + + @Override + protected void onComponentMouseLeave() { + backgroundColor.set(Color.fromRGBA(63, 63, 63, 0), 200); } @Override public Size computeIdealSize(Size constraints) { - return new Size(255 + 20, 141 + 40); + return new Size(102,128); } } diff --git a/editor/src/main/resources/assets/base/uis/editor/objects/circle.png b/editor/src/main/resources/assets/base/uis/editor/objects/circle.png index dbff9d9480fda17745f3d192b8372b7aacf24078..2d5bb79000200d381498eae845851a0750a8b76a 100644 GIT binary patch delta 1773 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 H9L@a~GD1)x diff --git a/editor/src/main/resources/assets/base/uis/editor/objects/emoticon.png b/editor/src/main/resources/assets/base/uis/editor/objects/emoticon.png index b9b6f5bf630d455f7a86b341ed0c121ff8d521c8..9e4a7f872e31e9465f86cae6d69f90b97a3a747a 100644 GIT binary patch delta 10701 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 diff --git a/editor/src/main/resources/assets/base/uis/editor/objects/location-info.png b/editor/src/main/resources/assets/base/uis/editor/objects/location-info.png index 596bff317f9128be0f511b0ae4f6115304a8241a..195c6447e606869f1c4830200bbba0b1498e3e0b 100644 GIT binary patch delta 2422 zcmb7``9IT-1IH&L=iD(Nq>Re3k7JJ98%0Zms1P}l!rILJM#AK7;)ARlA0kJ6TzxXf z2qbvL%EIm+ zyZ;*=?th-1gGXd2!1yR25QM@Z0lONVVULGw2Xlaaa)LnramLsk;3=RkgZVl#0|N0J zT(K~{8REK@!yiceCed~LY5ZF7PMrU=5IEE1gr1Fp72u9y`*(AF ztbYIIji{N({BYMjARBF^Qq(%nwgKhnIe_qE}@(H1`0=9h)q zMCP!}T`FwG;HNVzcR%a-Itzfd%mv@dIrTe~HxMzI2uHo%W|7>fqjtC6L6YKI_tRsf z9ATD(5#$bYH+5Jok6#FAIB_~^4?H{~>aGBNlHfJ_ae-e1`oMOF@ioMtvLm%~--@T; zMUjz`WXoLm`WeN|g3cl8v8;3&N{ktg60?mYal5P21-Ca)x18lf0sqx8k7U(8bI*#q zp;E>^o&vI&v4xb2b^-yN6#`3`L)$w!DfjfCFR;|e8?wR)j!^plt{F=eS(f` z)%939 zHJ#crVuYn{k@qCFyK+w<3IHSp@Kg569@Q-MUY`g;*RYh>wd4F=XvX z2GT!2=*uyZFy}s}pr;o@ob5`>b|@@rPab}c4743pL6j_E8^h`#*+)1o{>8yKou8p$ zc8>igvuju>4KQVqpxIe>$%pdH)yv6ElQJ^;Sb>1`v8R)?aVbqhPN9KZ zZlK~@JnposazavD^5OZ4#8pY@{vgue1k`_un^Sm|#aL789YGhTn>9wRZk+M(WI(la z8x*v)31-F;f+^R^!WfY<#-SNH$FRTtZu;Vo zPD;*>W7b1fsX=_&rGpd`mHW2dqDYbxpt3(TrS1Drcf`{_jSH!elX-M}cNI3;d zB4ex3N+3A|p&a*bBx^EHHwL{h>xNbOk_O4qNa*@f5$l#~^N zGXb5|;L-!A;=%7W?3#oo(o!}@EPCf-4ZW~XamyAd*O@wC8h#R*eUNskrwibG@gXDU zzMb3@l^S1LE8h3Jy@@N4u;s-CQ&VgGZtOJIJv6txxq#ajp2ue9WyzF9cf;891BUnK zJi4YA)e1fq*r<{N>_*K-$BD8+`u%?AEQ~8~hb&;8@JE}+lH#+2 z8r-1~Hq)p1!(*=XBNUXmW*lpsoo5AwVDT^PGdT9A(Z&P6k_!Yp><9C^RW#oC&7nuX z>3ogZc=L3W@Y|VJmiK5@Y(JI<+*?e7_i^J-le*!8F&;HIoa7`E6XA|`e%EW8*RWb_ zuFnC4EM@O^49WqP)0=JOnag6Qo~NF}iPg51{vm(f$pfNd`>cjXHWbgd8V&ziXWK|AW<@r6@-C z9J2BeH?SxqY#7|co>PX%>*=fXKI*p7ean|ByF}>-t7|zebLd!m*i|Ug7u=(5h57B=>j@0~lF|Jk$km6Xhl`s{yx+e$858`vcyX(#v^65%?*tsv zJEuvT7iJft!s3l*LHp|fVm)gDQA7t{D57_q9MPTLV`E9XqphaIxz}r(s=dxY3TI|Oh}UdEaJ z6>np4CW5l|RP^Qds&BpqwGrsdD82q|Myo+J_ox3685jRcdPI;xN1RXKl4n)m64L*q O6zB>PZBb?B9{Vq%TAF_V literal 6087 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

W1V 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 Lw7pJ2`lS93g6{04