diff --git a/buildSrc/src/main/kotlin/org/polyfrost/gradle/addDependencies.kt b/buildSrc/src/main/kotlin/org/polyfrost/gradle/addDependencies.kt index f17f736e5..fd61e4ecd 100644 --- a/buildSrc/src/main/kotlin/org/polyfrost/gradle/addDependencies.kt +++ b/buildSrc/src/main/kotlin/org/polyfrost/gradle/addDependencies.kt @@ -42,6 +42,7 @@ private val fabricApiModuleVersions = mapOf( "api-base-12108" to "0.4.64+9ec45cd8f3", // 1.21.8 "api-base-12109" to "0.5.2+2fa446b294", // 1.21.9 "api-base-12110" to "1.0.0+14b92d8968", // 1.21.10 + "api-base-12111" to "1.0.5+4ebb5c0853", // 1.21.11 "lifecycle-events-v1-common" to "1.1.0+2c3f108c81e6", // Common (Legacy Fabric API) "lifecycle-events-v1-10809" to "1.1.0+1.8.9+2c3f108c81e6", // 1.8.9 "lifecycle-events-v1-11202" to "1.1.0+1.12.2+2c3f108c81e6", // 1.12.2 @@ -63,6 +64,7 @@ private val fabricApiModuleVersions = mapOf( "lifecycle-events-v1-12108" to "2.6.3+db4dfd85f3", // 1.21.8 "lifecycle-events-v1-12109" to "2.6.8+33df5e6e94", // 1.21.9 "lifecycle-events-v1-12110" to "2.6.9+33df5e6e68", // 1.21.10 + "lifecycle-events-v1-12111" to "2.6.15+4ebb5c0853", // 1.21.11 "keybindings-api-v1-common" to "1.1.1+281301ea81e6", // Common (Legacy Fabric API) "keybindings-api-v1-10809" to "1.1.1+1.8.9+2c3f108c81e6", // 1.8.9 "keybindings-api-v1-11202" to "1.1.1+1.12.2+2c3f108c81e6", // 1.12.2 @@ -84,6 +86,7 @@ private val fabricApiModuleVersions = mapOf( "key-binding-api-v1-12108" to "1.0.65+946bf4c3f3", // 1.21.8 "key-binding-api-v1-12109" to "1.1.0+4ec8267494", // 1.21.9 "key-binding-api-v1-12110" to "1.1.1+b3ba9c4868", // 1.21.10 + "key-binding-api-v1-12111" to "1.1.7+4fc5413f53", // 1.21.11 "rendering-api-v1-common" to "1.0.0+7c545fdb81e6", // Common (Legacy Fabric API) "rendering-api-v1-10809" to "1.0.0+1.8.9+2c3f108c81e6", // 1.8.9 "rendering-api-v1-11202" to "1.0.0+1.8.9+2c3f108c81e6", // 1.12.2 @@ -105,6 +108,7 @@ private val fabricApiModuleVersions = mapOf( "rendering-v1-12108" to "12.4.0+e8d43c76f3", // 1.21.8 "rendering-v1-12109" to "15.0.2+9bce411b94", // 1.21.9 "rendering-v1-12110" to "16.0.1+328a75ba68", // 1.21.10 + "rendering-v1-12111" to "16.2.5+1fb1cde953", // 1.21.11 "screen-api-v1-11605" to "1.0.1+3cc0f0907d", // 1.16.5 "screen-api-v1-11701" to "1.0.5+cf39a74318", // 1.17.1 "screen-api-v1-11802" to "1.0.11+d882b91560", // 1.18.2 @@ -123,6 +127,7 @@ private val fabricApiModuleVersions = mapOf( "screen-api-v1-12108" to "2.1.0+277ecf7df3", // 1.21.8 "screen-api-v1-12109" to "3.0.2+86b3ae5794", // 1.21.9 "screen-api-v1-12110" to "3.1.0+9c82a79868", // 1.21.10 + "screen-api-v1-12111" to "3.1.7+4ebb5c0853", // 1.21.11 "command-api-v1-11605" to "1.1.3+3cc0f0907d", // 1.16.5 "command-api-v1-11701" to "1.1.4+cf39a74318", // 1.17.1 "command-api-v1-11802" to "1.1.10+d7c144a860", // 1.18.2 @@ -141,6 +146,7 @@ private val fabricApiModuleVersions = mapOf( "command-api-v2-12108" to "2.2.53+946bf4c3f3", // 1.21.8 "command-api-v2-12109" to "2.3.5+33df5e6e94", // 1.21.9 "command-api-v2-12110" to "2.3.7+328a75ba68", // 1.21.10 + "command-api-v2-12111" to "2.4.6+4ebb5c0853", // 1.21.11 "transitive-access-wideners-v1-11802" to "1.1.0+e747827960", // 1.18.2 "transitive-access-wideners-v1-11902" to "1.3.3+08b73de490", // 1.19.2 "transitive-access-wideners-v1-11904" to "3.3.0+1b5f819af4", // 1.19.4 @@ -157,6 +163,7 @@ private val fabricApiModuleVersions = mapOf( "transitive-access-wideners-v1-12108" to "6.4.1+ac3e15d1f3", // 1.21.8 "transitive-access-wideners-v1-12109" to "6.5.2+5d9dbead94", // 1.21.9 "transitive-access-wideners-v1-12110" to "6.5.3+41eb434368", // 1.21.10 + "transitive-access-wideners-v1-12111" to "7.0.7+1fb1cde953", // 1.21.11 ) /** diff --git a/gradle.properties b/gradle.properties index 0b58527de..5b1af1b5c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,9 +10,9 @@ ksp.incremental=false dgt.java.version=8 dgt.loom.mappings=official-like -dgt.minecraft.revision=3 +dgt.minecraft.revision=4 # Project Configuration project.group=org.polyfrost.oneconfig project.name=OneConfig -project.version=1.0.0-alpha.180 +project.version=1.0.0-alpha.181 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56b81b393..9eedb80e5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ kotlinx-coroutines = "1.10.2" kotlinx-atomicfu = "0.29.0" fabric-language-kotlin = "1.13.5+kotlin.2.2.10" google-ksp = "2.2.10-2.0.2" -polyui = "1.15.2" +polyui = "2.0.7" annotations = "24.1.0" hypixel-modapi = "1.0.1" hypixel-data = "0.2.0" # Dep of hypixel-modapi @@ -21,8 +21,8 @@ copycat = "0.1.3" copycat-image-awt = "0.1.1" # Per version -textile = "1.0.0-beta.8" -omnicore = "1.0.0-beta.38" +textile = "1.0.0-beta.11" +omnicore = "1.0.0-beta.40" lwjgl = "3.3.3" # All downloaded by the isolated-lwjgl3-loader # Legacy only @@ -35,7 +35,7 @@ brigadier = "1.0.18" oneconfig-loader = "1.1.0-alpha.48" # Gradle -dgt = "2.62.0" +dgt = "2.69.0" kotlinx-abi = "0.18.1" licenser = "2.1.1" jetbrains-idea-ext = "1.3" diff --git a/minecraft/build.gradle.kts b/minecraft/build.gradle.kts index b70045426..3bc5a32b2 100644 --- a/minecraft/build.gradle.kts +++ b/minecraft/build.gradle.kts @@ -39,7 +39,9 @@ if (mcData.isForge) { toolkitLoomHelper { disableRunConfigs(GameSide.SERVER) - useDevAuth("+") + if (mcData.version < MinecraftVersions.VERSION_1_21_11) { + useDevAuth("+") + } useProperty("mixin.debug.export", "true", GameSide.CLIENT) useProperty("debugBytecode", "true", GameSide.CLIENT) @@ -207,7 +209,8 @@ dependencies { rconfig("1.21.7", "3.7.2"), rconfig("1.21.8", "3.7.2", "1.21.7"), rconfig("1.21.9", "3.9.1"), - rconfig("1.21.10", "3.9.1", "1.21.9") + rconfig("1.21.10", "3.9.1", "1.21.9"), + rconfig("1.21.11", "3.9.1", "1.21.9"), ) compileOnlyCompat(rconfig[mcVersionString]) @@ -249,7 +252,8 @@ dependencies { yacl("1.21.7", "3.7.1", "1.21.6"), yacl("1.21.8", "3.7.1", "1.21.6"), yacl("1.21.9", "3.8.0", "1.21.6"), - yacl("1.21.10", "3.8.0", "1.21.9") + yacl("1.21.10", "3.8.0", "1.21.9"), + yacl("1.21.11", "3.8.0", "1.21.9"), ) compileOnlyCompat(yacl[mcVersionString]) @@ -274,6 +278,7 @@ dependencies { modMenu("1.21.7", "15.0.0-beta.3"), modMenu("1.21.8", "15.0.0-beta.3"), modMenu("1.21.10", "16.0.0-rc.1"), + modMenu("1.21.11", "17.0.0-alpha.1"), ) compileOnlyCompat(modMenu[mcVersionString]) diff --git a/minecraft/root.gradle.kts b/minecraft/root.gradle.kts index 77528ef84..4e213bc39 100644 --- a/minecraft/root.gradle.kts +++ b/minecraft/root.gradle.kts @@ -14,27 +14,31 @@ preprocess { strictExtraMappings.set(true) // FOR ALL NEW VERSIONS ENSURE TO UPDATE settings.gradle.kts ! - "1.21.10-neoforge"(1_21_10, "srg") { - "1.21.10-fabric"(1_21_10, "srg") { - "1.21.8-fabric"(1_21_08, "srg") { - "1.21.8-neoforge"(1_21_08, "srg") { - "1.21.5-neoforge"(1_21_05, "srg") { - "1.21.5-fabric"(1_21_05, "srg") { - "1.21.4-fabric"(1_21_04, "srg") { - "1.21.4-neoforge"(1_21_04, "srg") { - "1.21.1-neoforge"(1_21_01, "srg", file("mappings/1.21.4-forge+1.21.1-forge.txt")) { - "1.21.1-fabric"(1_21_01, "srg") { - "1.20.4-fabric"(1_20_04, "srg") { - "1.20.4-neoforge"(1_20_04, "srg") { - "1.20.4-forge"(1_20_04, "srg") { - "1.20.1-forge"(1_20_01, "srg", file("mappings/1.20.4-forge+1.20.1-forge.txt")) { - "1.20.1-fabric"(1_20_01, "srg") { - "1.16.5-fabric"(1_16_05, "srg", file("mappings/1.20.1-fabric+1.16.5-fabric.txt")) { - "1.16.5-forge"(1_16_05, "srg") { - "1.12.2-forge"(1_12_02, "srg", file("mappings/1.16.5-forge+1.12.2-forge.txt")) { - "1.12.2-fabric"(1_12_02, "srg") { - "1.8.9-fabric"(1_08_09, "srg") { - "1.8.9-forge"(1_08_09, "srg") + "1.21.11-fabric"(1_21_11, "srg") { + "1.21.11-neoforge"(1_21_11, "srg") { + "1.21.10-neoforge"(1_21_10, "srg") { + "1.21.10-fabric"(1_21_10, "srg") { + "1.21.8-fabric"(1_21_08, "srg") { + "1.21.8-neoforge"(1_21_08, "srg") { + "1.21.5-neoforge"(1_21_05, "srg") { + "1.21.5-fabric"(1_21_05, "srg") { + "1.21.4-fabric"(1_21_04, "srg") { + "1.21.4-neoforge"(1_21_04, "srg") { + "1.21.1-neoforge"(1_21_01, "srg", file("mappings/1.21.4-forge+1.21.1-forge.txt")) { + "1.21.1-fabric"(1_21_01, "srg") { + "1.20.4-fabric"(1_20_04, "srg") { + "1.20.4-neoforge"(1_20_04, "srg") { + "1.20.4-forge"(1_20_04, "srg") { + "1.20.1-forge"(1_20_01, "srg", file("mappings/1.20.4-forge+1.20.1-forge.txt")) { + "1.20.1-fabric"(1_20_01, "srg") { + "1.16.5-fabric"(1_16_05, "srg", file("mappings/1.20.1-fabric+1.16.5-fabric.txt")) { + "1.16.5-forge"(1_16_05, "srg") { + "1.12.2-forge"(1_12_02, "srg", file("mappings/1.16.5-forge+1.12.2-forge.txt")) { + "1.12.2-fabric"(1_12_02, "srg") { + "1.8.9-fabric"(1_08_09, "srg") { + "1.8.9-forge"(1_08_09, "srg") + } + } } } } diff --git a/minecraft/src/main/java/org/polyfrost/oneconfig/api/platform/v1/internal/GLPlatformImpl.java b/minecraft/src/main/java/org/polyfrost/oneconfig/api/platform/v1/internal/GLPlatformImpl.java index e2a185d3c..619ae55ac 100644 --- a/minecraft/src/main/java/org/polyfrost/oneconfig/api/platform/v1/internal/GLPlatformImpl.java +++ b/minecraft/src/main/java/org/polyfrost/oneconfig/api/platform/v1/internal/GLPlatformImpl.java @@ -27,9 +27,17 @@ package org.polyfrost.oneconfig.api.platform.v1.internal; import dev.deftu.omnicore.api.client.render.state.OmniRenderStates; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.BufferUtils; import org.polyfrost.oneconfig.api.platform.v1.GLPlatform; import org.polyfrost.oneconfig.utils.v1.MHUtils; +import java.nio.Buffer; +import java.nio.IntBuffer; + +import static org.lwjgl.opengl.GL11.*; + public class GLPlatformImpl implements GLPlatform { //@formatter:off //#if MC <= 1.12.2 @@ -38,6 +46,14 @@ public class GLPlatformImpl implements GLPlatform { .logIfErr().getOrElse(v -> 0L); //#endif + private final IntBuffer VIEWPORT = BufferUtils.createIntBuffer( + //#if MC >= 1.13 + //$$ 4 + //#else + 16 + //#endif + ); + @Override public long getFunctionAddress(String addr) { //#if MC <= 1.12.2 @@ -53,7 +69,7 @@ public long getFunctionAddress(String addr) { * to match what NanoVG leaves dropped into the OpenGL context. */ @Override - public void updateGameRenderStateAlongsideNanoVG() { + public void syncOpenGLContext() { OmniRenderStates.syncBlend(); OmniRenderStates.syncDepth(); OmniRenderStates.syncCull(); @@ -63,4 +79,17 @@ public void updateGameRenderStateAlongsideNanoVG() { //$$ com.mojang.blaze3d.vertex.BufferUploader.reset(); //#endif } + + @Override + public int @NotNull [] glViewport(int @Nullable [] in) { + ((Buffer) VIEWPORT).clear(); + //#if MC >= 1.13 + //$$ glGetIntegerv(GL_VIEWPORT, VIEWPORT); + //#else + glGetInteger(GL_VIEWPORT, VIEWPORT); + //#endif + int[] out = in != null ? in : new int[4]; + VIEWPORT.get(out, 0, 4); + return out; + } } diff --git a/minecraft/src/main/java/org/polyfrost/oneconfig/api/platform/v1/internal/ScreenPlatformImpl.java b/minecraft/src/main/java/org/polyfrost/oneconfig/api/platform/v1/internal/ScreenPlatformImpl.java index 6e6541c62..59f5dbd9f 100644 --- a/minecraft/src/main/java/org/polyfrost/oneconfig/api/platform/v1/internal/ScreenPlatformImpl.java +++ b/minecraft/src/main/java/org/polyfrost/oneconfig/api/platform/v1/internal/ScreenPlatformImpl.java @@ -26,19 +26,11 @@ package org.polyfrost.oneconfig.api.platform.v1.internal; -import dev.deftu.omnicore.api.client.render.OmniRenderingContext; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import org.jetbrains.annotations.Nullable; import org.polyfrost.oneconfig.api.event.v1.EventDelay; -import org.polyfrost.oneconfig.api.hud.v1.LegacyHud; import org.polyfrost.oneconfig.api.platform.v1.ScreenPlatform; -import org.polyfrost.oneconfig.api.ui.v1.UIManager; -import org.polyfrost.polyui.PolyUI; -import org.polyfrost.polyui.component.Component; -import org.polyfrost.polyui.component.Drawable; - -import java.util.List; public class ScreenPlatformImpl implements ScreenPlatform { // //#if MC > 1.13 @@ -81,18 +73,6 @@ public int windowHeight() { //#endif } - public void renderLegacyHuds(OmniRenderingContext ctx) { - PolyUI defaultInstance = UIManager.INSTANCE.getDefaultInstance(); - Drawable master = defaultInstance.getMaster(); - List children = master.getChildren(); - if (children == null || children.isEmpty()) return; - - for (Component child : children) { - if (!(child instanceof LegacyHud.LegacyHudComponent)) continue; - ((LegacyHud.LegacyHudComponent) child).renderLegacy(ctx); - } - } - // todo: https://github.com/Polyfrost/OneConfig/issues/478 // this override was removed to avoid the system receiving incorrect/inaccurate pixel ratio values // while the window was being resized (as it was calculated based on viewport / window size) diff --git a/minecraft/src/main/java/org/polyfrost/oneconfig/api/ui/v1/internal/UIManagerImpl.java b/minecraft/src/main/java/org/polyfrost/oneconfig/api/ui/v1/internal/UIManagerImpl.java index 439d14db7..fa45c0621 100644 --- a/minecraft/src/main/java/org/polyfrost/oneconfig/api/ui/v1/internal/UIManagerImpl.java +++ b/minecraft/src/main/java/org/polyfrost/oneconfig/api/ui/v1/internal/UIManagerImpl.java @@ -41,21 +41,21 @@ import dev.deftu.omnicore.api.client.render.GlCapabilities; import dev.deftu.omnicore.api.client.render.pipeline.OmniRenderPipeline; import dev.deftu.omnicore.api.client.render.pipeline.OmniRenderPipelines; +import dev.deftu.omnicore.api.client.render.OmniRenderingContext; import dev.deftu.omnicore.api.client.render.state.OmniBlendState; import dev.deftu.omnicore.api.client.screen.OmniScreen; import net.minecraft.client.Minecraft; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.polyfrost.oneconfig.api.ClassHasOverwrites; -import org.polyfrost.oneconfig.api.ui.v1.api.NanoSvgApi; -import org.polyfrost.oneconfig.api.ui.v1.api.NanoVgApi; -import org.polyfrost.oneconfig.api.ui.v1.api.StbApi; -import org.polyfrost.oneconfig.api.ui.v1.api.TinyFdApi; +import org.polyfrost.oneconfig.api.ui.v1.api.*; import org.polyfrost.oneconfig.api.ui.v1.UIManager; import org.polyfrost.oneconfig.api.ui.v1.internal.wrappers.MCWindow; import org.polyfrost.oneconfig.api.ui.v1.internal.wrappers.PolyUIScreen; import org.polyfrost.polyui.PolyUI; +import org.polyfrost.polyui.data.Font; import org.polyfrost.polyui.renderer.Renderer; import org.polyfrost.polyui.renderer.Window; @@ -69,15 +69,18 @@ public class UIManagerImpl implements UIManager { private static final String LWJGL_API_PACKAGE = "org.polyfrost.oneconfig.api.ui.v1.api."; private static final String LWJGL_IMPL_PACKAGE = "org.polyfrost.oneconfig.api.ui.v1.internal."; + private static final Font MC_FONT = Font.of("minecraft/assets/fonts/nowhere", null, false, Font.Weight.Regular); + private static final Logger LOGGER = LogManager.getLogger("OneConfig/LWJGL"); private PolyUI ui; private OmniRenderPipeline pipeline; + private OmniRenderingContext ctx; private final Set classLoaderInclude = new HashSet<>(); private final Map> classCache = new HashMap<>(); - private final NanoVgApi nanoVg; +// private final NanoVgApi nanoVg; private final NanoSvgApi nanoSvg; private final StbApi stb; private final TinyFdApi tinyFD; @@ -116,24 +119,29 @@ public UIManagerImpl() throws Throwable { boolean isGl3 = GlCapabilities.isGl3Available(); //#if MC >= 1.16.5 - //$$ nanoVg = new NanoVgImpl(isGl3); +// //$$ nanoVg = new NanoVgImpl(isGl3); //$$ nanoSvg = new NanoSvgImpl(); //$$ stb = new StbImpl(); //$$ tinyFD = new TinyFdImpl(); //#else - nanoVg = Lwjgl3Manager.getIsolated(NanoVgApi.class, LWJGL_IMPL_PACKAGE + "NanoVgImpl", isGl3); +// nanoVg = Lwjgl3Manager.getIsolated(NanoVgApi.class, LWJGL_IMPL_PACKAGE + "NanoVgImpl", isGl3); nanoSvg = Lwjgl3Manager.getIsolated(NanoSvgApi.class, LWJGL_IMPL_PACKAGE + "NanoSvgImpl"); stb = Lwjgl3Manager.getIsolated(StbApi.class, LWJGL_IMPL_PACKAGE + "StbImpl"); tinyFD = Lwjgl3Manager.getIsolated(TinyFdApi.class, LWJGL_IMPL_PACKAGE + "TinyFdImpl"); //#endif - renderer = new NVGRendererImpl(isGl3, nanoVg, nanoSvg, stb); -// renderer = new GLRendererImpl(nanoSvg, stb); +// renderer = new NVGRendererImpl(isGl3, nanoVg, nanoSvg, stb); + renderer = new GLRendererImpl(nanoSvg, stb); } catch (Exception e) { throw new RuntimeException("Failed to get valid rendering implementation", e); } } + @Override + public @Nullable RendererExt getRendererExt() { + return (renderer instanceof RendererExt ? (RendererExt) renderer : null); + } + @Override public Renderer getRenderer() { return renderer; @@ -170,4 +178,19 @@ public OmniRenderPipeline getRenderPipeline() { return pipeline; } + + @Override + public void __setRenderingContext(OmniRenderingContext renderingContext) { + ctx = renderingContext; + } + + @Override + public OmniRenderingContext getRenderingContext() { + return ctx; + } + + @Override + public Font getMCFont() { + return MC_FONT; + } } diff --git a/minecraft/src/main/java/org/polyfrost/oneconfig/api/ui/v1/internal/wrappers/PolyUIScreen.java b/minecraft/src/main/java/org/polyfrost/oneconfig/api/ui/v1/internal/wrappers/PolyUIScreen.java index 56da7a8f5..b911666cf 100644 --- a/minecraft/src/main/java/org/polyfrost/oneconfig/api/ui/v1/internal/wrappers/PolyUIScreen.java +++ b/minecraft/src/main/java/org/polyfrost/oneconfig/api/ui/v1/internal/wrappers/PolyUIScreen.java @@ -26,20 +26,14 @@ package org.polyfrost.oneconfig.api.ui.v1.internal.wrappers; -import dev.deftu.omnicore.api.client.framebuffer.ManagedFramebuffer; -import dev.deftu.omnicore.api.client.framebuffer.OmniFramebuffer; import dev.deftu.omnicore.api.client.input.KeyboardModifiers; import dev.deftu.omnicore.api.client.input.OmniKey; import dev.deftu.omnicore.api.client.input.OmniKeys; import dev.deftu.omnicore.api.client.input.OmniMouseButton; import dev.deftu.omnicore.api.client.render.OmniRenderingContext; -import dev.deftu.omnicore.api.client.render.OmniResolution; import dev.deftu.omnicore.api.client.screen.KeyPressEvent; import dev.deftu.omnicore.api.client.screen.OmniScreen; import dev.deftu.textile.Text; -import dev.deftu.omnicore.api.client.textures.OmniTextureFormat; -import dev.deftu.omnicore.api.color.OmniColors; -import kotlin.Unit; import net.minecraft.client.Minecraft; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -55,6 +49,7 @@ import java.util.function.Consumer; +import static org.lwjgl.opengl.GL11.*; import static org.polyfrost.oneconfig.api.ui.v1.keybind.KeybindManager.translateKey; @SuppressWarnings("unused") @@ -64,11 +59,10 @@ public class PolyUIScreen extends OmniScreen implements BlurScreen { @NotNull public final PolyUI polyUI; - private OmniFramebuffer framebuffer; - private final float designedWidth, designedHeight, initialWidth, initialHeight; private final boolean pauses, blurs; private final Consumer close; + private final int[] viewport = new int[4]; //#if MC < 1.13 private int mx, my; @@ -82,7 +76,7 @@ public PolyUIScreen(@NotNull PolyUI polyUI, float designedWidth, float designedH this.designedHeight = designedHeight; this.initialWidth = polyUI.getMaster().getWidth(); this.initialHeight = polyUI.getMaster().getHeight(); - this.blurs = blurs; + this.blurs = false; //todo fix blur this.pauses = pauses; this.close = onClose; //#if MC <= 1.12.2 @@ -118,42 +112,31 @@ public void onRender(@NotNull OmniRenderingContext ctx, int mouseX, int mouseY, } //#endif - if (framebuffer == null || polyUI == UIManager.INSTANCE.getDefaultInstance()) { + if (polyUI == UIManager.INSTANCE.getDefaultInstance()) { return; } - Drawable master = polyUI.getMaster(); - try { - framebuffer.clearColor(0f, 0f, 0f, 0f); // Clear to transparent black - if (framebuffer instanceof ManagedFramebuffer) { - ((ManagedFramebuffer) framebuffer).clearDepthStencil(1.0, 0); - } - - framebuffer.usingToRender((matrixStack, w, h) -> { - matrixStack.runReplacingGlobalState(polyUI::render); - return Unit.INSTANCE; - }); + // asm: we need to bind the main framebuffer on 1.21.5+ because for some reason mc unbinds it right before... whatever + //#if MC >= 1.21.5 + //$$ kotlin.jvm.functions.Function0 unbind = dev.deftu.omnicore.api.client.framebuffer.OmniFramebuffers.getMain().bind(); + //#endif + Platform.gl().glViewport(viewport); + float factor = Platform.screen().pixelRatio(); + int w = (int) (polyUI.getMaster().getWidth() * factor); + int h = (int) (polyUI.getMaster().getHeight() * factor); + int x = Platform.screen().viewportWidth() / 2 - w / 2; + int y = Platform.screen().viewportHeight() / 2 - h / 2; + glViewport(x, y, w, h); + polyUI.render(); + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + //#if MC >= 1.21.5 + //$$ unbind.invoke(); + //#endif } catch (Exception e) { polyUI.getRenderer().endFrame(); death(e); } - - float ratio = Platform.screen().pixelRatio(); - float scalingFactor = 1f / (float) OmniResolution.getScaleFactor(); - - float scaledX = (Platform.screen().viewportWidth() / 2f - master.getWidth() * ratio / 2f) * scalingFactor; - float scaledY = (Platform.screen().viewportHeight() / 2f - master.getHeight() * ratio / 2f) * scalingFactor; - float scaledWidth = master.getWidth() * scalingFactor * ratio; - float scaledHeight = master.getHeight() * scalingFactor * ratio; - - framebuffer.drawColorTexture( - UIManager.INSTANCE.getRenderPipeline(), - ctx.pose(), - scaledX, scaledY, - scaledWidth, scaledHeight, - OmniColors.WHITE - ); } @Override @@ -242,7 +225,7 @@ public boolean isPausingScreen() { @Override public boolean hasBackgroundBlur() { - return blurs; + return false; } @@ -283,11 +266,6 @@ public void onScreenClose() { } protected final void adjustResolution(float w, float h, boolean force) { - if (this.framebuffer == null) { - int width = Platform.screen().viewportWidth(); - int height = Platform.screen().viewportHeight(); - this.framebuffer = new ManagedFramebuffer(width, height, OmniTextureFormat.RGBA8, OmniTextureFormat.DEPTH24_STENCIL8); - } // asm: normally, a polyui instance is as big as its window and that is it. // however, inside minecraft, the actual content is smaller than the window size, so resizing it directly would just fuck it up. @@ -307,7 +285,6 @@ protected final void adjustResolution(float w, float h, boolean force) { // framebuffer should you know probably be the correct larger size because.. well yeah of course it does // didn't anyone think of that? polyUI.resize(initialWidth * sx, initialHeight * sy, force); - framebuffer.resize((int) (polyUI.getMaster().getWidth() * ratio), (int) (polyUI.getMaster().getHeight() * ratio)); polyUI.getWindow().setPixelRatio(ratio); } catch (Exception e) { death(e); diff --git a/minecraft/src/main/java/org/polyfrost/oneconfig/internal/OneConfig.java b/minecraft/src/main/java/org/polyfrost/oneconfig/internal/OneConfig.java index 2b91497f8..e8d04780e 100644 --- a/minecraft/src/main/java/org/polyfrost/oneconfig/internal/OneConfig.java +++ b/minecraft/src/main/java/org/polyfrost/oneconfig/internal/OneConfig.java @@ -53,6 +53,7 @@ import org.polyfrost.oneconfig.api.hypixel.v1.HypixelUtils; import org.polyfrost.oneconfig.api.platform.v1.Platform; import org.polyfrost.oneconfig.api.ui.v1.UIManager; +import org.polyfrost.oneconfig.api.ui.v1.api.RendererExt; import org.polyfrost.oneconfig.api.ui.v1.internal.BlurHandler; import org.polyfrost.oneconfig.api.ui.v1.keybind.OCKeybindHelper; import org.polyfrost.oneconfig.internal.ui.OneConfigUI; @@ -132,8 +133,8 @@ private void init() { if (Boolean.getBoolean("oneconfig.test")) { try { TestMod_Test.initialize(); - } catch (Throwable ignored) { - ignored.printStackTrace(); + } catch (Throwable e) { + e.printStackTrace(); } } @@ -155,7 +156,7 @@ private void init() { LOGGER.info("OneConfig initialization took {}ms", (System.nanoTime() - t1) / 1_000_000.0); } - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"unchecked", "rawtypes", "UnstableApiUsage"}) private static void registerCommands() { ClientCommandInternals.initialize(); // //#if MC > 1.16 @@ -201,7 +202,16 @@ private static void registerCommands() { return ctx.getSource().replyChat("OK"); }) ) - .build(); + .then(OmniClientCommands.literal("dumpAtlas") + .executes((ctx) -> { + RendererExt ext = UIManager.INSTANCE.getRendererExt(); + if (ext == null) { + return ctx.getSource().replyChat("RendererExt is not available on this platform."); + } + ext.dumpAtlas(); + return ctx.getSource().replyChat("OK"); + }) + ).build(); OmniClientCommands.register(node); OmniClientCommands.register(OmniClientCommands.literal("ocfg").executes(executor).redirect(node)); } diff --git a/modules/config-impl/api/config-impl.api b/modules/config-impl/api/config-impl.api index fe876416f..d9ff1ba0e 100644 --- a/modules/config-impl/api/config-impl.api +++ b/modules/config-impl/api/config-impl.api @@ -101,74 +101,107 @@ protected final class org/polyfrost/oneconfig/api/config/v1/KtConfig$Provider : } public abstract interface class org/polyfrost/oneconfig/api/config/v1/Visualizer { + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public abstract fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$ButtonVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$CheckboxVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$ColorVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } +public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$DefaultImpls { + public static fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Visualizer;Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public static fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Visualizer;Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; +} + public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$DraggableListVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public synthetic fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/impl/Group; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$DropdownVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$InfoVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public synthetic fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/impl/Group; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$KeybindVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$MultiSelectDropdownVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public synthetic fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/impl/Group; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$NumberVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$RadioVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$SliderVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$SwitchVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } public final class org/polyfrost/oneconfig/api/config/v1/Visualizer$TextVisualizer : org/polyfrost/oneconfig/api/config/v1/Visualizer { public fun ()V + public fun toEnumState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; + public fun toState (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/event/State; public fun visualize (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/polyui/component/Drawable; } @@ -440,6 +473,16 @@ public final class org/polyfrost/oneconfig/api/config/v1/dsl/Tree_extensionsKt { public static final fun setVisualizer (Lorg/polyfrost/oneconfig/api/config/v1/Property;Ljava/lang/Class;)V } +public class org/polyfrost/oneconfig/api/config/v1/serialize/adapter/impl/FontAdapter : org/polyfrost/oneconfig/api/config/v1/serialize/adapter/Adapter { + public fun ()V + public synthetic fun deserialize (Ljava/lang/Object;)Ljava/lang/Object; + public fun deserialize (Ljava/lang/String;)Lorg/polyfrost/polyui/data/Font; + public fun getOutputClass ()Ljava/lang/Class; + public fun getTargetClass ()Ljava/lang/Class; + public synthetic fun serialize (Ljava/lang/Object;)Ljava/lang/Object; + public fun serialize (Lorg/polyfrost/polyui/data/Font;)Ljava/lang/String; +} + public class org/polyfrost/oneconfig/api/config/v1/serialize/adapter/impl/PolyColorAdapter : org/polyfrost/oneconfig/api/config/v1/serialize/adapter/Adapter { public fun ()V public synthetic fun deserialize (Ljava/lang/Object;)Ljava/lang/Object; diff --git a/modules/config-impl/src/main/java/org/polyfrost/oneconfig/api/config/v1/ConfigManager.java b/modules/config-impl/src/main/java/org/polyfrost/oneconfig/api/config/v1/ConfigManager.java index f10fb35c6..8c4e28355 100644 --- a/modules/config-impl/src/main/java/org/polyfrost/oneconfig/api/config/v1/ConfigManager.java +++ b/modules/config-impl/src/main/java/org/polyfrost/oneconfig/api/config/v1/ConfigManager.java @@ -37,6 +37,7 @@ import org.polyfrost.oneconfig.api.config.v1.collect.PropertyCollector; import org.polyfrost.oneconfig.api.config.v1.collect.impl.OneConfigCollector; import org.polyfrost.oneconfig.api.config.v1.serialize.ObjectSerializer; +import org.polyfrost.oneconfig.api.config.v1.serialize.adapter.impl.FontAdapter; import org.polyfrost.oneconfig.api.config.v1.serialize.adapter.impl.PolyColorAdapter; import org.polyfrost.oneconfig.api.config.v1.serialize.adapter.impl.Vec4Adapter; import org.polyfrost.oneconfig.api.config.v1.serialize.impl.FileSerializer; @@ -64,6 +65,7 @@ public final class ConfigManager { static { ObjectSerializer.INSTANCE.registerTypeAdapter(new PolyColorAdapter()); + ObjectSerializer.INSTANCE.registerTypeAdapter(new FontAdapter()); ObjectSerializer.INSTANCE.registerTypeAdapter(new Vec4Adapter()); registerCollector(new OneConfigCollector()); } diff --git a/modules/config-impl/src/main/java/org/polyfrost/oneconfig/api/config/v1/serialize/adapter/impl/FontAdapter.java b/modules/config-impl/src/main/java/org/polyfrost/oneconfig/api/config/v1/serialize/adapter/impl/FontAdapter.java new file mode 100644 index 000000000..dbf637a18 --- /dev/null +++ b/modules/config-impl/src/main/java/org/polyfrost/oneconfig/api/config/v1/serialize/adapter/impl/FontAdapter.java @@ -0,0 +1,26 @@ +package org.polyfrost.oneconfig.api.config.v1.serialize.adapter.impl; + +import org.polyfrost.oneconfig.api.config.v1.serialize.adapter.Adapter; +import org.polyfrost.polyui.data.Font; + +public class FontAdapter extends Adapter { + @Override + public Font deserialize(String in) { + return Font.of(in); + } + + @Override + public String serialize(Font in) { + return in.getResourcePath(); + } + + @Override + public Class getTargetClass() { + return Font.class; + } + + @Override + public Class getOutputClass() { + return String.class; + } +} diff --git a/modules/config-impl/src/main/kotlin/org/polyfrost/oneconfig/api/config/v1/Visualizer.kt b/modules/config-impl/src/main/kotlin/org/polyfrost/oneconfig/api/config/v1/Visualizer.kt index fb75c589f..89a4eb6c7 100644 --- a/modules/config-impl/src/main/kotlin/org/polyfrost/oneconfig/api/config/v1/Visualizer.kt +++ b/modules/config-impl/src/main/kotlin/org/polyfrost/oneconfig/api/config/v1/Visualizer.kt @@ -33,7 +33,6 @@ import org.polyfrost.polyui.color.asMutable import org.polyfrost.polyui.component.Drawable import org.polyfrost.polyui.component.extensions.* import org.polyfrost.polyui.component.impl.* -import org.polyfrost.polyui.event.Event import org.polyfrost.polyui.event.State import org.polyfrost.polyui.input.PolyBind import org.polyfrost.polyui.unit.Align @@ -41,15 +40,80 @@ import org.polyfrost.polyui.unit.Align.Wrap import org.polyfrost.polyui.unit.Vec2 import org.polyfrost.polyui.utils.image import org.polyfrost.polyui.utils.mapToArray +import org.polyfrost.polyui.utils.setNumber +import org.polyfrost.polyui.utils.toString +import java.lang.ref.WeakReference import java.util.function.Predicate import kotlin.math.roundToInt /** * Visualizers are procedures that take a property, and return a drawable that represents it. */ +@Suppress("UNCHECKED_CAST") fun interface Visualizer { fun visualize(prop: Property<*>): Drawable + fun Property.toState(): State { + val state = State(getAs()) + var dodge = false + + val stateRef = WeakReference(state) + var callback: Predicate? = null + callback = Predicate { + if (!dodge) { + dodge = true + val ret = stateRef.get()?.set(it) ?: run { + removeCallback(callback!!) + false + } + dodge = false + ret + } else false + } + addCallback(callback) + + state.weaklyListen(this) { + if (!dodge) { + dodge = true + setAs(it) + dodge = false + } + } + return state + } + + fun Property>.toEnumState(): State { + val state = State(get()?.ordinal ?: 0) + var dodge = false + + val stateRef = WeakReference(state) + var callback: Predicate>? = null + callback = Predicate> { + if (!dodge) { + val st = stateRef.get() + if (st == null) { + removeCallback(callback!!) + false + } else { + dodge = true + val ret = st.set(it.ordinal) + dodge = false + ret + } + } else false + } + addCallback(callback) + + state.weaklyListen(this) { + if (!dodge) { + dodge = true + setAs(this.type.enumConstants[it] as Enum<*>) + dodge = false + } + } + return state + } + class ButtonVisualizer : Visualizer { override fun visualize(prop: Property<*>): Drawable { val text = prop.getMetadata("text")?.strv() @@ -68,49 +132,39 @@ fun interface Visualizer { if (p !is PolyColor.Mutable) { prop.setAsReferential(p.asMutable()) } - val state = State(prop.getAs()) + prop as Property + val state = prop.toState() val out = Block(color = state.value, size = Vec2(58f, 32f)).withBorder(3f, color = { page.border20 }) out.onClick { ColorPicker(state, polyUI, attachedDrawable = out); true } - prop.addCallback { - state.value = (it as PolyColor).asMutable() - false - } return out } } class DropdownVisualizer : Visualizer { override fun visualize(prop: Property<*>): Drawable { - val options: Array = prop.getMetadata("options") ?: emptyArray() - if (prop.type.isEnum || prop.type.superclass?.isEnum == true) { - require(options.isEmpty()) { "Dropdowns should not have options when used with enums (offender=${prop.id})" } + val options = prop.getMetadata>("options") + return if (prop.type.isEnum || prop.type.superclass?.isEnum == true) { + require(options.isNullOrEmpty()) { "Dropdowns should not have options when used with enums (offender=${prop.id})" } val constants = if (prop.type.isEnum) prop.type.enumConstants else prop.type.superclass.enumConstants - val index = constants.indexOf(prop.get()) - val s = Dropdown( + prop as Property> + Dropdown( optPadding = 24f, - initial = index, + state = prop.toEnumState(), entries = constants.mapToArray { it as Enum<*> null to (it::class.java.fields[0].get(it) as? String ?: it.name) }, - ).onChange { i: Int -> - prop.setAs(constants[i]) - false - } - // todo setback not supported currently on dropdowns - return s + ) } else { require(prop.type == Int::class.java) { "Dropdowns can only be used with enums or integers (offender=${prop.id}, type=${prop.type})" } + require(options != null) { "Dropdown integer properties must have options specified in their metadata (offender=${prop.id})" } require(options.size >= 2) { "Dropdowns must have at least two options (offender=${prop.id})" } - val s = Dropdown( + prop as Property + Dropdown( optPadding = 24f, - initial = prop.getAs(), + state = prop.toState(), entries = options.mapToArray { null to it }, - ).onChange { i: Int -> - prop.setAs(i) - false - } - return s + ) } } } @@ -169,76 +223,48 @@ fun interface Visualizer { val unit = prop.getMetadata("unit") val min = prop.getMetadata("min") ?: 0f val max = prop.getMetadata("max") ?: 100f - val integral = prop.type == Int::class.java || prop.type == Long::class.java - val placeholder = prop.getMetadata("placeholder") ?: if (integral) "${min.toInt()}-${max.toInt()}" else "$min-$max" - var dodge = false + val placeholder = prop.getMetadata("placeholder") ?: "${min.toString(dps = 2)}-${max.toString(dps = 2)}" + prop as Property + val state = prop.toState() val s = BoxedTextInput( placeholder = placeholder, image = "assets/oneconfig/ico/text.svg".image(), size = Vec2(200f, 32f), - initialValue = prop.getAs().toString(), + value = state.value.toFloat().toString(dps = 2), post = unit - ).apply { - (this[1][0] as TextInput).numeric(min, max, integral).on(Event.Change.Number) { - dodge = true - prop.setAs(if (integral) it.amount.toInt() else it.amount.toFloat()) - } - } - prop.addCallback { - if (!dodge) (s[1][0] as TextInput).text = it.toString() - dodge = false - false - } + ) + s.getTextFromBoxedTextInput().numeric(min, max, state) return s } } class RadioVisualizer : Visualizer { override fun visualize(prop: Property<*>): Drawable { - val options: Array = prop.getMetadata("options") ?: emptyArray() - var dodge = false + val options: Array? = prop.getMetadata("options") if (prop.type.isEnum) { val values = prop.type.enumConstants var field = prop.type::class.java.fields.firstOrNull() if (field?.type == String::class.java) field = null - require(options.isEmpty()) { "Radio button ${prop.id} cannot have options when used with enums" } - val r = - Radiobutton( + require(options.isNullOrEmpty()) { "Radio button ${prop.id} cannot have options when used with enums" } + prop as Property> + return Radiobutton( entries = values.mapToArray { it as Enum<*> null to (field?.get(it) as? String ?: it.name) }, - initial = values.indexOf(prop.get()), + state = prop.toEnumState(), optionLateralPadding = 20f, - ).onChange { amount: Int -> - dodge = true - prop.setAs(values[amount]) - false - } - prop.addCallback { - if (!dodge) r.setRadiobuttonEntry(values.indexOf(it as Enum<*>)) - dodge = false - false - } - return r + ) } else { require(prop.type == Int::class.java) { "Radio buttons ${prop.id} can only be used with enum or integer types (type=${prop.type}" } + require(options != null) { "Radio button ${prop.id} integer properties must have options specified in their metadata" } require(options.size >= 2) { "Radio button ${prop.id} must have at least two options" } - val r = Radiobutton( + prop as Property + return Radiobutton( entries = options.mapToArray { null to it }, - initial = prop.getAs(), + state = prop.toState(), optionLateralPadding = 20f, - ).onChange { amount: Int -> - dodge = true - prop.setAs(amount) - false - } - prop.addCallback { - if (!dodge) r.setRadiobuttonEntry(it as Int) - dodge = false - false - } - return r + ) } } } @@ -249,75 +275,44 @@ fun interface Visualizer { val max = prop.getMetadata("max") ?: 100f val stepAmount = prop.getMetadata("step") ?: 0f val nsteps = if (stepAmount > 0f) ((max - min) / stepAmount).roundToInt() else 0 - var dodge = false - val value = prop.getAs().toFloat() - if (value.isNaN() || value.isInfinite()) { - println("Warning: Slider property ${prop.id} has invalid value $value, resetting to $min") - } + prop as Property + val state = prop.toState() - val initialValue = value.coerceIn(min, max) - // todo stepped - val s = - Slider( + val f = state.value.toFloat() + if (f.isNaN() || f.isInfinite()) { + println("Warning: Slider property ${prop.id} has invalid value $f, resetting to $min") + state.setNumber(min) + } else state.setNumber(f.coerceIn(min, max)) + + return Slider( min = min, max = max, length = 200f, steps = nsteps, - initialValue = initialValue, - integral = prop.type == Int::class.java || prop.type == Long::class.java, - ).onChange { amount: Float -> - dodge = true - if (prop.type == Int::class.java) prop.setAs(amount.toInt()) else prop.setAs(amount) - false - } - prop.addCallback { - if (!dodge) s.setSliderValue((it as Number).toFloat(), min, max, false) - dodge = false - false - } - return s + state = state + ) } } class SwitchVisualizer : Visualizer { override fun visualize(prop: Property<*>): Drawable { - val state = prop.getAs() - var dodge = false + prop as Property val s = Switch( lateralStretch = 2f, size = 21f, - state = state, - ).onChange { new: Boolean -> - dodge = true - prop.setAs(new) - false - } - prop.addCallback { - if (!dodge) s.toggle(it as Boolean) - dodge = false - false - } + state = prop.toState(), + ) return s } } class CheckboxVisualizer : Visualizer { override fun visualize(prop: Property<*>): Drawable { - val state = prop.getAs() - var dodge = false + prop as Property val s = Checkbox( size = 24f, - state = state, - ).onChange { new: Boolean -> - dodge = true - prop.setAs(new) - false - } - prop.addCallback { - if (!dodge) s.toggle(it as Boolean) - dodge = false - false - } + state = prop.toState(), + ) return s } } @@ -328,31 +323,23 @@ fun interface Visualizer { val regexString = prop.getMetadata("regex") val regex = regexString?.let { Regex(it) } val validate = prop.getMetadata>("validate") - var dodge = false + prop as Property + val state = prop.toState() val s = BoxedTextInput( image = "assets/oneconfig/ico/text.svg".image(), placeholder = placeholder, //size = Vec2(200f, 32f), - initialValue = prop.getAs(), - ).onChange { text: String -> + value = state, + ).onChange(state) { text: String -> if (validate != null && !validate.test(text)) { - shake() - return@onChange true + shake(); return@onChange true } if (regex != null && !regex.matches(text)) { - shake() - return@onChange true + shake(); return@onChange true } - dodge = true - prop.setAs(text) false } if (regexString != null) s.addHoverInfo(Text("Must match regex: $regexString")) - prop.addCallback { - if (!dodge) (s[1][0] as TextInput).text = it as String - dodge = false - false - } return s } } diff --git a/modules/config-impl/src/main/kotlin/org/polyfrost/oneconfig/api/config/v1/internal/ConfigVisualizer.kt b/modules/config-impl/src/main/kotlin/org/polyfrost/oneconfig/api/config/v1/internal/ConfigVisualizer.kt index 3ccb5836a..ad6e3d601 100644 --- a/modules/config-impl/src/main/kotlin/org/polyfrost/oneconfig/api/config/v1/internal/ConfigVisualizer.kt +++ b/modules/config-impl/src/main/kotlin/org/polyfrost/oneconfig/api/config/v1/internal/ConfigVisualizer.kt @@ -38,6 +38,7 @@ import org.polyfrost.polyui.component.extensions.* import org.polyfrost.polyui.component.impl.* import org.polyfrost.polyui.data.PolyImage import org.polyfrost.polyui.event.Event +import org.polyfrost.polyui.event.State import org.polyfrost.polyui.operations.Resize import org.polyfrost.polyui.operations.Rotate import org.polyfrost.polyui.unit.* @@ -261,43 +262,42 @@ open class ConfigVisualizer { wrapForAccordion(vis.visualize(node), node.title ?: return@map null, node.description).addHideHandler(node).addResetMenu(root, node).linkTo(node) } - var open = true + var open = State(true) val e: Property<*>? = tree.getProp("enabled") val toWrap: Drawable var enabled: Property? = null var contentHeight = -1f // asm: signature as it prevents re-wrapping of function val openInsn: Drawable.(Any?) -> Unit = { - open = !open val arrow = if (enabled != null) this[1][1] else this[1] val anim = Animations.Default.create(0.6.seconds) - Rotate(arrow, if (!open) PI else 0.0, false, anim).add() + Rotate(arrow, if (!open.value) PI else 0.0, false, anim).add() val content = parent[1] if (contentHeight == -1f) contentHeight = content.height - Resize(parent, width = 0f, height = if (open) -contentHeight else contentHeight, add = true, animation = anim).add() - Resize(content, width = 0f, height = if (open) -contentHeight else contentHeight, add = true, animation = anim).add() + Resize(parent, width = 0f, height = if (open.value) -contentHeight else contentHeight, add = true, animation = anim).add() + Resize(content, width = 0f, height = if (open.value) -contentHeight else contentHeight, add = true, animation = anim).add() // won't ever open properly unless it renders at least once (tee hee) :) - if (!open) { + if (!open.value) { content.height = 1f content.renders = true } } if (e != null && e.type == Boolean::class.java && e.getVisualizer() == null) { - open = e.getAs() + open.value = e.getAs() toWrap = Group( Switch( lateralStretch = 2f, size = 21f, state = open - ).onToggle { - enabled?.setAs(it) - if (open != it) (parent.parent as Drawable).openInsn(null) + ).onChange(open) { + enabled?.set(it) + if (open.value != it) (parent.parent as Drawable).openInsn(null) }, Image("polyui/chevron-down.svg").also { it.rotation = PI } ) // lmao - open = !open + //open.value = !open.value @Suppress("UNCHECKED_CAST") // reason: #already-type-checked enabled = e as Property } else { diff --git a/modules/config/api/config.api b/modules/config/api/config.api index 2d094c931..a42f83ef4 100644 --- a/modules/config/api/config.api +++ b/modules/config/api/config.api @@ -73,12 +73,13 @@ public abstract class org/polyfrost/oneconfig/api/config/v1/Property : org/polyf public fun onDisplayChange (Ljava/util/function/Consumer;)V public fun overwrite (Lorg/polyfrost/oneconfig/api/config/v1/Node;ZZLorg/polyfrost/oneconfig/api/config/v1/Tree;)V public static fun recast (Lorg/polyfrost/oneconfig/api/config/v1/Property;)Lorg/polyfrost/oneconfig/api/config/v1/Property; + public final fun removeCallback (Ljava/util/function/Predicate;)Z public fun revaluateDisplay ()V - public final fun set (Ljava/lang/Object;)V + public final fun set (Ljava/lang/Object;)Z protected abstract fun set0 (Ljava/lang/Object;)V - public final fun setAs (Ljava/lang/Object;)V - public final fun setAsReferential (Ljava/lang/Object;)V - public fun setReferential (Ljava/lang/Object;)V + public final fun setAs (Ljava/lang/Object;)Z + public final fun setAsReferential (Ljava/lang/Object;)Z + public fun setReferential (Ljava/lang/Object;)Z public final fun toString ()Ljava/lang/String; } diff --git a/modules/config/src/main/java/org/polyfrost/oneconfig/api/config/v1/Property.java b/modules/config/src/main/java/org/polyfrost/oneconfig/api/config/v1/Property.java index 3d2ae4ac7..5937053ce 100644 --- a/modules/config/src/main/java/org/polyfrost/oneconfig/api/config/v1/Property.java +++ b/modules/config/src/main/java/org/polyfrost/oneconfig/api/config/v1/Property.java @@ -179,6 +179,13 @@ public final Property addCallback(@NotNull Collection> callbacks return this; } + public final boolean removeCallback(@NotNull Predicate callback) { + if (callbacks != null) { + return callbacks.remove(callback); + } + return false; + } + @Override @SuppressWarnings({"unchecked", "rawtypes"}) public void overwrite(Node with, boolean preserveMissingOptions, boolean skipOverwritten, @Nullable Tree root) { @@ -218,9 +225,9 @@ protected final void clearCallbacks() { *
* The value (and callbacks) are only set/called if the value is different from the previous value (using {@link Objects#equals(Object)}). */ - public final void set(@Nullable T value) { - if (Objects.equals(this.get(), value)) return; - setReferential(value); + public final boolean set(@Nullable T value) { + if (Objects.equals(this.get(), value)) return false; + return setReferential(value); } /** @@ -229,7 +236,7 @@ public final void set(@Nullable T value) { * Unlike {@link #set(Object)}, this will always set the value, regardless of if it is the same as the previous value. * Use this method with caution. */ - public void setReferential(@Nullable T value) { + public boolean setReferential(@Nullable T value) { if (callbacks != null) { T prev = get(); set0(value); @@ -238,13 +245,14 @@ public void setReferential(@Nullable T value) { if (p.test(value)) { LOGGER.info("property {} set cancelled by {}", this.getID(), p); set0(prev); - return; + return true; } } catch (Throwable t) { LOGGER.error("failed to call cancellable callback {} on property {}", p, this.getID(), t); } } } else set0(value); + return false; } protected abstract void set0(@Nullable T value); @@ -274,8 +282,8 @@ public final V getAs() { * This method is unsafe, and will throw a {@link ClassCastException} if the value is not of the correct type. */ @SuppressWarnings("unchecked") - public final void setAs(V value) { - set((T) WrappingUtils.richCast(value, type)); + public final boolean setAs(V value) { + return set((T) WrappingUtils.richCast(value, type)); } /** @@ -285,8 +293,8 @@ public final void setAs(V value) { * Use this method with caution. */ @SuppressWarnings("unchecked") - public final void setAsReferential(V value) { - setReferential((T) WrappingUtils.richCast(value, type)); + public final boolean setAsReferential(V value) { + return setReferential((T) WrappingUtils.richCast(value, type)); } /** @@ -454,7 +462,8 @@ static final class Dummy extends Property { @Override @Deprecated - public void setReferential(@Nullable Void value) { + public boolean setReferential(@Nullable Void value) { + return false; } @Override diff --git a/modules/dependencies/legacy/build.gradle.kts b/modules/dependencies/legacy/build.gradle.kts index edd26cd99..27ed664f7 100644 --- a/modules/dependencies/legacy/build.gradle.kts +++ b/modules/dependencies/legacy/build.gradle.kts @@ -1,7 +1,7 @@ import org.polyfrost.gradle.provideIncludedDependencies plugins { - id("gg.essential.loom") + id("dev.deftu.gradle.loom") } repositories { diff --git a/modules/events/src/main/java/org/polyfrost/oneconfig/api/event/v1/EventManager.java b/modules/events/src/main/java/org/polyfrost/oneconfig/api/event/v1/EventManager.java index 0a155bdce..03d830cbd 100644 --- a/modules/events/src/main/java/org/polyfrost/oneconfig/api/event/v1/EventManager.java +++ b/modules/events/src/main/java/org/polyfrost/oneconfig/api/event/v1/EventManager.java @@ -218,7 +218,7 @@ public void post(E event) { //noinspection StringConcatenationArgumentToLogCall LOGGER.error("Failed to invoke event handler for " + event.getClass().getName(), throwable); if (OmniLoader.isDevelopment()) { - throw new EventException("Event handler " + handler.getEventClass().getName() + " for " + handler.getEventClass().getName() + " failed", throwable); + throw new EventException("Event handler " + handler.getClass().getName() + " for " + handler.getEventClass().getName() + " failed", throwable); } if (handler.onError()) { LOGGER.error("removing {} registered to {} as it has failed too many times ({})", handler, event.getClass().getName(), EventHandler.ERROR_THRESHOLD); diff --git a/modules/hud/api/hud.api b/modules/hud/api/hud.api index 4c62331c1..0da2ebebe 100644 --- a/modules/hud/api/hud.api +++ b/modules/hud/api/hud.api @@ -94,8 +94,7 @@ public abstract class org/polyfrost/oneconfig/api/hud/v1/LegacyHud : org/polyfro public static synthetic fun createLegacy-Op45VSw$default (Lorg/polyfrost/oneconfig/api/hud/v1/LegacyHud;[Lorg/polyfrost/polyui/component/Component;Lorg/polyfrost/polyui/unit/Align;JZILjava/lang/Object;)Lorg/polyfrost/oneconfig/api/hud/v1/LegacyHud$LegacyHudComponent; public abstract fun getHeight ()F public abstract fun getWidth ()F - public fun render (Lorg/polyfrost/polyui/renderer/Renderer;Lorg/polyfrost/polyui/component/Drawable;)V - public abstract fun renderLegacy (Ldev/deftu/omnicore/api/client/render/OmniRenderingContext;FFFF)V + public abstract fun renderLegacy (Lorg/polyfrost/polyui/renderer/Renderer;Ldev/deftu/omnicore/api/client/render/OmniRenderingContext;FFFFLorg/polyfrost/polyui/component/Drawable;)V public abstract fun setHeight (F)V public abstract fun setWidth (F)V } @@ -106,7 +105,6 @@ public class org/polyfrost/oneconfig/api/hud/v1/LegacyHud$LegacyHudComponent : o public fun getHeight ()F public fun getWidth ()F protected fun render ()V - public final fun renderLegacy (Ldev/deftu/omnicore/api/client/render/OmniRenderingContext;)V public fun setHeight (F)V public fun setWidth (F)V } diff --git a/modules/hud/src/main/kotlin/org/polyfrost/oneconfig/api/hud/v1/LegacyHud.kt b/modules/hud/src/main/kotlin/org/polyfrost/oneconfig/api/hud/v1/LegacyHud.kt index 056bb0456..5a96e9090 100644 --- a/modules/hud/src/main/kotlin/org/polyfrost/oneconfig/api/hud/v1/LegacyHud.kt +++ b/modules/hud/src/main/kotlin/org/polyfrost/oneconfig/api/hud/v1/LegacyHud.kt @@ -30,6 +30,7 @@ import dev.deftu.omnicore.api.client.render.OmniRenderingContext import dev.deftu.omnicore.api.client.render.OmniResolution import org.jetbrains.annotations.ApiStatus import org.polyfrost.oneconfig.api.platform.v1.Platform +import org.polyfrost.oneconfig.api.ui.v1.UIManager import org.polyfrost.polyui.component.Component import org.polyfrost.polyui.component.Drawable import org.polyfrost.polyui.renderer.Renderer @@ -38,7 +39,7 @@ import org.polyfrost.polyui.unit.AlignDefault import org.polyfrost.polyui.unit.Vec2 /** - * [Hud] implementation that uses the old rendering system, with a standard [render] method. + * [Hud] implementation that uses the old rendering system. * * **You must** ensure that the [width] and [height] properties accurately reflect the size of the HUD. * Note that they are only queried when the HUD is first created, and when the [update] method returns `true`. @@ -69,18 +70,7 @@ abstract class LegacyHud(id: String, title: String, category: Category) : Hud): Drawable { + val state = State(0) return Group( Radiobutton( "assets/oneconfig/ico/cog.svg".image() to "oneconfig.hudeditor.settings.title", "assets/oneconfig/ico/paintbrush.svg".image() to "oneconfig.hudeditor.designer.title", - ).onInit { color = polyUI.colors.component.bgDeselected }.onChange { index: Int -> + state = state + ).onInit { color = polyUI.colors.component.bgDeselected }.onChange(state) { index: Int -> if (index == 0) { parent[1] = HudVisualizer.get(hud.tree) } else { @@ -163,37 +163,53 @@ private fun makeHudDesigner(hud: Hud<*>): Drawable { val theHud = hud.get() val bg = hud.getBackground() val receiver = bg ?: theHud + + val padState = State(receiver.alignment.padBetween.x).listen { + receiver.alignment = receiver.alignment.copy(padBetween = Vec2(it, it)) + receiver.recalculate(false) + false + } + val radiusState = State((receiver as? Block)?.radii?.getOrNull(0) ?: 0f).listen { + (receiver as? Block)?.radius(it) + receiver.recalculate(false) + false + } + + val xPadState = State(receiver.padding.x).listen { + receiver.padding = receiver.padding.copy(x = it) + receiver.recalculate(false) + false + } + val yPadState = State(receiver.padding.y).listen { + receiver.padding = receiver.padding.copy(y = it) + receiver.recalculate(false) + false + } + val wPadState = State(receiver.padding.w).listen { + receiver.padding = receiver.padding.copy(w = it) + receiver.recalculate(false) + false + } + val hPadState = State(receiver.padding.h).listen { + receiver.padding = receiver.padding.copy(h = it) + receiver.recalculate(false) + false + } + + val staticWidth = State(false) + return Group( Text("oneconfig.hudeditor.general.title", fontSize = 16f).setFont { medium }, subheading("oneconfig.hudeditor.padding.title", "oneconfig.hudeditor.padding.info"), interactiveAlignment(receiver), Group( - DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", max = 30f, size = Vec2(120f, 32f)).also { - it[0].onChange { value: Float -> - receiver.alignment = receiver.alignment.copy(padBetween = Vec2(value, value)) - receiver.recalculate(false) - false - } - }.titled("oneconfig.hudeditor.padding.between"), - DraggingNumericTextInput(icon = "assets/oneconfig/ico/maximise.svg".image(), suffix = "px", max = 10f, size = Vec2(120f, 32f)).also { - it[0].onChange { value: Float -> - (receiver as? Block)?.radius(value) - false - } - }.titled("oneconfig.hudeditor.corner.radius"), + DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", max = 30f, size = Vec2(120f, 32f), state = padState).titled("oneconfig.hudeditor.padding.between"), + DraggingNumericTextInput(icon = "assets/oneconfig/ico/maximise.svg".image(), suffix = "px", max = 10f, size = Vec2(120f, 32f), state = radiusState).titled("oneconfig.hudeditor.corner.radius"), Group( - DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", initialValue = receiver.padding.x, max = 30f, size = Vec2(68f, 32f)).onChange { value: Float -> - receiver.padding = receiver.padding.copy(x = value) - }, - DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", initialValue = receiver.padding.y, max = 30f, size = Vec2(68f, 32f)).onChange { value: Float -> - receiver.padding = receiver.padding.copy(y = value) - }.also { it[0].rotation = PI / 2 }, - DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", initialValue = receiver.padding.w, max = 30f, size = Vec2(68f, 32f)).onChange { value: Float -> - receiver.padding = receiver.padding.copy(w = value) - }.also { it[0].rotation = PI }, - DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", initialValue = receiver.padding.h, max = 30f, size = Vec2(68f, 32f)).onChange { value: Float -> - receiver.padding = receiver.padding.copy(h = value) - }.also { it[0].rotation = PI * 1.5 }, + DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", state = xPadState, max = 30f, size = Vec2(68f, 32f)), + DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", state = yPadState, max = 30f, size = Vec2(68f, 32f)).also { it[0].rotation = PI / 2 }, + DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", state = wPadState, max = 30f, size = Vec2(68f, 32f)).also { it[0].rotation = PI }, + DraggingNumericTextInput(icon = "assets/oneconfig/ico/align.svg".image(), suffix = "px", state = hPadState, max = 30f, size = Vec2(68f, 32f)).also { it[0].rotation = PI * 1.5 }, alignment = Align(main = Align.Content.SpaceBetween, wrap = Align.Wrap.NEVER, padEdges = Vec2.ZERO), size = Vec2(308f, 32f) ).titled("oneconfig.hudeditor.padding.edges"),//.padded(16f, 12f, 0f, 0f), @@ -201,15 +217,15 @@ private fun makeHudDesigner(hud: Hud<*>): Drawable { size = Vec2(320f, 126f), ), Group( - Checkbox(size = 18f).onToggle { + Checkbox(size = 18f, state = staticWidth).onChange(staticWidth) { hud.staticWidth = it val siblings = siblings siblings[2].isEnabled = !it siblings[3].isEnabled = !it }, Text("oneconfig.hudeditor.staticwidth"), - DraggingNumericTextInput(pre = "oneconfig.width", suffix = "px", initialValue = receiver.width.roundToInt().toFloat(), max = 1000f, size = Vec2(128f, 32f)), - DraggingNumericTextInput(pre = "oneconfig.height", suffix = "px", initialValue = receiver.height.roundToInt().toFloat(), max = 1000f, size = Vec2(128f, 32f)), + DraggingNumericTextInput(pre = "oneconfig.width", suffix = "px", state = State.of(receiver::width), max = 1000f, size = Vec2(128f, 32f)), + DraggingNumericTextInput(pre = "oneconfig.height", suffix = "px", state = State.of(receiver::height), max = 1000f, size = Vec2(128f, 32f)), alignment = Align(padBetween = Vec2(12f, 6f)) ), *(if (bg != null) colorOptions(bg) else arrayOf()), @@ -316,68 +332,81 @@ fun interactiveAlignment(recv: Drawable): Block { fun textOptions(text: Text): Drawable { var prevWeight: Font.Weight = Font.Weight.Regular + var example: Text? = null + + val fontState = State(0).listen { + text.font = when (it) { + 1 -> PolyUI.monospaceFont + 2 -> UIManager.INSTANCE.mcFont + else -> text.polyUI.fonts.get(text.fontWeight, text.italic) + } + text._parent?.recalculate(false) + example?.run { + font = text.font + parent.recalculate() + } + false + } + val fontSize = State(text.fontSize).listen { + text.fontSize = it + text._parent?.recalculate(false) + example?.run { + fontSize = text.fontSize + parent.recalculate() + } + false + } + + val fontWeight = State(text.fontWeight.value / 100 - 1).listen { + text.fontWeight = Font.byWeight((it + 1) * 100) + text._parent?.recalculate(false) + example?.run { + fontWeight = text.fontWeight + parent.recalculate() + } + false + } + + val boldState = State(text.fontWeight.value > 500).listen { + if (it) { + prevWeight = text.fontWeight + text.fontWeight = when (text.fontWeight) { + Font.Weight.Thin, Font.Weight.ExtraLight, Font.Weight.Light -> Font.Weight.SemiBold + Font.Weight.Regular -> Font.Weight.Bold + Font.Weight.Medium -> Font.Weight.ExtraBold + else -> text.fontWeight + } + } else { + text.fontWeight = prevWeight + } + false + } + return Group( subheading("oneconfig.hudeditor.text.title", "oneconfig.hudeditor.text.info"), Block( - Text("oneconfig.hudeditor.text.example", fontSize = 16f), + Text("oneconfig.hudeditor.text.example", fontSize = 16f).also { example = it }, size = Vec2(476f, 58f), alignment = alignC, ).withBorder(), Dropdown( - "Poppins", "JetBrains Mono", "Minecraft" - ).onChange { it: Int -> - text.font = when (it) { - 1 -> PolyUI.monospaceFont - 2 -> mcFont.get(text.fontWeight, text.italic) - else -> polyUI.fonts.get(text.fontWeight, text.italic) - } - text._parent?.recalculate(false) - val ex = (parent.parent[1][0] as? Text) ?: return@onChange false - ex.font = text.font - ex.parent.recalculate() - false - }.titled("oneconfig.hudeditor.text.font"), - DraggingNumericTextInput("assets/oneconfig/ico/text-input.svg".image(), initialValue = text.fontSize.roundToInt().toFloat(), min = 1f, size = Vec2(72f, 0f), suffix = "px").also { - it[0].onChange { value: Float -> - text.fontSize = value - text._parent?.recalculate(false) - val ex = (parent.parent.parent[1][0] as? Text) ?: return@onChange false - ex.fontSize = text.fontSize - ex.parent.recalculate() - false - } - }.titled("oneconfig.hudeditor.text.size"), + "Poppins", "JetBrains Mono", "Minecraft", + state = fontState + ).titled("oneconfig.hudeditor.text.font"), + DraggingNumericTextInput("assets/oneconfig/ico/text-input.svg".image(), state = fontSize, min = 1f, size = Vec2(72f, 0f), suffix = "px").titled("oneconfig.hudeditor.text.size"), Dropdown( "oneconfig.fweight.100", "oneconfig.fweight.200", "oneconfig.fweight.300", "oneconfig.fweight.400", "oneconfig.fweight.500", - ).onChange { it: Int -> - text.fontWeight = Font.byWeight((it + 1) * 100) - text._parent?.recalculate(false) - val ex = (parent.parent[1][0] as? Text) ?: return@onChange false - ex.fontWeight = text.fontWeight - ex.parent.recalculate() - false - }.titled("oneconfig.hudeditor.text.weight"), + state = fontWeight, + ).titled("oneconfig.hudeditor.text.weight"), Group( - Block(Image("assets/oneconfig/ico/bold.svg"), alignment = alignNoPad).radius(2f).toggleable(text.fontWeight.value > 500).onToggle { - if (it) { - prevWeight = text.fontWeight - text.fontWeight = when (text.fontWeight) { - Font.Weight.Thin, Font.Weight.ExtraLight, Font.Weight.Light -> Font.Weight.SemiBold - Font.Weight.Regular -> Font.Weight.Bold - Font.Weight.Medium -> Font.Weight.ExtraBold - else -> text.fontWeight - } - } else { - text.fontWeight = prevWeight - } - }, - Block(Image("assets/oneconfig/ico/italic.svg"), alignment = alignNoPad).radius(2f).toggleable(text.italic).onToggle { text.italic = it }, - Block(Image("assets/oneconfig/ico/underline.svg"), alignment = alignNoPad).radius(2f).toggleable(text.underline).onToggle { text.underline = it }, - Block(Image("assets/oneconfig/ico/strikethrough.svg"), alignment = alignNoPad).radius(2f).toggleable(text.strikethrough).onToggle { text.strikethrough = it }, + Block(Image("assets/oneconfig/ico/bold.svg"), alignment = alignNoPad).radius(2f).toggleable(boldState), + Block(Image("assets/oneconfig/ico/italic.svg"), alignment = alignNoPad).radius(2f).toggleable(State.of(text::italic)), + Block(Image("assets/oneconfig/ico/underline.svg"), alignment = alignNoPad).radius(2f).toggleable(State.of(text::underline)), + Block(Image("assets/oneconfig/ico/strikethrough.svg"), alignment = alignNoPad).radius(2f).toggleable(State.of(text::strikethrough)), ).titled("oneconfig.hudeditor.text.effects"), *colorOptions(text), size = Vec2(476f, 0f), diff --git a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/OneConfigUI.kt b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/OneConfigUI.kt index f54945347..f33e66275 100644 --- a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/OneConfigUI.kt +++ b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/OneConfigUI.kt @@ -25,6 +25,7 @@ */ @file:JvmName("OneConfigUI") +@file:Suppress("FunctionName") package org.polyfrost.oneconfig.internal.ui @@ -55,6 +56,7 @@ import org.polyfrost.polyui.data.Cursor import org.polyfrost.polyui.data.Font import org.polyfrost.polyui.data.PolyImage import org.polyfrost.polyui.event.Event +import org.polyfrost.polyui.event.State import org.polyfrost.polyui.operations.Move import org.polyfrost.polyui.operations.Recolor import org.polyfrost.polyui.unit.Align @@ -132,6 +134,32 @@ object OneConfigUI { } if (!OmniLoader.isDevelopment) builder.pauses() + val searchState = State("").listen { text: String -> + if (text.length > 2) { + if(current?.name != "oneconfig.search") { + val search = Group(children = ConfigVisualizer.INSTANCE.getMatching(text).toTypedArray(), visibleSize = Vec2(1130f, 635f)).named("oneconfig.search") + if (search.children.isNullOrEmpty()) search.addChild(searchNoneFound, recalculate = false) + openPage(search, SetAnimation.Fade) + } else { + val search = current as Group + search.children?.clear() + ConfigVisualizer.INSTANCE.getMatching(text).fastEach { + search.addChild(it, recalculate = false) + } + if (search.children.isNullOrEmpty()) search.addChild(searchNoneFound, recalculate = false) + // search.at = search.screenAt + search.recalculate(false) + search.resetScroll() + search.visibleSize = Vec2(1130f, 635f).rescaleToPolyUIInstance(search.polyUI) + search.clipChildren() + } + } else { + openPage(ModsPage(collectTrees()), SetAnimation.Fade) + } + + false + } + val (polyUI, win) = builder.makeAndOpenWithRef( Block( Block( @@ -210,31 +238,8 @@ object OneConfigUI { TextInput( placeholder = "oneconfig.search.placeholder", visibleSize = Vec2(210f, 12f), - ).onChange { text: String -> - if (text.length > 2) { - if(current?.name != "oneconfig.search") { - val search = Group(children = ConfigVisualizer.INSTANCE.getMatching(text).toTypedArray(), visibleSize = Vec2(1130f, 635f)).named("oneconfig.search") - if (search.children.isNullOrEmpty()) search.addChild(searchNoneFound, recalculate = false) - openPage(search, SetAnimation.Fade) - } else { - val search = current as Group - search.children?.clear() - ConfigVisualizer.INSTANCE.getMatching(text).fastEach { - search.addChild(it, recalculate = false) - } - if (search.children.isNullOrEmpty()) search.addChild(searchNoneFound, recalculate = false) - // search.at = search.screenAt - search.recalculate(false) - search.resetScroll() - search.visibleSize = Vec2(1130f, 635f).rescaleToPolyUIInstance(polyUI) - search.clipChildren() - } - } else { - openPage(ModsPage(collectTrees()), SetAnimation.Fade) - } - - false - }, + text = searchState, + ), size = Vec2(256f, 32f), alignment = Align(pad = Vec2(10f, 8f)), ).withBorder(1f) { page.border5 }.named("SearchField").onRightClick { @@ -293,9 +298,11 @@ object OneConfigUI { prevArrow?.disable(false) } current = page - val translated = ui.polyUI.translator.translate(page.name) - val title = ui[1][0][0][2] as Text - title._text = translated + // todo expose _theText or do this in a better way +// val translated = ui.polyUI.translator.translate(page.name) +// val title = ui[1][0][0][2] as Text +// @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UnstableApiUsage") +// title._theText = translated val prev = ui[1][1] ui[1].set(prev, page, animation) } diff --git a/modules/ui/api/ui.api b/modules/ui/api/ui.api index 3e5e9ed9f..3e8776876 100644 --- a/modules/ui/api/ui.api +++ b/modules/ui/api/ui.api @@ -37,12 +37,16 @@ public final class org/polyfrost/oneconfig/api/ui/v1/OCPolyUIBuilder : org/polyf public abstract interface class org/polyfrost/oneconfig/api/ui/v1/UIManager { public static final field INSTANCE Lorg/polyfrost/oneconfig/api/ui/v1/UIManager; + public abstract fun __setRenderingContext (Ldev/deftu/omnicore/api/client/render/OmniRenderingContext;)V public fun createDefault ()Lorg/polyfrost/polyui/PolyUI; public abstract fun createPolyUIScreen (Lorg/polyfrost/polyui/PolyUI;FFZZLjava/util/function/Consumer;)Ldev/deftu/omnicore/api/client/screen/OmniScreen; public abstract fun createWindow ()Lorg/polyfrost/polyui/renderer/Window; public abstract fun getDefaultInstance ()Lorg/polyfrost/polyui/PolyUI; + public abstract fun getMCFont ()Lorg/polyfrost/polyui/data/Font; public abstract fun getRenderPipeline ()Ldev/deftu/omnicore/api/client/render/pipeline/OmniRenderPipeline; public abstract fun getRenderer ()Lorg/polyfrost/polyui/renderer/Renderer; + public abstract fun getRendererExt ()Lorg/polyfrost/oneconfig/api/ui/v1/api/RendererExt; + public abstract fun getRenderingContext ()Ldev/deftu/omnicore/api/client/render/OmniRenderingContext; public abstract fun getTinyFD ()Lorg/polyfrost/oneconfig/api/ui/v1/api/TinyFdApi; } @@ -118,13 +122,18 @@ public abstract interface class org/polyfrost/oneconfig/api/ui/v1/api/NanoVgApi$ public abstract fun NVG_ROUND ()I } +public abstract interface class org/polyfrost/oneconfig/api/ui/v1/api/RendererExt { + public abstract fun dumpAtlas ()V +} + public abstract interface class org/polyfrost/oneconfig/api/ui/v1/api/StbApi { - public abstract fun createFontInfo ()J - public abstract fun createPackContext ()J - public abstract fun createPackRange ()J - public abstract fun createPackedCharArray (I)J + public abstract fun font_CreateFontInfo ()J + public abstract fun font_CreatePackContext ()J + public abstract fun font_CreatePackRange ()J + public abstract fun font_CreatePackedCharArray (I)J public abstract fun font_GetFontVMetrics (J[I[I[I)V public abstract fun font_GetPackedGlyph (JI)J + public abstract fun font_InitFont (JLjava/nio/ByteBuffer;)Z public abstract fun font_PackBegin (JLjava/nio/ByteBuffer;IIIIJ)Z public abstract fun font_PackEnd (J)V public abstract fun font_PackFontRange (JLjava/nio/ByteBuffer;IFIIJ)Z @@ -145,7 +154,6 @@ public abstract interface class org/polyfrost/oneconfig/api/ui/v1/api/StbApi { public abstract fun image_free (Ljava/nio/ByteBuffer;)V public abstract fun image_load_from_memory (Ljava/nio/ByteBuffer;[I[I[II)Ljava/nio/ByteBuffer; public abstract fun image_write_png (Ljava/lang/String;IIILjava/nio/ByteBuffer;I)V - public abstract fun initFont (JLjava/nio/ByteBuffer;)Z } public abstract interface class org/polyfrost/oneconfig/api/ui/v1/api/TinyFdApi { diff --git a/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/UIManager.java b/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/UIManager.java index 265ddf2aa..e911aa8a7 100644 --- a/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/UIManager.java +++ b/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/UIManager.java @@ -47,15 +47,16 @@ import org.polyfrost.oneconfig.api.event.v1.EventDelay; import org.polyfrost.oneconfig.api.event.v1.EventManager; import org.polyfrost.oneconfig.api.event.v1.events.HudRenderEvent; -import org.polyfrost.oneconfig.api.event.v1.events.RenderEvent; import org.polyfrost.oneconfig.api.event.v1.events.ResizeEvent; import org.polyfrost.oneconfig.api.event.v1.events.WorldEvent; import org.polyfrost.oneconfig.api.platform.v1.Platform; +import org.polyfrost.oneconfig.api.ui.v1.api.RendererExt; import org.polyfrost.oneconfig.api.ui.v1.api.TinyFdApi; import org.polyfrost.polyui.PolyUI; import org.polyfrost.polyui.Settings; import org.polyfrost.polyui.component.Component; import org.polyfrost.polyui.component.Drawable; +import org.polyfrost.polyui.data.Font; import org.polyfrost.polyui.renderer.Renderer; import org.polyfrost.polyui.renderer.Window; @@ -65,6 +66,7 @@ /** * Abstraction over the LWJGL3 implementation and loading. */ +@SuppressWarnings("DeprecatedIsStillUsed") public interface UIManager { UIManager INSTANCE = ServiceLoader.load( UIManager.class, @@ -76,6 +78,12 @@ public interface UIManager { */ Renderer getRenderer(); + /** + * Return the renderer extension instance. This interface specifies additional (debug) operations for the renderer. + */ + @Nullable + RendererExt getRendererExt(); + /** * Return the TinyFD implementation instance. This interface specifies operations for opening native * file dialogs, and showing notifications. @@ -107,6 +115,25 @@ public interface UIManager { OmniRenderPipeline getRenderPipeline(); + /** + * Return the current rendering context of the default UI instance. + * This method is internal as, well, you shouldn't have any reason to use it. + *
+ * In fact, if you are using it, please let us know on the GitHub issues page. + */ + @ApiStatus.Internal + OmniRenderingContext getRenderingContext(); + + /** + *

don't use this method!!

+ *
if you do, I don't like you. you will probably break like, everything. + */ + @Deprecated + @ApiStatus.Internal + void __setRenderingContext(OmniRenderingContext renderingContext); + + Font getMCFont(); + /** *

don't use this method!!

*/ @@ -132,8 +159,8 @@ default PolyUI createDefault() { framebuffer.clearDepthStencil(1.0, 0); framebuffer.usingToRender((matrixStack, w, h) -> { ctx.pose().runReplacingGlobalState(() -> { + __setRenderingContext(ctx); polyUI.render(); - Platform.screen().renderLegacyHuds(ctx); }); return Unit.INSTANCE; @@ -157,9 +184,8 @@ default PolyUI createDefault() { }); }; - EventManager.register(RenderEvent.Post.class, event -> { - renderer.accept(event.ctx); - }); + // todo temporary reversion: on RenderEvent it just renders nothing (see #592, #579) + EventManager.register(HudRenderEvent.class, event -> renderer.accept(event.ctx)); EventManager.register(ResizeEvent.class, event -> { float ratio = Platform.screen().pixelRatio(); diff --git a/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/RendererExt.java b/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/RendererExt.java new file mode 100644 index 000000000..f4812bc91 --- /dev/null +++ b/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/RendererExt.java @@ -0,0 +1,5 @@ +package org.polyfrost.oneconfig.api.ui.v1.api; + +public interface RendererExt { + void dumpAtlas(); +} diff --git a/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/StbApi.java b/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/StbApi.java index 838d65894..29443a217 100644 --- a/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/StbApi.java +++ b/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/StbApi.java @@ -12,15 +12,15 @@ public interface StbApi { void image_write_png(String filename, int w, int h, int comp, ByteBuffer data, int strideInBytes); - long createFontInfo(); + long font_CreateFontInfo(); - long createPackRange(); + long font_CreatePackRange(); - long createPackedCharArray(int capacity); + long font_CreatePackedCharArray(int capacity); - long createPackContext(); + long font_CreatePackContext(); - boolean initFont(long info, ByteBuffer data); + boolean font_InitFont(long info, ByteBuffer data); float font_ScaleForMappingEmToPixels(long info, float pixels); diff --git a/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/TinyFdApi.java b/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/TinyFdApi.java index 06807735d..4a567f30d 100644 --- a/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/TinyFdApi.java +++ b/modules/ui/src/main/java/org/polyfrost/oneconfig/api/ui/v1/api/TinyFdApi.java @@ -53,11 +53,17 @@ public interface TinyFdApi { /** * Open a save file selection prompt. * Same as {@link #openFileSelector(String, String, String[], String)} but says save instead of open. + *

+ * On Linux, TinyFD "allows shell metacharacters in titles, messages, and other input data," meaning that it is vulnerable to command injection. + * **Treat all user input as untrusted and sanitize it before passing it to TinyFD.** */ Path openSaveSelector(@Nullable String title, @Nullable String defaultFilePath, String[] filterPatterns, @Nullable String filterDescription); /** * Open a file selection prompt. + *

+ * On Linux, TinyFD "allows shell metacharacters in titles, messages, and other input data," meaning that it is vulnerable to command injection. + * **Treat all user input as untrusted and sanitize it before passing it to TinyFD.** * * @param title the title of the prompt * @param defaultFilePath the path to the default file to select @@ -71,17 +77,26 @@ public interface TinyFdApi { /** * Open a multi file selection prompt. * Same as {@link #openFileSelector(String, String, String[], String)} but allows the user to select multiple files. + *

+ * On Linux, TinyFD "allows shell metacharacters in titles, messages, and other input data," meaning that it is vulnerable to command injection. + * **Treat all user input as untrusted and sanitize it before passing it to TinyFD.** */ Path[] openMultiFileSelector(@Nullable String title, @Nullable String defaultFilePath, String[] filterPatterns, @Nullable String filterDescription); /** * Open a folder selection prompt. * Same as {@link #openFileSelector(String, String, String[], String)} but allows the user to select a folder. + *

+ * On Linux, TinyFD "allows shell metacharacters in titles, messages, and other input data," meaning that it is vulnerable to command injection. + * **Treat all user input as untrusted and sanitize it before passing it to TinyFD.** */ Path openFolderSelector(@Nullable String title, @Nullable String defaultFolderPath); /** * Shows a message box. + *

+ * On Linux, TinyFD "allows shell metacharacters in titles, messages, and other input data," meaning that it is vulnerable to command injection. + * **Treat all user input as untrusted and sanitize it before passing it to TinyFD.** * * @param message the message. may contain \n and \t * @param dialog the type of message box to show.
One of: {@link #OK_DIALOG}, {@link #OK_CANCEL_DIALOG}, {@link #YES_NO_DIALOG}, {@link #YES_NO_CANCEL_DIALOG} @@ -93,6 +108,9 @@ public interface TinyFdApi { /** * Shows a notification. + *

+ * On Linux, TinyFD "allows shell metacharacters in titles, messages, and other input data," meaning that it is vulnerable to command injection. + * **Treat all user input as untrusted and sanitize it before passing it to TinyFD.** * * @param icon the icon to use. One of: {@link #QUESTION_ICON}, {@link #ERROR_ICON}, {@link #WARNING_ICON}, {@link #INFO_ICON} * @return 0 if the user clicked the "ok" button, 1 for "cancel" diff --git a/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/GLRendererImpl.kt b/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/GLRendererImpl.kt index 2e1571a83..7e2a363f2 100644 --- a/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/GLRendererImpl.kt +++ b/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/GLRendererImpl.kt @@ -1,14 +1,25 @@ +@file:Suppress("UnstableApiUsage", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + package org.polyfrost.oneconfig.api.ui.v1.internal import dev.deftu.omnicore.api.client.render.GlCapabilities +import dev.deftu.omnicore.api.client.render.OmniResolution +import dev.deftu.omnicore.api.client.render.OmniTextRenderer +import dev.deftu.omnicore.api.client.render.stack.OmniPoseStack +import dev.deftu.omnicore.api.color.OmniColor +import dev.deftu.omnicore.api.math.OmniMatrix4f import dev.deftu.omnicore.internal.client.render.shader.ShaderInternals import org.apache.logging.log4j.LogManager import org.lwjgl.BufferUtils import org.lwjgl.opengl.GL11.* +import org.lwjgl.opengl.GL13.* import org.lwjgl.opengl.GL14.* import org.lwjgl.opengl.GL15.* import org.lwjgl.opengl.GL20.* +import org.polyfrost.oneconfig.api.platform.v1.Platform +import org.polyfrost.oneconfig.api.ui.v1.UIManager import org.polyfrost.oneconfig.api.ui.v1.api.NanoSvgApi +import org.polyfrost.oneconfig.api.ui.v1.api.RendererExt import org.polyfrost.oneconfig.api.ui.v1.api.StbApi import org.polyfrost.polyui.PolyUI import org.polyfrost.polyui.color.Color @@ -22,33 +33,51 @@ import org.polyfrost.polyui.utils.toDirectByteBuffer import org.polyfrost.polyui.utils.toDirectByteBufferNT import java.nio.ByteBuffer import java.nio.FloatBuffer -import kotlin.math.cos -import kotlin.math.roundToInt -import kotlin.math.sin -import kotlin.math.tan +import kotlin.math.* private val LOGGER = LogManager.getLogger("PolyUI/GLRenderer") private const val MAX_UI_DEPTH = 16 -private const val FONT_MAX_BITMAP_W = 1024 +private const val FONT_MAX_BITMAP_W = 1536 private const val FONT_MAX_BITMAP_H = 512 private const val ATLAS_SIZE = 2048 -private const val FONT_RENDER_SIZE = 24f // 48f scales nicely to 16f, 12f, 32f, etc. private const val ATLAS_SVG_UPSCALE_FACTOR = 2f -private const val STRIDE = 4 + 4 + 4 + 4 + 4 + 1 // bounds, radii, color0, color1, UV, thick +private const val STRIDE = 4 + 4 + 1 + 1 + 4 + 1 // bounds, radii, color0, color1, UV, thick private const val MAX_BATCH = 1024 private val EMPTY_ROW = floatArrayOf(0f, 0f, 0f, 0f) private val NO_UV = floatArrayOf(-1f, -1f, 1f, 1f) +private val IDENTITY = floatArrayOf( + 1f, 0f, 0f, + 0f, 1f, 0f, + 0f, 0f, 1f +) + +private fun FloatArray.isIdentity(): Boolean { + return this[0] == 1f && this[1] == 0f && this[2] == 0f && + this[3] == 0f && this[4] == 1f && this[5] == 0f && + this[6] == 0f && this[7] == 0f && this[8] == 1f +} -@Suppress("UnstableApiUsage", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") -class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Renderer { +@kotlin.internal.InlineOnly +private inline fun FloatArray.set(other: FloatArray) { + System.arraycopy(other, 0, this, 0, 9) +} + +@kotlin.internal.InlineOnly +private inline fun FloatArray.setThenClear(other: FloatArray) { + System.arraycopy(other, 0, this, 0, 9) + System.arraycopy(IDENTITY, 0, other, 0, 9) +} + +class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Renderer, RendererExt { private val buffer = BufferUtils.createFloatBuffer(MAX_BATCH * STRIDE) - private val transformBuffer = BufferUtils.createFloatBuffer(9) private val scissorStack = IntArray(MAX_UI_DEPTH * 4) - private val transformStack = ArrayList(MAX_UI_DEPTH) - private val fonts = HashMap() + private val transformStack = Array(MAX_UI_DEPTH) { FloatArray(9) } + private val transformBuffer = BufferUtils.createFloatBuffer(9) + private val fonts = HashMap() + private val init get() = program != 0 // lateinit private var mipmapMode = 0 @@ -73,29 +102,18 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re // Current batch state + private val viewport = IntArray(4) private var count = 0 - private var curTex = 0 - private var curScissor = 0 - private var transform = floatArrayOf( - 1f, 0f, 0f, - 0f, 1f, 0f, - 0f, 0f, 1f - ) - set(value) { - field = value - transformBuffer.clear() - transformBuffer.put(value).flip() - } - private var viewportWidth = 0f - private var viewportHeight = 0f + private var transformDepth = 0 + private var scissorDepth = 0 + private var transform = IDENTITY.copyOf() private var pixelRatio = 1f - private var alphaCap = 1f + private var alphaCap = 255 private var popFlushNeeded = false + // atlas data private var slotX = 0 private var slotY = 0 - - /** current max height of the currently active row in the atlas. */ private var atlasRowHeight = 0 private val FRAG = """ @@ -128,23 +146,28 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re float py = step(0.0, p.y); // Select radius per quadrant - float rLeft = mix(r.w, r.x, py); // bottom-left / top-left - float rRight = mix(r.z, r.y, py); // bottom-right / top-right - float radius = mix(rLeft, rRight, px); + float rLeft = mix(r.x, r.w, py); // top-left / bottom-left + float rRight = mix(r.y, r.z, py); // top-right / bottom-right + float radius = mix(rLeft, rRight, px); // left vs right - vec2 d = abs(p) - b + vec2(radius); - vec2 dClamped = max(d, vec2(0.0)); - return length(dClamped) - radius + min(max(d.x, d.y), 0.0); + vec2 q = abs(p) - (b - radius); + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; } float hollowRoundedBoxSDF(vec2 p, vec2 b, vec4 r, float thickness) { - float dist = roundedBoxSDF(p, b, r); - return abs(dist) - thickness * 0.5; + float outer = roundedBoxSDF(p, b + 0.2, r + 0.2); + float inner = roundedBoxSDF(p, b - thickness, max(r - thickness, 0.0)); + return max(outer, -inner); + } + + float roundBoxSDF(vec2 p, vec2 halfSize, float radius) { + vec2 q = abs(p) - (halfSize - radius); + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; } void main() { - vec2 center = vRect.xy + 0.5 * vRect.zw; vec2 halfSize = 0.5 * vRect.zw; + vec2 center = vRect.xy + halfSize; vec2 p = vPos - center; float d = (vThickness > 0.0) ? hollowRoundedBoxSDF(p, halfSize, vRadii, vThickness) : roundedBoxSDF(p, halfSize, vRadii); @@ -156,22 +179,25 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re col = (vThickness == -1.0) ? vec4(col.rgb, col.a * texColor.r) : col * texColor; } else if (vThickness == -2.0) { // linear gradient, vUV as start and vUV2 as end - vec2 dir = normalize(vUV2 - vUV); - float t = dot((p + halfSize) - vUV, dir) / length(vUV2 - vUV); - t = clamp(t, 0.0, 1.0); + vec2 dir = vUV2 - vUV; + float len = length(dir); + float t = clamp(dot((p + halfSize) - vUV, dir / len) / len, 0.0, 1.0); col = mix(vColor0, vColor1, t); } else if (vThickness == -3.0) { // radial gradient, vUV as center and vUV2.x as radius float dist = length(p + halfSize - vUV); - float t = (dist - vUV2.x) / (vUV2.y - vUV2.x); - t = clamp(t, 0.0, 1.0); + float t = clamp((dist - vUV2.x) / (vUV2.y - vUV2.x), 0.0, 1.0); col = mix(vColor0, vColor1, t); } else if (vThickness == -4.0) { // box gradient, vUV.x as radius and vUV.y as feather - float dist = roundedBoxSDF(p, halfSize - vec2(vUV.x), vec4(vUV.x)); - float t = clamp(dist / vUV.y, 0.0, 1.0); + float dist = roundBoxSDF(p, halfSize, vUV.x); + float t = clamp((dist + vUV.y * 0.5) / vUV.y, 0.0, 1.0); col = mix(vColor0, vColor1, t); } + else if (vThickness == -5.0) { // drop shadow, vUV.x as spread and vUV.y as blur + float dShadow = roundBoxSDF(p, halfSize + vUV.x, vRadii.x); + col = vec4(vColor0.rgb, vColor0.a * (1.0 - smoothstep(-vUV.y, vUV.y, dShadow))); + } // Proper antialiasing based on distance field float f = fwidth(d); @@ -183,19 +209,22 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re private val VERT = """ #version $$$ // replaced by compileShader + #extension GL_EXT_gpu_shader4 : enable #if __VERSION__ >= 130 #define ATTRIBUTE in #define VARYING out + #define U_INT uint #else #define ATTRIBUTE attribute #define VARYING varying + #define U_INT unsigned int #endif ATTRIBUTE vec2 aLocal; ATTRIBUTE vec4 iRect; ATTRIBUTE vec4 iRadii; - ATTRIBUTE vec4 iColor0; - ATTRIBUTE vec4 iColor1; + ATTRIBUTE U_INT iColor0; + ATTRIBUTE U_INT iColor1; ATTRIBUTE vec4 iUVRect; ATTRIBUTE float iThickness; @@ -214,6 +243,15 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re VARYING vec2 vUV; VARYING vec2 vUV2; VARYING float vThickness; + + vec4 unpackColor(U_INT c) { + float a = float((c >> 24) & 0xFFu) / 255.0; + float r = float((c >> 16) & 0xFFu) / 255.0; + float g = float((c >> 8) & 0xFFu) / 255.0; + float b = float((c ) & 0xFFu) / 255.0; + + return vec4(r, g, b, a); + } void main() { // Position inside rect @@ -230,8 +268,8 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re vPos = pos; vRect = iRect; vRadii = iRadii; - vColor0 = iColor0; - vColor1 = iColor1; + vColor0 = unpackColor(iColor0); + vColor1 = unpackColor(iColor1); vUV = uv; // pass through for gradients. vUV2 = iUVRect.zw; @@ -284,11 +322,23 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re @Suppress("SameParameterValue") - private fun glUniformMatrix3fv(location: Int, transpose: Boolean, buf: FloatBuffer) { + private fun glUniformMatrix3fv(location: Int, transpose: Boolean, array: FloatArray) { + val buf = transformBuffer + buf.clear() + buf.put(array).flip() ShaderInternals.uniformMatrix3(location, transpose, buf) } + @kotlin.internal.InlineOnly + private inline fun Int.capAlpha(): Int { + val a = (this ushr 24) and 0xFF + val capped = if (a > alphaCap) alphaCap else a + return (capped shl 24) or (this and 0x00FFFFFF) + } + + override fun init() { + if (init) return // check if instancing extension is available require(GlCapabilities.isGl21Available) { "At least OpenGL 2.1 is required" } if (!GlCapabilities.isGl33Available) { // asm: skip check, both are core past 3.3 @@ -309,18 +359,19 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re } else { // gl2 check val extensions = glGetString(GL_EXTENSIONS) ?: "" + require("GL_EXT_gpu_shader4" in extensions) { "GL_EXT_gpu_shader4 is not supported and is required" } require("GL_ARB_instanced_arrays" in extensions) { "GL_ARB_instanced_arrays is not supported and is required" } require("GL_ARB_draw_instanced" in extensions) { "GL_ARB_draw_instanced is not supported and is required" } - if ("GL_EXT_framebuffer_object" in extensions) { - LOGGER.info("Using mipmaps as extension GL_EXT_framebuffer_object is available") - mipmapMode = 2 - } +// if ("GL_EXT_framebuffer_object" in extensions) { +// LOGGER.info("Using mipmaps as extension GL_EXT_framebuffer_object is available") +// mipmapMode = 2 +// } } } if (GlCapabilities.isGl3Available) { - LOGGER.info("Using mipmaps and VAOs as OpenGL 3+ is available.") - mipmapMode = 1 +// LOGGER.info("Using mipmaps and VAOs as OpenGL 3+ is available.") +// mipmapMode = 1 // ...ok I guess this is needed vao = org.lwjgl.opengl.GL30.glGenVertexArrays() org.lwjgl.opengl.GL30.glBindVertexArray(vao) @@ -328,8 +379,6 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re program = linkProgram(compileShader(GL_VERTEX_SHADER, VERT), compileShader(GL_FRAGMENT_SHADER, FRAG)) - if (GlCapabilities.isGl3Available) org.lwjgl.opengl.GL30.glBindVertexArray(0) - val prevBuf = glGetInteger(GL_ARRAY_BUFFER_BINDING) val quadData = BufferUtils.createFloatBuffer(8).put( floatArrayOf( @@ -345,7 +394,6 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re instancedVbo = glGenBuffers() glBindBuffer(GL_ARRAY_BUFFER, instancedVbo) glBufferData(GL_ARRAY_BUFFER, MAX_BATCH * STRIDE * 4L, GL_STREAM_DRAW) - glBindBuffer(GL_ARRAY_BUFFER, prevBuf) uWindow = glGetUniformLocation(program, "uWindow") uTransform = glGetUniformLocation(program, "uTransform") @@ -357,6 +405,21 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re iUVRect = glGetAttribLocation(program, "iUVRect") iThickness = glGetAttribLocation(program, "iThickness") + if (GlCapabilities.isGl3Available) { + var offset = 0L + offset = enableAttrib(iRect, 4, offset) + offset = enableAttrib(iRadii, 4, offset) + offset = enableAttrib(iColor0, 1, offset, GL_UNSIGNED_INT) + offset = enableAttrib(iColor1, 1, offset, GL_UNSIGNED_INT) + offset = enableAttrib(iUVRect, 4, offset) + enableAttrib(iThickness, 1, offset) + + glBindBuffer(GL_ARRAY_BUFFER, quadVbo) + glEnableVertexAttribArray(aLocal) + glVertexAttribPointer(aLocal, 2, GL_FLOAT, false, 0, 0L) + org.lwjgl.opengl.GL30C.glBindVertexArray(0) + } + val prevTex = glGetInteger(GL_TEXTURE_BINDING_2D) atlas = glGenTextures() glBindTexture(GL_TEXTURE_2D, atlas) @@ -371,27 +434,29 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re GL_UNSIGNED_BYTE, null as ByteBuffer? ) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, if (mipmapMode == 0) GL_LINEAR else GL_LINEAR_MIPMAP_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glBindTexture(GL_TEXTURE_2D, prevTex) + glBindBuffer(GL_ARRAY_BUFFER, prevBuf) } override fun beginFrame(width: Float, height: Float, pixelRatio: Float) { - curScissor = 0 - alphaCap = 1f + scissorDepth = 0 + alphaCap = 255 count = 0 buffer.clear() - loadIdentity() + transform.set(IDENTITY) + popFlushNeeded = false + transformDepth = 0 val prevProg = glGetInteger(GL_CURRENT_PROGRAM) glUseProgram(program) glUniform2f(uWindow, width, height) - glUniformMatrix3fv(uTransform, false, transformBuffer) + glUniformMatrix3fv(uTransform, false, transform) glUseProgram(prevProg) glDisable(GL_SCISSOR_TEST) - viewportWidth = width * pixelRatio - viewportHeight = height * pixelRatio + Platform.gl().glViewport(viewport) this.pixelRatio = pixelRatio } @@ -403,6 +468,7 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re private fun flush() { if (count == 0) return buffer.flip() + val prevActive = glGetInteger(GL_ACTIVE_TEXTURE) val prevTex = glGetInteger(GL_TEXTURE_BINDING_2D) val prevProg = glGetInteger(GL_CURRENT_PROGRAM) val prevBuf = glGetInteger(GL_ARRAY_BUFFER_BINDING) @@ -430,28 +496,31 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re glUseProgram(program) if (popFlushNeeded) { - glUniformMatrix3fv(uTransform, false, transformBuffer) + glUniformMatrix3fv(uTransform, false, transform) popFlushNeeded = false } glActiveTexture(GL_TEXTURE0) - glBindTexture(GL_TEXTURE_2D, curTex) - - // Quad attrib - glBindBuffer(GL_ARRAY_BUFFER, quadVbo) - glEnableVertexAttribArray(aLocal) - glVertexAttribPointer(aLocal, 2, GL_FLOAT, false, 0, 0L) - - // Instance attribs - glBindBuffer(GL_ARRAY_BUFFER, instancedVbo) + glBindTexture(GL_TEXTURE_2D, atlas) - var offset = 0L - offset = enableAttrib(iRect, 4, offset) - offset = enableAttrib(iRadii, 4, offset) - offset = enableAttrib(iColor0, 4, offset) - offset = enableAttrib(iColor1, 4, offset) - offset = enableAttrib(iUVRect, 4, offset) - enableAttrib(iThickness, 1, offset) + // asm: on VAO the state is stored, so we only need to set it up once + if (!GlCapabilities.isGl3Available) { + // Quad attrib + glBindBuffer(GL_ARRAY_BUFFER, quadVbo) + glEnableVertexAttribArray(aLocal) + glVertexAttribPointer(aLocal, 2, GL_FLOAT, false, 0, 0L) + + // Instance attribs + glBindBuffer(GL_ARRAY_BUFFER, instancedVbo) + + var offset = 0L + offset = enableAttrib(iRect, 4, offset) + offset = enableAttrib(iRadii, 4, offset) + offset = enableAttrib(iColor0, 1, offset, GL_UNSIGNED_INT) + offset = enableAttrib(iColor1, 1, offset, GL_UNSIGNED_INT) + offset = enableAttrib(iUVRect, 4, offset) + enableAttrib(iThickness, 1, offset) + } // Draw all instances if (GlCapabilities.isGl31Available) org.lwjgl.opengl.GL31.glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, count) @@ -464,14 +533,15 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re if (prevDepth) glEnable(GL_DEPTH_TEST) if (prevCull) glEnable(GL_CULL_FACE) glUseProgram(prevProg) + glActiveTexture(prevActive) glBindTexture(GL_TEXTURE_2D, prevTex) glBindBuffer(GL_ARRAY_BUFFER, prevBuf) if (GlCapabilities.isGl3Available) org.lwjgl.opengl.GL30.glBindVertexArray(prevVao) } - private fun enableAttrib(loc: Int, size: Int, offset: Long): Long { + private fun enableAttrib(loc: Int, size: Int, offset: Long, type: Int = GL_FLOAT): Long { glEnableVertexAttribArray(loc) - glVertexAttribPointer(loc, size, GL_FLOAT, false, STRIDE * 4, offset) + glVertexAttribPointer(loc, size, type, false, STRIDE * 4, offset) // I don't know why core disables the extension functions... but ok! if (GlCapabilities.isGl33Available) org.lwjgl.opengl.GL33.glVertexAttribDivisor(loc, 1) else org.lwjgl.opengl.ARBInstancedArrays.glVertexAttribDivisorARB(loc, 1) @@ -490,10 +560,9 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re if (count >= MAX_BATCH) flush() buffer.put(x).put(y).put(width).put(height) buffer.put(topLeftRadius).put(topRightRadius).put(bottomRightRadius).put(bottomLeftRadius) - buffer.put(color.r / 255f).put(color.g / 255f).put(color.b / 255f).put(color.alpha.coerceAtMost(alphaCap)) + buffer.put(java.lang.Float.intBitsToFloat(color.argb.capAlpha())) if (color is PolyColor.Gradient) { - buffer.put(color.color2.r / 255f).put(color.color2.g / 255f).put(color.color2.b / 255f) - .put(color.color2.alpha.coerceAtMost(alphaCap)) + buffer.put(java.lang.Float.intBitsToFloat(color.color2.argb.capAlpha())) when (val type = color.type) { is PolyColor.Gradient.Type.LeftToRight -> { buffer.put(0f).put(height / 2f).put(width).put(height / 2f) @@ -528,7 +597,7 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re } } } else { - buffer.put(EMPTY_ROW) // color1 unused + buffer.put(0f) // color1 unused buffer.put(NO_UV) // -1f UVs to indicate no texture buffer.put(0f) } @@ -551,8 +620,8 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re if (count >= MAX_BATCH) flush() buffer.put(x).put(y).put(width).put(height) buffer.put(topLeftRadius).put(topRightRadius).put(bottomRightRadius).put(bottomLeftRadius) - buffer.put(color.r / 255f).put(color.g / 255f).put(color.b / 255f).put(color.alpha.coerceAtMost(alphaCap)) - buffer.put(EMPTY_ROW) // color1 unused + buffer.put(java.lang.Float.intBitsToFloat(color.argb.capAlpha())) + buffer.put(0f) // color1 unused buffer.put(NO_UV) // -1f UVs to indicate no texture buffer.put(lineWidth) count += 1 @@ -563,34 +632,45 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re colorMask: Int, topLeftRadius: Float, topRightRadius: Float, bottomLeftRadius: Float, bottomRightRadius: Float ) { if (count >= MAX_BATCH) flush() - if (count > 0 && curTex != atlas) flush() - curTex = atlas buffer.put(x).put(y).put(width).put(height) buffer.put(topLeftRadius).put(topRightRadius).put(bottomRightRadius).put(bottomLeftRadius) - buffer.put((colorMask shr 16 and 0xFF) / 255f) - .put((colorMask shr 8 and 0xFF) / 255f) - .put((colorMask and 0xFF) / 255f) - .put(((colorMask ushr 24 and 0xFF) / 255f).coerceAtMost(alphaCap)) - buffer.put(EMPTY_ROW) // color1 unused + buffer.put(java.lang.Float.intBitsToFloat(colorMask.capAlpha())) + buffer.put(0f) // color1 unused buffer.put(image.uv.x).put(image.uv.y).put(image.uv.w).put(image.uv.h) buffer.put(0f) // thickness = 0 for filled rect count += 1 } + private fun FloatArray.getScaledMat4(): OmniMatrix4f { + // asm: scale to MC instance coordinates and mutate to a 4x4 matrix + val sf = pixelRatio / OmniResolution.scaleFactor.toFloat() + return OmniMatrix4f.from(floatArrayOf( + this[0] * sf, this[1] * sf, 0f, 0f, + this[3] * sf, this[4] * sf, 0f, 0f, + 0f, 0f, 1f, 0f, + this[6] * sf, this[7] * sf, 0f, 1f + )) + } + override fun text(font: Font, x: Float, y: Float, text: String, color: Color, fontSize: Float) { - val fAtlas = getFontAtlas(font) + if (font === UIManager.INSTANCE.mcFont) { + val ctx = UIManager.INSTANCE.renderingContext + ctx.pose.push(OmniPoseStack.Entry(transform.getScaledMat4(), ctx.pose.current.normalMatrix)) + // asm: can be optimized by https://github.com/Deftu/OmniCore/issues/58 + OmniTextRenderer.render(ctx.pose, text, x, y, OmniColor.argb(color.argb), false) + ctx.pose.pop() + return + } + val fAtlas = getFontAtlas(font, fontSize) + val s = transformScale() + val fAtlasForRendering = if (s == 1f) fAtlas else getFontAtlas(font, fontSize * s) if (count >= MAX_BATCH) flush() - if (count > 0 && curTex != atlas) flush() - curTex = atlas var penX = x val scaleFactor = fontSize / fAtlas.renderedSize val penY = y + (fAtlas.ascent + fAtlas.descent) * scaleFactor - val r = (color.r / 255f) - val g = (color.g / 255f) - val b = (color.b / 255f) - val a = (color.alpha.coerceAtMost(alphaCap)) + val col = java.lang.Float.intBitsToFloat(color.argb.capAlpha()) val buffer = buffer for (c in text) { @@ -601,9 +681,9 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re buffer.put(penX + glyph.xOff * scaleFactor).put(penY + glyph.yOff * scaleFactor) .put(glyph.width * scaleFactor).put(glyph.height * scaleFactor) buffer.put(EMPTY_ROW) // zero radii - buffer.put(r).put(g).put(b).put(a) - buffer.put(EMPTY_ROW) // color1 unused - buffer.put(glyph.u).put(glyph.v).put(glyph.uw).put(glyph.vh) + buffer.put(col) + buffer.put(0f) // color1 unused + buffer.put(if (s == 1f) glyph else fAtlasForRendering.get(c), 0, 4) buffer.put(-1f) // thickness = -1 for text penX += glyph.xAdvance * scaleFactor count += 1 @@ -611,7 +691,9 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re } override fun textBounds(font: Font, text: String, fontSize: Float): Vec2 { - return getFontAtlas(font).measure(text, fontSize) + return if (font === UIManager.INSTANCE.mcFont) { + Vec2(OmniTextRenderer.width(text).toFloat(), OmniTextRenderer.lineHeight.toFloat()) + } else getFontAtlas(font, fontSize).measure(text, fontSize) } override fun line(x1: Float, y1: Float, x2: Float, y2: Float, color: Color, width: Float) { @@ -628,68 +710,75 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re spread: Float, radius: Float ) { - // rect(x, y, width, height, ) + if (count >= MAX_BATCH) flush() + buffer.put(x).put(y).put(width).put(height) + buffer.put(EMPTY_ROW) // zero radii + buffer.put(java.lang.Float.intBitsToFloat(alphaCap shl 24)) // black, alpha to alphaCap + buffer.put(0f) // color1 unused + buffer.put(spread).put(blur).put(0f).put(0f) + buffer.put(-5f) // thickness = -5 for drop shadow + count += 1 } override fun pushScissor(x: Float, y: Float, width: Float, height: Float) { flush() val nx = (x * pixelRatio).roundToInt() - val ny = (viewportHeight - (y + height) * pixelRatio).roundToInt() + val ny = (viewport[3] - (y + height) * pixelRatio).roundToInt() val nw = (width * pixelRatio).roundToInt() val nh = (height * pixelRatio).roundToInt() - scissorStack[curScissor++] = nx - scissorStack[curScissor++] = ny - scissorStack[curScissor++] = nw - scissorStack[curScissor++] = nh + scissorStack[scissorDepth++] = nx + scissorStack[scissorDepth++] = ny + scissorStack[scissorDepth++] = nw + scissorStack[scissorDepth++] = nh glEnable(GL_SCISSOR_TEST) - glScissor(nx, ny, nw, nh) + glScissor(nx + viewport[0], ny + viewport[1], nw, nh) } override fun pushScissorIntersecting(x: Float, y: Float, width: Float, height: Float) { - if (curScissor < 4) { + if (scissorDepth < 4) { pushScissor(x, y, width, height) return } flush() - val px = scissorStack[curScissor - 4] - val py = scissorStack[curScissor - 3] - val pw = scissorStack[curScissor - 2] - val ph = scissorStack[curScissor - 1] + val px = scissorStack[scissorDepth - 4] + val py = scissorStack[scissorDepth - 3] + val pw = scissorStack[scissorDepth - 2] + val ph = scissorStack[scissorDepth - 1] val nx = (x * pixelRatio).roundToInt() - val ny = (viewportHeight - (y + height) * pixelRatio).roundToInt() + val ny = (viewport[3] - (y + height) * pixelRatio).roundToInt() val ix = maxOf(nx, px) val iy = maxOf(ny, py) val iw = maxOf(0, minOf(nx + (width * pixelRatio).roundToInt(), px + pw) - ix) val ih = maxOf(0, minOf(ny + (height * pixelRatio).roundToInt(), py + ph) - iy) - scissorStack[curScissor++] = ix - scissorStack[curScissor++] = iy - scissorStack[curScissor++] = iw - scissorStack[curScissor++] = ih + scissorStack[scissorDepth++] = ix + scissorStack[scissorDepth++] = iy + scissorStack[scissorDepth++] = iw + scissorStack[scissorDepth++] = ih glEnable(GL_SCISSOR_TEST) - glScissor(ix, iy, iw, ih) + glScissor(ix + viewport[0], iy + viewport[1], iw, ih) } override fun popScissor() { flush() - if (curScissor <= 4) { - curScissor = 0 + if (scissorDepth <= 4) { + scissorDepth = 0 glDisable(GL_SCISSOR_TEST) return } - curScissor -= 4 - val x = scissorStack[curScissor - 4] - val y = scissorStack[curScissor - 3] - val width = scissorStack[curScissor - 2] - val height = scissorStack[curScissor - 1] + scissorDepth -= 4 + val nx = scissorStack[scissorDepth - 4] + val ny = scissorStack[scissorDepth - 3] + val nw = scissorStack[scissorDepth - 2] + val nh = scissorStack[scissorDepth - 1] glEnable(GL_SCISSOR_TEST) - glScissor(x, y, width, height) + glScissor(nx + viewport[0], ny + viewport[1], nw, nh) } override fun globalAlpha(alpha: Float) { - alphaCap = alpha + alphaCap = (alpha * 255f).toInt() } override fun resetGlobalAlpha() = globalAlpha(1f) @@ -698,18 +787,34 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re override fun push() { if (transform.isIdentity()) return - transformStack.add(transform.copyOf()) + transformStack[transformDepth++].set(transform) } override fun pop() { if (transform.isIdentity()) return flush() - if (transformStack.isEmpty()) { - loadIdentity() - } else transform = transformStack.removeLast() + if (transformDepth == 0) { + transform.set(IDENTITY) + } else { + transform.setThenClear(transformStack[--transformDepth]) + } popFlushNeeded = true } + private fun transformScale(): Float { + val a = transform[0] + val c = transform[1] + val b = transform[3] + val d = transform[4] + // Fast-path: identity (no rotation, no scale, no shear) + if (a == 1f && d == 1f && c == 0f && b == 0f) { + return 1f + } + val sx = sqrt(a * a + b * b) + val sy = sqrt(c * c + d * d) + return (sx + sy) * 0.5f + } + override fun translate(x: Float, y: Float) { flush() transform[6] += transform[0] * x + transform[3] * y @@ -764,20 +869,6 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re popFlushNeeded = true } - private fun FloatArray.isIdentity(): Boolean { - return this[0] == 1f && this[1] == 0f && this[2] == 0f && - this[3] == 0f && this[4] == 1f && this[5] == 0f && - this[6] == 0f && this[7] == 0f && this[8] == 1f - } - - private fun loadIdentity() { - val transform = transform - transform[0] = 1f; transform[1] = 0f; transform[2] = 0f - transform[3] = 0f; transform[4] = 1f; transform[5] = 0f - transform[6] = 0f; transform[7] = 0f; transform[8] = 1f - this.transform = transform - } - override fun initImage(image: PolyImage, size: Vec2) { if (image.initialized) return val w = IntArray(1) @@ -801,7 +892,9 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re h[0] / ATLAS_SIZE.toFloat() ) + val prevActive = glGetInteger(GL_ACTIVE_TEXTURE) val prevTex = glGetInteger(GL_TEXTURE_BINDING_2D) + glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, atlas) glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glPixelStorei(GL_UNPACK_ROW_LENGTH, 0) @@ -813,10 +906,12 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re 2 -> org.lwjgl.opengl.EXTFramebufferObject.glGenerateMipmapEXT(GL_TEXTURE_2D) } glBindTexture(GL_TEXTURE_2D, prevTex) + glActiveTexture(prevActive) + if (image.type == PolyImage.Type.Raster) stb.image_free(d) - slotX += w[0] - if (h[0] > atlasRowHeight) atlasRowHeight = h[0] + slotX += w[0] + 1 + if (h[0] + 1 > atlasRowHeight) atlasRowHeight = h[0] + 1 image.reportInit() } @@ -840,30 +935,36 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re } } - private fun getFontAtlas(font: Font): FontAtlas { - return fonts.getOrPut(font) { + private fun getFontAtlas(font: Font, fontSize: Float): FontAtlas { + val renderSize = when (fontSize) { + in 0f..36f -> 24f + else -> 48f + } + return fonts.getOrPut(font.resourcePath.hashCode() + renderSize.toInt()) { val data = font.load { LOGGER.error("Failed to load font: $font", it) - return@getOrPut fonts[PolyUI.defaultFonts.regular] + return@getOrPut fonts[PolyUI.defaultFonts.regular.resourcePath.hashCode() + 12f.toInt()] ?: throw IllegalStateException("Default font couldn't be loaded") }.toDirectByteBuffer() - FontAtlas(data, FONT_RENDER_SIZE) + FontAtlas(data, renderSize) } } - fun dumpTexture(texId: Int = atlas) { + private var i = 0 + override fun dumpAtlas() { val buf = BufferUtils.createByteBuffer(ATLAS_SIZE * ATLAS_SIZE * 4) - glBindTexture(GL_TEXTURE_2D, texId) + glBindTexture(GL_TEXTURE_2D, atlas) glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf) glBindTexture(GL_TEXTURE_2D, 0) stb.image_write_png( - "debug_texture$texId.png", + "debug_atlas$i.png", ATLAS_SIZE, ATLAS_SIZE, 4, buf, ATLAS_SIZE * 4 ) + i++ } override fun cleanup() { @@ -872,9 +973,8 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re if (quadVbo != 0) glDeleteBuffers(quadVbo) if (instancedVbo != 0) glDeleteBuffers(instancedVbo) if (atlas != 0) glDeleteTextures(atlas) - transformStack.clear() + if (vao != 0) org.lwjgl.opengl.GL30C.glDeleteVertexArrays(vao) fonts.clear() - buffer.clear() } override fun delete(font: Font?) {} @@ -891,8 +991,9 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re val lineGap: Float init { - val fontInfo = stb.createFontInfo() - if (!stb.initFont(fontInfo, data)) { + val stb = stb + val fontInfo = stb.font_CreateFontInfo() + if (!stb.font_InitFont(fontInfo, data)) { throw IllegalStateException("Failed to initialize font") } val scale = stb.font_ScaleForMappingEmToPixels(fontInfo, renderedSize) @@ -906,15 +1007,15 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re descent = des[0] * scale lineGap = gap[0] * scale - val range = stb.createPackRange() + val range = stb.font_CreatePackRange() stb.font_RangeSetFontSize(range, renderedSize) stb.font_RangeSetFirstUnicodeCodepointInRange(range, 32) stb.font_RangeSetNumChars(range, 95) - val packed = stb.createPackedCharArray(95) + val packed = stb.font_CreatePackedCharArray(95) stb.font_RangeSetChardata(range, packed) val bitMap = BufferUtils.createByteBuffer(FONT_MAX_BITMAP_W * FONT_MAX_BITMAP_H) - val pack = stb.createPackContext() + val pack = stb.font_CreatePackContext() if (!stb.font_PackBegin(pack, bitMap, FONT_MAX_BITMAP_W, FONT_MAX_BITMAP_H, 0, 1, 0L)) { throw IllegalStateException("Failed to initialize font packer") } @@ -974,7 +1075,9 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re stb.free(packed) stb.free(range) + val prevActive = glGetInteger(GL_ACTIVE_TEXTURE) val prevTex = glGetInteger(GL_TEXTURE_BINDING_2D) + glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, atlas) glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glPixelStorei(GL_UNPACK_ROW_LENGTH, 0) @@ -992,13 +1095,15 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re GL_UNSIGNED_BYTE, bitMap ) - glBindTexture(GL_TEXTURE_2D, prevTex) when (mipmapMode) { 1 -> org.lwjgl.opengl.GL30.glGenerateMipmap(GL_TEXTURE_2D) 2 -> org.lwjgl.opengl.EXTFramebufferObject.glGenerateMipmapEXT(GL_TEXTURE_2D) } - slotX += totalSizeX - atlasRowHeight = maxOf(atlasRowHeight, totalSizeY) + glBindTexture(GL_TEXTURE_2D, prevTex) + glActiveTexture(prevActive) + + slotX += totalSizeX + 1 + atlasRowHeight = maxOf(atlasRowHeight, totalSizeY + 1) } fun measure(text: String, fontSize: Float): Vec2 { @@ -1006,7 +1111,8 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re // var height = 0f val scaleFactor = fontSize / this.renderedSize for (c in text) { - width += get(c).xAdvance * scaleFactor + val g = get(c) + width += g.xAdvance * scaleFactor // height = maxOf(height, g.height + g.offsetY) } return Vec2.of(width, fontSize) @@ -1014,7 +1120,7 @@ class GLRendererImpl(private val nsvg: NanoSvgApi, private val stb: StbApi) : Re @Suppress("DEPRECATION") @kotlin.internal.InlineOnly - inline fun get(char: Char) = glyphs[(char.toInt() - 32) /* .coerceIn(0, glyphs.size - 1) */] + inline fun get(char: Char) = if (char.toInt() in 32..32 + 95) glyphs[(char.toInt() - 32)] else glyphs['?'.toInt()] } @kotlin.internal.InlineOnly diff --git a/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/NVGRendererImpl.kt b/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/NVGRendererImpl.kt index 0dacb64c2..52a928dda 100644 --- a/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/NVGRendererImpl.kt +++ b/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/NVGRendererImpl.kt @@ -30,7 +30,6 @@ import dev.deftu.omnicore.api.client.render.OmniTextureUnit import dev.deftu.omnicore.api.paddedMinecraftVersion import dev.deftu.omnicore.internal.client.textures.TextureInternals import org.apache.logging.log4j.LogManager -import org.lwjgl.nanovg.NanoVG import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL20 import org.lwjgl.opengl.GL30 @@ -176,7 +175,7 @@ class NVGRendererImpl( GL11.glPopAttrib() } - Platform.gl().updateGameRenderStateAlongsideNanoVG() + Platform.gl().syncOpenGLContext() if (mcVersion >= 1_16_05) { if (prevProgram != -1) { diff --git a/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/StbImpl.kt b/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/StbImpl.kt index a37379759..a3a698224 100644 --- a/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/StbImpl.kt +++ b/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/internal/StbImpl.kt @@ -27,15 +27,15 @@ class StbImpl : StbApi { STBImageWrite.stbi_write_png(filename, w, h, comp, data, strideInBytes) } - override fun createFontInfo() = STBTTFontinfo.malloc().address() + override fun font_CreateFontInfo() = STBTTFontinfo.malloc().address() - override fun createPackRange() = STBTTPackRange.malloc().address() + override fun font_CreatePackRange() = STBTTPackRange.malloc().address() - override fun createPackedCharArray(capacity: Int) = STBTTPackedchar.malloc(capacity).address() + override fun font_CreatePackedCharArray(capacity: Int) = STBTTPackedchar.malloc(capacity).address() - override fun createPackContext() = STBTTPackContext.malloc().address() + override fun font_CreatePackContext() = STBTTPackContext.malloc().address() - override fun initFont(info: Long, data: ByteBuffer) = nstbtt_InitFont(info, MemoryUtil.memAddress(data), 0) != 0 + override fun font_InitFont(info: Long, data: ByteBuffer) = nstbtt_InitFont(info, MemoryUtil.memAddress(data), 0) != 0 override fun font_ScaleForMappingEmToPixels(info: Long, pixels: Float) = nstbtt_ScaleForMappingEmToPixels(info, pixels) diff --git a/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/keybind/KeybindManager.kt b/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/keybind/KeybindManager.kt index 82e7690dc..ad68d505c 100644 --- a/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/keybind/KeybindManager.kt +++ b/modules/ui/src/main/kotlin/org/polyfrost/oneconfig/api/ui/v1/keybind/KeybindManager.kt @@ -87,16 +87,17 @@ object KeybindManager { try { if (char.isValid() && inputManager.mods <= 2) { // asm: only allow no mod or shift mod for char input if (down) inputManager.keyTyped(char.code) + return } val mod = when (keyCode) { OmniKeys.KEY_LEFT_SHIFT.code -> KeyModifiers.LSHIFT OmniKeys.KEY_LEFT_CONTROL.code -> if (PolyUI.isOnMac) KeyModifiers.LMETA else KeyModifiers.LPRIMARY -// OmniKeys.KEY_LMENU -> KeyModifiers.LSECONDARY -// OmniKeys.KEY_LMETA -> if (PolyUI.isOnMac) KeyModifiers.LPRIMARY else KeyModifiers.LSECONDARY + OmniKeys.KEY_LEFT_ALT.code -> KeyModifiers.LSECONDARY + OmniKeys.KEY_LEFT_SUPER.code -> if (PolyUI.isOnMac) KeyModifiers.LPRIMARY else KeyModifiers.LSECONDARY OmniKeys.KEY_RIGHT_SHIFT.code -> KeyModifiers.RSHIFT OmniKeys.KEY_RIGHT_CONTROL.code -> if (PolyUI.isOnMac) KeyModifiers.RMETA else KeyModifiers.RPRIMARY -// OmniKeys.KEY_RIGHT_MENU -> KeyModifiers.RSECONDARY -// OmniKeys.KEY_RIGHT_META -> if (PolyUI.isOnMac) KeyModifiers.RPRIMARY else KeyModifiers.RSECONDARY + OmniKeys.KEY_RIGHT_ALT.code -> KeyModifiers.RSECONDARY + OmniKeys.KEY_RIGHT_SUPER.code -> if (PolyUI.isOnMac) KeyModifiers.RPRIMARY else KeyModifiers.RSECONDARY else -> null } diff --git a/modules/utils/api/utils.api b/modules/utils/api/utils.api index 94d93d681..cdcf7f51e 100644 --- a/modules/utils/api/utils.api +++ b/modules/utils/api/utils.api @@ -53,7 +53,9 @@ public abstract interface class org/polyfrost/oneconfig/api/platform/v1/Compatib public abstract interface class org/polyfrost/oneconfig/api/platform/v1/GLPlatform { public abstract fun getFunctionAddress (Ljava/lang/String;)J - public abstract fun updateGameRenderStateAlongsideNanoVG ()V + public fun glViewport ()[I + public abstract fun glViewport ([I)[I + public abstract fun syncOpenGLContext ()V } public abstract interface class org/polyfrost/oneconfig/api/platform/v1/I18nPlatform { @@ -79,7 +81,6 @@ public abstract interface class org/polyfrost/oneconfig/api/platform/v1/ScreenPl public fun display (Ljava/lang/Object;)V public abstract fun display (Ljava/lang/Object;I)V public fun pixelRatio ()F - public abstract fun renderLegacyHuds (Ldev/deftu/omnicore/api/client/render/OmniRenderingContext;)V public abstract fun viewportHeight ()I public abstract fun viewportWidth ()I public abstract fun windowHeight ()I diff --git a/modules/utils/src/main/java/org/polyfrost/oneconfig/api/platform/v1/GLPlatform.java b/modules/utils/src/main/java/org/polyfrost/oneconfig/api/platform/v1/GLPlatform.java index 7070d6478..f5b168d12 100644 --- a/modules/utils/src/main/java/org/polyfrost/oneconfig/api/platform/v1/GLPlatform.java +++ b/modules/utils/src/main/java/org/polyfrost/oneconfig/api/platform/v1/GLPlatform.java @@ -26,6 +26,9 @@ package org.polyfrost.oneconfig.api.platform.v1; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + public interface GLPlatform { /** @@ -36,6 +39,16 @@ public interface GLPlatform { */ long getFunctionAddress(String addr); - void updateGameRenderStateAlongsideNanoVG(); + void syncOpenGLContext(); + + /** + * Return the result of glGetInteger(GL_VIEWPORT). + * If in is supplied, it will be set to the result and returned. If not, a new array will be created. + */ + int @NotNull [] glViewport(int @Nullable [] in); + + default int @NotNull [] glViewport() { + return glViewport(null); + } } diff --git a/modules/utils/src/main/java/org/polyfrost/oneconfig/api/platform/v1/ScreenPlatform.java b/modules/utils/src/main/java/org/polyfrost/oneconfig/api/platform/v1/ScreenPlatform.java index f0c210dff..240b8d0eb 100644 --- a/modules/utils/src/main/java/org/polyfrost/oneconfig/api/platform/v1/ScreenPlatform.java +++ b/modules/utils/src/main/java/org/polyfrost/oneconfig/api/platform/v1/ScreenPlatform.java @@ -26,7 +26,6 @@ package org.polyfrost.oneconfig.api.platform.v1; -import dev.deftu.omnicore.api.client.render.OmniRenderingContext; import org.jetbrains.annotations.Nullable; public interface ScreenPlatform { @@ -39,8 +38,6 @@ public interface ScreenPlatform { int windowHeight(); - void renderLegacyHuds(OmniRenderingContext ctx); - default float pixelRatio() { return (float) viewportWidth() / windowWidth(); } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2200de399..8fb912204 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.2.10") - id("dev.deftu.gradle.multiversion-root") version("2.62.0") // Update in libs.versions.toml too!!! + id("dev.deftu.gradle.multiversion-root") version("2.69.0") // Update in libs.versions.toml too!!! } } @@ -110,6 +110,9 @@ listOf( "1.21.10-neoforge", "1.21.10-fabric", + + "1.21.11-neoforge", + "1.21.11-fabric", ).forEach { version -> val proj = ":minecraft:$version" include(proj) @@ -125,7 +128,9 @@ listOf( "1.21.1-fabric", "1.21.4-fabric", "1.21.5-fabric", - "1.21.8-fabric" + "1.21.8-fabric", +// "1.21.10-fabric", +// "1.21.11-fabric", ).contains(version)) { include(bootstrapProj) project(bootstrapProj).apply {