diff --git a/build.gradle b/build.gradle index b6f4027..a9b435e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.10-SNAPSHOT' + id 'net.fabricmc.fabric-loom-remap' version "${loom_version}" id 'maven-publish' } @@ -13,8 +13,6 @@ base { allprojects { apply plugin: "java" apply plugin: "maven-publish" - - archivesBaseName = rootProject.archives_base_name } repositories { @@ -33,11 +31,14 @@ repositories { dependencies { // To change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + mappings loom.officialMojangMappings() modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "dev.isxander:yet-another-config-lib:${project.yacl_version}" + + //fuzzyscore + implementation 'org.apache.commons:commons-text:1.12.0' } processResources { @@ -64,7 +65,6 @@ java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 - archivesBaseName = project.archives_base_name // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task // if it is present. // If you remove this line, sources will not be generated. diff --git a/gradle.properties b/gradle.properties index d78b830..d4c996a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,18 +2,21 @@ org.gradle.jvmargs=-Xmx1G org.gradle.parallel=true +# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349 +org.gradle.configuration-cache=false + # Fabric Properties # check these on https://fabricmc.net/develop -minecraft_version=1.21.5 -yarn_mappings=1.21.5+build.1 -loader_version=0.16.10 +minecraft_version=1.21.11 +loader_version=0.18.2 +loom_version=1.14-SNAPSHOT # Mod Properties # need versioning system -mod_version = 3.1.0 +mod_version = 4.0.0 maven_group = com.tanishisherewith archives_base_name = dynamichud # Dependencies -fabric_version=0.119.5+1.21.5 -yacl_version=3.6.6+1.21.5-fabric \ No newline at end of file +fabric_version=0.139.4+1.21.11 +yacl_version=3.8.2+1.21.11-fabric \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9bf7bd3..d205b54 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java b/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java index 7021a07..480605e 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java +++ b/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java @@ -1,25 +1,41 @@ package com.tanishisherewith.dynamichud; +import com.mojang.blaze3d.platform.InputConstants; import com.tanishisherewith.dynamichud.config.GlobalConfig; import com.tanishisherewith.dynamichud.helpers.MouseColorQuery; import com.tanishisherewith.dynamichud.integration.IntegrationManager; +import com.tanishisherewith.dynamichud.widget.WidgetRenderer; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.client.rendering.v1.HudLayerRegistrationCallback; -import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; -import net.minecraft.client.MinecraftClient; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry; +import net.fabricmc.fabric.api.client.rendering.v1.hud.VanillaHudElements; +import net.fabricmc.fabric.impl.client.rendering.hud.HudElementRegistryImpl; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.rendertype.RenderType; +import net.minecraft.client.renderer.rendertype.RenderTypes; +import net.minecraft.resources.Identifier; +import org.lwjgl.glfw.GLFW; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Environment(EnvType.CLIENT) public class DynamicHUD implements ClientModInitializer { - public static MinecraftClient MC = MinecraftClient.getInstance(); + public static Minecraft MC = Minecraft.getInstance(); public static final Logger logger = LoggerFactory.getLogger("DynamicHud"); public static String MOD_ID = "dynamichud"; + static KeyMapping EDITOR_SCREEN_KEYBIND = KeyBindingHelper.registerKeyBinding(new KeyMapping( + "DynamicHud Editor Screen", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_RIGHT_SHIFT, + KeyMapping.Category.register(Identifier.fromNamespaceAndPath("dynamichud", "editor_screen")) + )); + public static void printInfo(String msg) { logger.info(msg); } @@ -28,6 +44,7 @@ public static void printWarn(String msg) { logger.warn(msg); } + @Override public void onInitializeClient() { printInfo("Initialising DynamicHUD"); @@ -35,10 +52,32 @@ public void onInitializeClient() { //YACL load GlobalConfig.HANDLER.load(); - IntegrationManager.integrate(); + ClientLifecycleEvents.CLIENT_STARTED.register((minecraft)-> IntegrationManager.integrate()); + //In game screen render. - HudLayerRegistrationCallback.EVENT.register(new HudRender()); + /* + * Using the fabric event {@link HudElementRegistry} to render widgets in the game HUD. + * Mouse positions are passed in the negatives even though theoretically it's in the centre of the screen. + */ + HudElementRegistryImpl.attachElementBefore(VanillaHudElements.MISC_OVERLAYS, + Identifier.fromNamespaceAndPath("dynamichud","hudrender_callback"), + (graphics, tickCounter) -> { + for (WidgetRenderer widgetRenderer : IntegrationManager.getWidgetRenderers()) { + widgetRenderer.renderWidgets(graphics, -120, -120); + } + }); + ClientTickEvents.END_CLIENT_TICK.register(mc-> MouseColorQuery.processIfPending()); } + + /** + * Get applied scale on all widgets, modified by YACL. + *
+ * From {@link GlobalConfig#getScale()} + * @return scale in float + */ + public static float getGlobalScale() { + return GlobalConfig.get().getScale(); + } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/HudRender.java b/src/main/java/com/tanishisherewith/dynamichud/HudRender.java deleted file mode 100644 index f7abd75..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/HudRender.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.tanishisherewith.dynamichud; - -import com.tanishisherewith.dynamichud.integration.IntegrationManager; -import com.tanishisherewith.dynamichud.widget.WidgetRenderer; -import net.fabricmc.fabric.api.client.rendering.v1.HudLayerRegistrationCallback; -import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; -import net.fabricmc.fabric.api.client.rendering.v1.IdentifiedLayer; -import net.fabricmc.fabric.api.client.rendering.v1.LayeredDrawerWrapper; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.RenderTickCounter; -import net.minecraft.util.Identifier; - -/** - * Using the fabric event {@link HudLayerRegistrationCallback} to render widgets in the game HUD. - * Mouse positions are passed in the negatives even though theoretically it's in the centre of the screen. - */ -public class HudRender implements HudLayerRegistrationCallback { - @Override - public void register(LayeredDrawerWrapper layeredDrawer) { - layeredDrawer.attachLayerAfter( - IdentifiedLayer.MISC_OVERLAYS, - IdentifiedLayer.of(Identifier.of("dynamichud","hudrender_callback"), - (context, tickCounter) -> { - for (WidgetRenderer widgetRenderer : IntegrationManager.getWidgetRenderers()) { - widgetRenderer.renderWidgets(context, -120, -120); - } - }) - ); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java b/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java index e6c010c..98b173c 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java +++ b/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java @@ -1,5 +1,6 @@ package com.tanishisherewith.dynamichud; +import com.mojang.blaze3d.platform.InputConstants; import com.tanishisherewith.dynamichud.integration.DynamicHudConfigurator; import com.tanishisherewith.dynamichud.integration.DynamicHudIntegration; import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; @@ -7,8 +8,12 @@ import com.tanishisherewith.dynamichud.widget.Widget; import com.tanishisherewith.dynamichud.widgets.GraphWidget; import com.tanishisherewith.dynamichud.widgets.TextWidget; -import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.text.Text; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import org.lwjgl.glfw.GLFW; import java.awt.*; @@ -23,13 +28,13 @@ public class IntegrationTest implements DynamicHudIntegration { public void init() { //Global registry // We recommend using the syntax "modid:key_name" for easier debugging and to prevent data conflicts in global registries. - DynamicValueRegistry.registerGlobal("dynamichud:FPS", () -> "FPS: " + DynamicHUD.MC.getCurrentFps()); + DynamicValueRegistry.registerGlobal("dynamichud:FPS", () -> "FPS: " + DynamicHUD.MC.getFps()); //Local registry registry = new DynamicValueRegistry(DynamicHUD.MOD_ID); - registry.registerLocal("Hello", () -> "Hello " + DynamicHUD.MC.getSession().getUsername() + "!"); + registry.registerLocal("Hello", () -> "Hello " + DynamicHUD.MC.getGameProfile().name() + "!"); registry.registerLocal("DynamicHUD", () -> "DynamicHUD"); - registry.registerLocal("FPS", () -> DynamicHUD.MC.getCurrentFps()); + registry.registerLocal("FPS", () -> DynamicHUD.MC.getFps()); FPSWidget = new TextWidget.Builder() .setX(250) @@ -69,22 +74,24 @@ public void init() { .setY(100) .label("FPS Chart") .graphColor(Color.CYAN) - .anchor(Widget.Anchor.CENTER) - .height(100) - .width(150) + .anchor(Widget.Anchor._default()) + .gWidth(100) + .gHeight(60) .gridLines(10) - .backgroundColor(Color.DARK_GRAY) - .lineThickness(1f) + .backgroundColor(Color.BLACK) + .lineThickness(0.6f) .maxDataPoints(100) .maxValue(120) .minValue(30) .setModID(DynamicHUD.MOD_ID) .setDraggable(true) - .setDisplay(true) + .setIsVisible(true) .showGrid(true) + .shouldScale(true) .registryKey("FPS") .registryID(registry.getId()) .build() + .setSampleInterval(120) .autoUpdateRange(); } @@ -99,7 +106,7 @@ public DynamicHudConfigurator configure(DynamicHudConfigurator configurator) { //renderer.shouldRenderInGameHud(true); renderer.addScreen(TitleScreen.class); }) - .withMoveableScreen(config -> new AbstractMoveableScreen(Text.literal("Editor Screen"), config.getRenderer()) {}); + .withMoveableScreen(config -> new AbstractMoveableScreen(Component.literal("Editor Screen"), config.getRenderer()) {}); return configurator; } @@ -108,4 +115,9 @@ public DynamicHudConfigurator configure(DynamicHudConfigurator configurator) { public void registerCustomWidgets() { //WidgetManager.addWidgetData(MyWidget.DATA); } + + @Override + public KeyMapping getKeyBind() { + return DynamicHUD.EDITOR_SCREEN_KEYBIND; + } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java b/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java index e832380..23240c8 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java +++ b/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java @@ -6,16 +6,16 @@ import dev.isxander.yacl3.config.v2.api.SerialEntry; import dev.isxander.yacl3.config.v2.api.serializer.GsonConfigSerializerBuilder; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; import java.awt.*; public final class GlobalConfig { public static final ConfigClassHandler HANDLER = ConfigClassHandler.createBuilder(GlobalConfig.class) - .id(Identifier.of("dynamichud", "dynamichud_config")) + .id(Identifier.fromNamespaceAndPath("dynamichud", "dynamichud_config")) .serializer(config -> GsonConfigSerializerBuilder.create(config) .setPath(FabricLoader.getInstance().getConfigDir().resolve("dynamichud.json5")) .setJson5(true) @@ -29,6 +29,9 @@ public final class GlobalConfig { @SerialEntry private float scale = 1.0f; + @SerialEntry + private int cmAnimationTimeInMs = 200; + @SerialEntry private boolean displayDescriptions = false; @@ -38,6 +41,9 @@ public final class GlobalConfig { @SerialEntry private boolean renderInDebugScreen = false; + @SerialEntry + private boolean smartSnapping = true; + @SerialEntry private final boolean forceSameContextMenuSkin = true; @@ -60,69 +66,81 @@ public static GlobalConfig get() { public Screen createYACLGUI() { return YetAnotherConfigLib.createBuilder() - .title(Text.literal("DynamicHUD config screen.")) + .title(Component.literal("DynamicHUD config screen.")) .category(ConfigCategory.createBuilder() - .name(Text.literal("General")) - .tooltip(Text.literal("Set the general settings for all widgets.")) + .name(Component.literal("General")) + .tooltip(Component.literal("Set the general settings for all widgets.")) .group(OptionGroup.createBuilder() - .name(Text.literal("Global")) - .description(OptionDescription.of(Text.literal("Global settings for all widgets."))) + .name(Component.literal("Global")) + .description(OptionDescription.of(Component.literal("Global settings for all widgets."))) .option(Option.createBuilder() - .name(Text.literal("Scale")) - .description(OptionDescription.of(Text.literal("Set scale for all widgets."))) + .name(Component.literal("Scale")) + .description(OptionDescription.of(Component.literal("Set scale for all widgets."))) .binding(1.0f, () -> this.scale, newVal -> this.scale = newVal) .controller(floatOption -> FloatSliderControllerBuilder.create(floatOption).range(0.1f, 2.5f).step(0.1f)) .build()) .option(Option.createBuilder() - .name(Text.literal("Render in debug screen")) - .description(OptionDescription.of(Text.literal("Renders widgets even when the debug screen is on"))) - .binding(true, () -> this.renderInDebugScreen, newVal -> this.renderInDebugScreen = newVal) + .name(Component.literal("Render in debug screen")) + .description(OptionDescription.of(Component.literal("Renders widgets even when the debug screen is on"))) + .binding(false, () -> this.renderInDebugScreen, newVal -> this.renderInDebugScreen = newVal) .controller(booleanOption -> BooleanControllerBuilder.create(booleanOption).yesNoFormatter()) .build()) .option(Option.createBuilder() - .name(Text.literal("Show Color picker preview")) - .description(OptionDescription.of(Text.literal("Shows the preview below your mouse pointer on selecting color from the screen. Note: You may drop some frames with the preview on."))) + .name(Component.literal("Show Color picker preview")) + .description(OptionDescription.of(Component.literal("Shows the preview below your mouse pointer on selecting color from the screen. Note: You may drop some frames with the preview on."))) .binding(true, () -> this.showColorPickerPreview, newVal -> this.showColorPickerPreview = newVal) .controller(booleanOption -> BooleanControllerBuilder.create(booleanOption).yesNoFormatter()) .build()) .option(Option.createBuilder() - .name(Text.literal("Show widget descriptions/tooltips")) - .description(OptionDescription.of(Text.literal("Shows the description of widgets as tooltips."))) - .binding(true, () -> this.displayDescriptions, newVal -> this.displayDescriptions = newVal) + .name(Component.literal("Show widget descriptions/tooltips")) + .description(OptionDescription.of(Component.literal("Shows the description of widgets as tooltips."))) + .binding(false, () -> this.displayDescriptions, newVal -> this.displayDescriptions = newVal) + .controller(booleanOption -> BooleanControllerBuilder.create(booleanOption).yesNoFormatter()) + .build()) + .option(Option.createBuilder() + .name(Component.literal("Smart Snapping")) + .description(OptionDescription.of(Component.literal("Enables widgets to automatically snap to each other or the center of the screen, displaying alignment guidelines while dragging"))) + .binding(true, () -> this.smartSnapping, newVal -> this.smartSnapping = newVal) .controller(booleanOption -> BooleanControllerBuilder.create(booleanOption).yesNoFormatter()) .build()) .option(Option.createBuilder() - .name(Text.literal("Snap Size")) - .description(OptionDescription.of(Text.literal("Grid size for snapping widgets"))) + .name(Component.literal("Snap Size")) + .description(OptionDescription.of(Component.literal("Grid size for snapping widgets"))) .binding(100, () -> this.snapSize, newVal -> this.snapSize = newVal) .controller(integerOption -> IntegerFieldControllerBuilder.create(integerOption).range(10, 500)) .build()) + .option(Option.createBuilder() + .name(Component.literal("ContextMenu Animation Time")) + .description(OptionDescription.of(Component.literal("The time in seconds for context menu to open"))) + .binding(200, () -> this.cmAnimationTimeInMs, newVal -> this.cmAnimationTimeInMs = newVal) + .controller(integerOption -> IntegerSliderControllerBuilder.create(integerOption).range(0, 500).step(1)) + .build()) .build()) .option(Option.createBuilder() - .name(Text.literal("Widget HUD Active Background Color")) - .description(OptionDescription.of(Text.literal("Color of the background of the widget when it will be rendered"))) + .name(Component.literal("Widget HUD Active Background Color")) + .description(OptionDescription.of(Component.literal("Color of the background of the widget when it will be rendered"))) .binding(new Color(0, 0, 0, 128), () -> this.hudActiveColor, newVal -> this.hudActiveColor = newVal) .controller(ColorControllerBuilder::create) .build()) .option(Option.createBuilder() - .name(Text.literal("Widget HUD Inactive Background Color")) - .description(OptionDescription.of(Text.literal("Color of the background of the widget when it will NOT be rendered"))) + .name(Component.literal("Widget HUD Inactive Background Color")) + .description(OptionDescription.of(Component.literal("Color of the background of the widget when it will NOT be rendered"))) .binding(new Color(255, 0, 0, 128), () -> this.hudInactiveColor, newVal -> this.hudInactiveColor = newVal) .controller(ColorControllerBuilder::create) .build()) .option(Option.createBuilder() - .name(Text.literal("Settings Complexity")) - .description(OptionDescription.of(Text.literal("The level of options to display. Options equal to or below this level will be displayed"))) + .name(Component.literal("Settings Complexity")) + .description(OptionDescription.of(Component.literal("The level of options to display. Options equal to or below this level will be displayed"))) .binding(com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity.Simple, () -> this.complexity, newVal -> this.complexity = newVal) .controller((option) -> EnumControllerBuilder.create(option) .enumClass(com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity.class) - .formatValue(value -> Text.of(value.name())) + .formatValue(value -> Component.literal(value.name())) ) .build()) .build()) .save(HANDLER::save) .build() - .generateScreen(MinecraftClient.getInstance().currentScreen); + .generateScreen(Minecraft.getInstance().screen); } public float getScale() { @@ -153,6 +171,14 @@ public Color getHudActiveColor() { return hudActiveColor; } + public int getCmAnimationTimeInMs() { + return cmAnimationTimeInMs; + } + + public boolean doSmartSnapping() { + return smartSnapping; + } + public com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity complexity() { return complexity; } diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java index 3e39197..c6894ce 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java @@ -1,16 +1,8 @@ package com.tanishisherewith.dynamichud.helpers; -import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.util.Window; -import net.minecraft.util.math.MathHelper; -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL30; +import net.minecraft.util.Mth; import java.awt.*; -import java.nio.ByteBuffer; /** * This class provides helper methods for working with colors. @@ -45,6 +37,10 @@ public class ColorHelper { public static int r, g, b, a; + // Used in [ClassicSkin.java] + public static Color DARK_RED = new Color(116, 0, 0); + public static Color DARK_GREEN = new Color(24, 132, 0, 226); + public ColorHelper(int r, int g, int b, int a) { ColorHelper.r = r; ColorHelper.g = g; @@ -113,9 +109,9 @@ public static float[] getRainbowColor() { float pi = (float) Math.PI; float[] rainbow = new float[3]; - rainbow[0] = 0.5F + 0.5F * MathHelper.sin(x * pi); - rainbow[1] = 0.5F + 0.5F * MathHelper.sin((x + 4F / 3F) * pi); - rainbow[2] = 0.5F + 0.5F * MathHelper.sin((x + 8F / 3F) * pi); + rainbow[0] = 0.5F + 0.5F * Mth.sin(x * pi); + rainbow[1] = 0.5F + 0.5F * Mth.sin((x + 4F / 3F) * pi); + rainbow[2] = 0.5F + 0.5F * Mth.sin((x + 8F / 3F) * pi); return rainbow; } diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java index 0d26f32..fe13c06 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java @@ -1,25 +1,25 @@ package com.tanishisherewith.dynamichud.helpers; -import com.mojang.blaze3d.systems.ProjectionType; -import com.mojang.blaze3d.systems.RenderSystem; -import com.tanishisherewith.dynamichud.DynamicHUD; -import com.tanishisherewith.dynamichud.internal.IRenderLayer; +import com.tanishisherewith.dynamichud.renderstates.GeometryRenderState; +import com.tanishisherewith.dynamichud.renderstates.QuadColorRectRenderState; +import com.tanishisherewith.dynamichud.renderstates.RoundedRectRenderState; import com.tanishisherewith.dynamichud.utils.CustomRenderLayers; import com.tanishisherewith.dynamichud.widget.WidgetBox; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.*; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.client.util.math.Vector2f; -import net.minecraft.text.Text; +import net.fabricmc.fabric.api.renderer.v1.render.RenderLayerHelper; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.util.ARGB; import net.minecraft.util.Util; -import net.minecraft.util.math.MathHelper; import org.jetbrains.annotations.NotNull; -import org.joml.Matrix4f; +import org.joml.Matrix3x2f; +import org.joml.Matrix3x2fStack; import org.joml.Vector4f; -import org.lwjgl.opengl.GL40C; import java.awt.*; +import java.util.Arrays; import java.util.Objects; import static com.tanishisherewith.dynamichud.helpers.TextureHelper.mc; @@ -28,6 +28,13 @@ * Credits: HeliosClient */ public class DrawHelper { + public static final float[] SINA = {0,0.1736482f,0.3420201f,0.5f,0.6427876f,0.7660444f,0.8660254f,0.9396926f,0.9848077f,1,0.9848078f,0.9396927f,0.8660255f,0.7660446f,0.6427878f,0.5000002f,0.3420205f,0.1736485f,3.894144E-07f,-0.1736478f,-0.3420197f,-0.4999996f,-0.6427872f,-0.7660443f,-0.8660252f,-0.9396925f,-0.9848077f,-1,-0.9848078f,-0.9396928f,-0.8660257f,-0.7660449f,-0.6427881f,-0.5000006f,-0.3420208f,-0.1736489f,0,0.1736482f,0.3420201f,0.5f,0.6427876f,0.7660444f,0.8660254f,0.9396926f,0.9848077f}; + public static final float[] COSA = new float[45]; + static { + // Cosa is simply Sina shifted by 90 degrees (9 steps) + System.arraycopy(SINA, 9, COSA, 0, 36); + System.arraycopy(SINA, 9, COSA, 36, 9); + } /** * Draws a singular gradient rectangle on screen with the given parameters @@ -40,59 +47,49 @@ public class DrawHelper { * @param endColor end color of the gradient * @param direction Draws the gradient in the given direction */ - public static void drawGradient(DrawContext drawContext, float x, float y, float width, float height, int startColor, int endColor, Direction direction) { - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getDebugQuads()); - - switch (direction) { - case LEFT_RIGHT: - consumer.vertex(matrix4f, x, y + height, 0.0F).color(startColor); - consumer.vertex(matrix4f, x + width, y + height, 0.0F).color(endColor); - consumer.vertex(matrix4f, x + width, y, 0.0F).color(endColor); - consumer.vertex(matrix4f, x, y, 0.0F).color(startColor); - break; - case TOP_BOTTOM: - consumer.vertex(matrix4f, x, y + height, 0.0F).color(endColor); - consumer.vertex(matrix4f, x + width, y + height, 0.0F).color(endColor); - consumer.vertex(matrix4f, x + width, y, 0.0F).color(startColor); - consumer.vertex(matrix4f, x, y, 0.0F).color(startColor); - break; - case RIGHT_LEFT: - consumer.vertex(matrix4f, x, y + height, 0.0F).color(endColor); - consumer.vertex(matrix4f, x + width, y + height, 0.0F).color(startColor); - consumer.vertex(matrix4f, x + width, y, 0.0F).color(startColor); - consumer.vertex(matrix4f, x, y, 0.0F).color(endColor); - break; - case BOTTOM_TOP: - consumer.vertex(matrix4f, x, y + height, 0.0F).color(startColor); - consumer.vertex(matrix4f, x + width, y + height, 0.0F).color(startColor); - consumer.vertex(matrix4f, x + width, y, 0.0F).color(endColor); - consumer.vertex(matrix4f, x, y, 0.0F).color(endColor); - break; - } - }); - } - - public static void enableScissor(int x, int y, int width, int height) { - enableScissor(x, y, width, height, mc.getWindow().getScaleFactor()); - } - - public static void enableScissor(WidgetBox box) { - enableScissor((int) box.x, (int) box.y, (int) box.getWidth(), (int) box.getHeight(), mc.getWindow().getScaleFactor()); - } - - public static void enableScissor(int x, int y, int width, int height, double scaleFactor) { + public static void drawGradient(GuiGraphics g, float x, float y, float width, float height, int startColor, int endColor, Direction direction) { + int[] c = switch(direction) { + case TOP_BOTTOM -> new int[]{startColor, startColor, endColor, endColor}; + case LEFT_RIGHT -> new int[]{startColor, endColor, endColor, startColor}; + case RIGHT_LEFT -> new int[]{endColor, startColor, startColor, endColor}; + case BOTTOM_TOP -> new int[]{endColor, endColor, startColor, startColor}; + }; + + g.guiRenderState.submitGuiElement( + new QuadColorRectRenderState(RenderPipelines.GUI,new Matrix3x2f(g.pose()),x,y,width,height,c, + g.scissorStack.peek()) + ); + } + + public static void enableScissor(int x, int y, int width, int height, GuiGraphics graphics) { + enableScissor(x, y, width, height, mc.getWindow().getGuiScale(),graphics); + } + + public static void enableScissor(WidgetBox box,GuiGraphics graphics) { + enableScissor((int) box.x, (int) box.y, (int) box.getWidth(), (int) box.getHeight(), mc.getWindow().getGuiScale(),graphics); + } + + public static void enableScissor(int x, int y, int width, int height, float scaleFactor, GuiGraphics graphics) { + scaleFactor = scaleFactor / mc.getWindow().getGuiScale(); int scissorX = (int) (x * scaleFactor); - int scissorY = (int) (DynamicHUD.MC.getWindow().getHeight() - ((y + height) * scaleFactor)); + int scissorY = (int) (y * scaleFactor); int scissorWidth = (int) (width * scaleFactor); int scissorHeight = (int) (height * scaleFactor); - RenderSystem.enableScissor(scissorX, scissorY, scissorWidth, scissorHeight); + ScreenRectangle rect = new ScreenRectangle(scissorX, scissorY, scissorWidth, scissorHeight); + graphics.scissorStack.push(rect); + } + + /** + * All coordinates by minecraft need to unscaled first and then scaled by our factor. + */ + public static float scaleFactor(float scale) { + return scale / mc.getWindow().getGuiScale(); } - public static void disableScissor() { - RenderSystem.disableScissor(); + + public static void disableScissor(GuiGraphics graphics) { + graphics.disableScissor(); } /** @@ -104,16 +101,8 @@ public static void disableScissor() { * @param height Height of the rectangle * @param color Color of the rectangle */ - public static void drawRectangle(DrawContext drawContext, float x, float y, float width, float height, int color) { - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getDebugQuads()); - - consumer.vertex(matrix4f, x, y + height, 0.0F).color(color); - consumer.vertex(matrix4f, x + width, y + height, 0.0F).color(color); - consumer.vertex(matrix4f, x + width, y, 0.0F).color(color); - consumer.vertex(matrix4f, x, y, 0.0F).color(color); - }); + public static void drawRectangle(GuiGraphics graphics, float x, float y, float width, float height, int color) { + drawGradient(graphics,x,y,width,height,color,color,Direction.LEFT_RIGHT); } /* ==== Drawing Rectangles ==== */ @@ -127,11 +116,11 @@ public static void drawRectangle(DrawContext drawContext, float x, float y, floa * @param height Height of the rectangle * @param color Color of the rectangle */ - public static void drawOutlineBox(DrawContext drawContext, float x, float y, float width, float height, float thickness, int color) { - drawRectangle(drawContext, x, y, width, thickness, color); - drawRectangle(drawContext, x, y + height - thickness, width, thickness, color); - drawRectangle(drawContext, x, y + thickness, thickness, height - thickness * 2, color); - drawRectangle(drawContext, x + width - thickness, y + thickness, thickness, height - thickness * 2, color); + public static void drawOutlineBox(GuiGraphics graphics, float x, float y, float width, float height, float thickness, int color) { + drawRectangle(graphics, x, y, width, thickness, color); + drawRectangle(graphics, x, y + height - thickness, width, thickness, color); + drawRectangle(graphics, x, y + thickness, thickness, height - thickness * 2, color); + drawRectangle(graphics, x + width - thickness, y + thickness, thickness, height - thickness * 2, color); } /** @@ -147,12 +136,12 @@ public static void drawOutlineBox(DrawContext drawContext, float x, float y, flo * @param shadowOffsetX X position Offset of the shadow from the main rectangle X pos * @param shadowOffsetY Y position Offset of the shadow from the main rectangle Y pos */ - public static void drawRectangleWithShadowBadWay(DrawContext drawContext, float x, float y, float width, float height, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { + public static void drawRectangleWithShadowBadWay(GuiGraphics graphics, float x, float y, float width, float height, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { // First, render the shadow - drawRectangle(drawContext, x + shadowOffsetX, y + shadowOffsetY, width, height, ColorHelper.getColor(0, 0, 0, shadowOpacity)); + drawRectangle(graphics, x + shadowOffsetX, y + shadowOffsetY, width, height, ColorHelper.getColor(0, 0, 0, shadowOpacity)); // Then, render the rectangle - drawRectangle(drawContext, x, y, width, height, color); + drawRectangle(graphics, x, y, width, height, color); } /** @@ -166,12 +155,12 @@ public static void drawRectangleWithShadowBadWay(DrawContext drawContext, float * @param color Color of the rounded.fsh rectangle * @param thickness thickness of the outline */ - public static void drawOutlineRoundedBox(DrawContext drawContext, float x, float y, float width, float height, float radius, float thickness, int color) { + public static void drawOutlineRoundedBox(GuiGraphics graphics, float x, float y, float width, float height, float radius, float thickness, int color) { Color c = new Color(color, true); - drawOutlineRoundedBox(drawContext,x,y,width,height,new Vector4f(radius),thickness,c,c,c,c); + drawOutlineRoundedBox(graphics,x,y,width,height,new Vector4f(radius),thickness,c,c,c,c); } - public static void drawOutlineRoundedBox(DrawContext drawContext, float x, float y, float width, float height, Vector4f radii, float thickness, Color tl, Color tr, Color br, Color bl) { + public static void drawOutlineRoundedBox(GuiGraphics graphics, float x, float y, float width, float height, Vector4f radii, float thickness, Color tl, Color tr, Color br, Color bl) { if (width <= 0 || height <= 0) return; float maxRadius = Math.min(width, height) / 2; radii.set(Math.min(radii.x, maxRadius), // top-left @@ -179,43 +168,37 @@ public static void drawOutlineRoundedBox(DrawContext drawContext, float x, float Math.min(radii.z, maxRadius), // bottom-right Math.min(radii.w, maxRadius) // bottom-left ); - drawContext.draw(vcp -> { - VertexConsumer dvc = vcp.getBuffer(CustomRenderLayers.ROUNDED_RECT_OUTLINE.apply(new CustomRenderLayers.OutlineParameters(radii, thickness ,new float[]{width, height}))); - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); + int[] intColors = {tl.getRGB(),tr.getRGB(),br.getRGB(),bl.getRGB()}; - dvc.vertex(matrix4f, x, y + height, 0).texture(0, 0).color(bl.getRGB()); - dvc.vertex(matrix4f, x + width, y + height, 0).texture(width, 0).color(br.getRGB()); - dvc.vertex(matrix4f, x + width, y, 0).texture(width, height).color(tr.getRGB()); - dvc.vertex(matrix4f, x, y, 0).texture(0, height).color(tl.getRGB()); - }); + graphics.guiRenderState.submitGuiElement(new RoundedRectRenderState( + RenderPipelines.DEBUG_QUADS, + new Matrix3x2f(graphics.pose()), + x, y, width, height, thickness, intColors, radii, graphics.scissorStack.peek() + )); } /** - * Draw chroma text (text with a nice rainbow effect) + * Draw chroma Component (Component with a nice rainbow effect) * - * @param drawContext A drawContext object - * @param text The text to display - * @param x X pos of text - * @param y Y pos of text + * @param graphics A graphics object + * @param Component The Component to display + * @param x X pos of Component + * @param y Y pos of Component * @param speed Speed of rainbow * @param saturation Saturation of the rainbow colors * @param brightness Brightness of the rainbow colors * @param spread How much the color difference should be between each character (ideally between 0.001 to 0.2) - * @param shadow Whether to render the text as shadow. + * @param shadow Whether to render the Component as shadow. */ - public static void drawChromaText(@NotNull DrawContext drawContext, String text, int x, int y, float speed, float saturation, float brightness, float spread, boolean shadow) { + public static void drawChromaText(@NotNull GuiGraphics graphics, String Component, int x, int y, float speed, float saturation, float brightness, float spread, boolean shadow) { long time = System.currentTimeMillis(); - int length = text.length(); + int length = Component.length(); for (int i = 0; i < length; i++) { float hue = (time % (int) (5000 / speed)) / (5000f / speed) + (i * spread); // Adjust the hue based on time and character position - hue = MathHelper.floorMod(hue, 1.0f); // hue should stay within the range [0, 1] - - // Convert the hue to an RGB color + hue = floorMod(hue, 1.0f); // hue should stay within the range [0, 1] int color = Color.HSBtoRGB(hue, saturation, brightness); - - // Draw the character with the calculated color - drawContext.drawText(mc.textRenderer, String.valueOf(text.charAt(i)), x + mc.textRenderer.getWidth(text.substring(0, i)), y, color, shadow); + graphics.drawString(mc.font, String.valueOf(Component.charAt(i)), x + mc.font.width(Component.substring(0, i)), y, color, shadow); } } @@ -231,20 +214,30 @@ public static void drawChromaText(@NotNull DrawContext drawContext, String text, * @param radius radius of the circle outline * @param color color of the circle outline */ - public static void drawOutlineCircle(DrawContext drawContext, float xCenter, float yCenter, float radius, float lineWidth, int color) { - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getDebugLineStrip(lineWidth)); - - for (int i = 0; i <= 360; i++) { - double x = xCenter + Math.sin(Math.toRadians(i)) * radius; - double y = yCenter + Math.cos(Math.toRadians(i)) * radius; - double x2 = xCenter + Math.sin(Math.toRadians(i)) * (radius + lineWidth); - double y2 = yCenter + Math.cos(Math.toRadians(i)) * (radius + lineWidth); - consumer.vertex(matrix4f, (float) x, (float) y, 0).color(color); - consumer.vertex(matrix4f, (float) x2, (float) y2, 0).color(color); - } - }); + public static void drawOutlineCircle(GuiGraphics graphics, float xCenter, float yCenter, float radius, float lineWidth, int color) { + int segments = 72; // 5-degree steps + float[] verts = new float[(segments + 1) * 4]; + int[] colors = new int[(segments + 1) * 2]; + Arrays.fill(colors, color); + + for (int i = 0; i <= segments; i++) { + float rad = (float) Math.toRadians(i * 5); + float sin = (float) Math.sin(rad); + float cos = (float) Math.cos(rad); + + + int base = i * 4; + verts[base] = xCenter + sin * radius; + verts[base + 1] = yCenter + cos * radius; + verts[base + 2] = xCenter + sin * (radius + lineWidth); + verts[base + 3] = yCenter + cos * (radius + lineWidth); + } + + graphics.guiRenderState.submitGuiElement(new GeometryRenderState( + CustomRenderLayers.TRIANGLE_STRIP, + new Matrix3x2f(graphics.pose()), + verts, colors, graphics.scissorStack.peek() + )); } /** @@ -255,19 +248,36 @@ public static void drawOutlineCircle(DrawContext drawContext, float xCenter, flo * @param radius radius of the circle outline * @param color color of the circle outline */ - public static void drawFilledCircle(DrawContext drawContext, float xCenter, float yCenter, float radius, int color) { - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getDebugTriangleFan()); - - consumer.vertex(matrix4f, xCenter, yCenter, 0).color(color); + public static void drawFilledCircle(GuiGraphics graphics, float xCenter, float yCenter, float radius, int color) { + int segments = 36; + float[] verts = new float[segments * 4 * 2]; + int[] colors = new int[segments * 4]; + + int vIdx = 0; + int cIdx = 0; + + for (int i = 0; i < segments; i++) { + float x1 = xCenter + SINA[i] * radius; + float y1 = yCenter + COSA[i] * radius; + float x2 = xCenter + SINA[i + 1] * radius; + float y2 = yCenter + COSA[i + 1] * radius; + + verts[vIdx++] = xCenter; verts[vIdx++] = yCenter; + verts[vIdx++] = x1; verts[vIdx++] = y1; + verts[vIdx++] = x2; verts[vIdx++] = y2; + verts[vIdx++] = x2; verts[vIdx++] = y2; + + colors[cIdx++] = color; + colors[cIdx++] = color; + colors[cIdx++] = color; + colors[cIdx++] = color; + } - for (int i = 0; i <= 360; i++) { - double x = xCenter + Math.sin(Math.toRadians(i)) * radius; - double y = yCenter + Math.cos(Math.toRadians(i)) * radius; - consumer.vertex(matrix4f, (float) x, (float) y, 0).color(color); - } - }); + graphics.guiRenderState.submitGuiElement(new GeometryRenderState( + CustomRenderLayers.QUADS_CUSTOM_BLEND, + new Matrix3x2f(graphics.pose()), + verts, colors, graphics.scissorStack.peek() + )); } /** @@ -281,42 +291,14 @@ public static void drawFilledCircle(DrawContext drawContext, float xCenter, floa * @param shadowOffsetY X position of the circle shadow offset from main circle * @param shadowOpacity Opacity of the circle shadow offset from main circle */ - public static void drawCircleWithShadow(DrawContext drawContext, float xCenter, float yCenter, float radius, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { + public static void drawCircleWithShadow(GuiGraphics graphics, float xCenter, float yCenter, float radius, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { // First, render the shadow - drawFilledCircle(drawContext, xCenter + shadowOffsetX, yCenter + shadowOffsetY, radius, ColorHelper.getColor(0, 0, 0, shadowOpacity)); + drawFilledCircle(graphics, xCenter + shadowOffsetX, yCenter + shadowOffsetY, radius, ColorHelper.getColor(0, 0, 0, shadowOpacity)); // Then, render the circle - drawFilledCircle(drawContext, xCenter, yCenter, radius, color); + drawFilledCircle(graphics, xCenter, yCenter, radius, color); } - /** - * Not Tested - * - * @param x - * @param y - * @param radius - * @param startAngle - * @param endAngle - * @param color - */ - @Deprecated - public static void drawFilledArc(DrawContext drawContext, float x, float y, float radius, float startAngle, float endAngle, int color) { - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getDebugLineStrip(1.0f)); - - for (float angle = startAngle; angle <= endAngle; angle += 1.0F) { - float x1 = x + MathHelper.cos(angle * 0.017453292F) * radius; - float y1 = y + MathHelper.sin(angle * 0.017453292F) * radius; - float x2 = x + MathHelper.cos((angle + 1.0F) * 0.017453292F) * radius; - float y2 = y + MathHelper.sin((angle + 1.0F) * 0.017453292F) * radius; - - consumer.vertex(matrix4f, x, y, 0).color(color); - consumer.vertex(matrix4f, x1, y1, 0).color(color); - consumer.vertex(matrix4f, x2, y2, 0).color(color); - } - }); - } /* ==== Drawing Quadrants, Arcs, and Triangles ==== */ /** @@ -329,37 +311,29 @@ public static void drawFilledArc(DrawContext drawContext, float x, float y, floa * @param endColor end color of the gradient * @param quadrant Integer value of the quadrant of the circle. 1 == Top Right, 2 == Top Left, 3 == Bottom Right, 4 == Bottom Left */ - public static void drawFilledGradientQuadrant(DrawContext drawContext, float xCenter, float yCenter, float radius, int startColor, int endColor, int quadrant) { - float startRed = (float) (startColor >> 16 & 255) / 255.0F; - float startGreen = (float) (startColor >> 8 & 255) / 255.0F; - float startBlue = (float) (startColor & 255) / 255.0F; - float startAlpha = (float) (startColor >> 24 & 255) / 255.0F; - - float endRed = (float) (endColor >> 16 & 255) / 255.0F; - float endGreen = (float) (endColor >> 8 & 255) / 255.0F; - float endBlue = (float) (endColor & 255) / 255.0F; - float endAlpha = (float) (endColor >> 24 & 255) / 255.0F; - - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getDebugTriangleFan()); - - consumer.vertex(matrix4f, xCenter, yCenter, 0).color(startColor); - - for (int i = quadrant * 90; i <= quadrant * 90 + 90; i++) { - double x = xCenter + Math.sin(Math.toRadians(i)) * radius; - double y = yCenter + Math.cos(Math.toRadians(i)) * radius; - - // Interpolate the color based on the angle - float t = (float) (i - quadrant * 90) / 90.0f; - float red = startRed * (1 - t) + endRed * t; - float green = startGreen * (1 - t) + endGreen * t; - float blue = startBlue * (1 - t) + endBlue * t; - float alpha = startAlpha * (1 - t) + endAlpha * t; + public static void drawFilledGradientQuadrant(GuiGraphics graphics, float xCenter, float yCenter, float radius, int startColor, int endColor, int quadrant) { + int segments = 18; // 90 degrees / 5 + float[] verts = new float[(segments + 2) * 2]; + int[] colors = new int[segments + 2]; + + verts[0] = xCenter; verts[1] = yCenter; + colors[0] = startColor; + + for (int i = 0; i <= segments; i++) { + float angle = (quadrant * 90) + (i * 5); + float rad = (float) Math.toRadians(angle); + + int idx = (i + 1) * 2; + verts[idx] = xCenter + (float) Math.sin(rad) * radius; + verts[idx + 1] = yCenter + (float) Math.cos(rad) * radius; + colors[i + 1] = ARGB.linearLerp((float) i / segments, startColor, endColor); + } - consumer.vertex(matrix4f, (float) x, (float) y, 0).color(red,green,blue,alpha); - } - }); + graphics.guiRenderState.submitGuiElement(new GeometryRenderState( + CustomRenderLayers.TRIANGLE_FAN_CUSTOM_BLEND, + new Matrix3x2f(graphics.pose()), + verts, colors, graphics.scissorStack.peek() + )); } /** @@ -372,23 +346,37 @@ public static void drawFilledGradientQuadrant(DrawContext drawContext, float xCe * @param endAngle end Angle of the arc * @param thickness Thickness of the arc (width of the arc) */ - public static void drawArc(DrawContext drawContext, float xCenter, float yCenter, float radius, float thickness, int color, int startAngle, int endAngle) { - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getDebugTriangleFan()); - - for (int i = startAngle; i <= endAngle; i++) { - double innerX = xCenter + Math.sin(Math.toRadians(i)) * (radius - thickness); - double innerY = yCenter + Math.cos(Math.toRadians(i)) * (radius - thickness); - double outerX = xCenter + Math.sin(Math.toRadians(i)) * radius; - double outerY = yCenter + Math.cos(Math.toRadians(i)) * radius; + public static void drawArc(GuiGraphics graphics, float xCenter, float yCenter, float radius, float thickness, int color, int startAngle, int endAngle) { + int segments = Math.max(1, (endAngle - startAngle) / 5); + float[] verts = new float[(segments + 1) * 4]; + int[] colors = new int[(segments + 1) * 2]; + Arrays.fill(colors, color); + + for (int i = 0; i <= segments; i++) { + int currentAngle = startAngle + (i * 5); + if (currentAngle > endAngle) currentAngle = endAngle; + + float rad = (float) Math.toRadians(currentAngle); + float sin = (float) Math.sin(rad); + float cos = (float) Math.cos(rad); + + int base = i * 4; + // Inner Vertex + verts[base] = xCenter + sin * (radius - thickness); + verts[base + 1] = yCenter + cos * (radius - thickness); + // Outer Vertex + verts[base + 2] = xCenter + sin * radius; + verts[base + 3] = yCenter + cos * radius; + } - consumer.vertex(matrix4f, (float) innerX, (float) innerY, 0).color(color); - consumer.vertex(matrix4f, (float) outerX, (float) outerY, 0).color(color); - } - }); + graphics.guiRenderState.submitGuiElement(new GeometryRenderState( + CustomRenderLayers.TRIANGLE_FAN_CUSTOM_BLEND, + new Matrix3x2f(graphics.pose()), + verts, colors, graphics.scissorStack.peek() + )); } + /** * Draws a filled quadrant * @@ -398,44 +386,32 @@ public static void drawArc(DrawContext drawContext, float xCenter, float yCenter * @param color color of the quadrant * @param quadrant Integer value of the quadrant of the circle. 1 == Top Right, 2 == Top Left, 3 == Bottom Right, 4 == Bottom Left */ - public static void drawFilledQuadrant(DrawContext drawContext, float xCenter, float yCenter, float radius, int color, int quadrant) { - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getDebugTriangleFan()); - - consumer.vertex(matrix4f, xCenter, yCenter, 0).color(color); - - for (int i = quadrant * 90; i <= quadrant * 90 + 90; i++) { - double x = xCenter + Math.sin(Math.toRadians(i)) * radius; - double y = yCenter + Math.cos(Math.toRadians(i)) * radius; - consumer.vertex(matrix4f, (float) x, (float) y, 0).color(color); - } - }); + public static void drawFilledQuadrant(GuiGraphics graphics, float xCenter, float yCenter, float radius, int color, int quadrant) { + drawFilledGradientQuadrant(graphics, xCenter, yCenter, radius, color, color, quadrant); } /** * Draws a Triangle with the given coordinates - * - * @param x1 - * @param y1 - * @param x2 - * @param y2 - * @param x3 - * @param y3 - * @param color */ - public static void drawOutlineTriangle(DrawContext drawContext, int x1, int y1, int x2, int y2, int x3, int y3, int color) { - drawContext.draw(vcp -> { - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(RenderLayer.getLines()); - - consumer.vertex(matrix4f, x1, y1, 0).color(color); - consumer.vertex(matrix4f, x2, y2, 0).color(color); - consumer.vertex(matrix4f, x3, y3, 0).color(color); - consumer.vertex(matrix4f, x1, y1, 0).color(color); - }); + public static void drawOutlineTriangle(GuiGraphics graphics, int x1, int y1, int x2, int y2, int x3, int y3, int color) { + // 3 lines require 6 vertices (1-2, 2-3, 3-1) to form a closed loop + float[] vertices = { + (float)x1, (float)y1, (float)x2, (float)y2, // Line 1 + (float)x2, (float)y2, (float)x3, (float)y3, // Line 2 + (float)x3, (float)y3, (float)x1, (float)y1 // Line 3 + }; + + int[] colors = new int[6]; + java.util.Arrays.fill(colors, color); + + graphics.guiRenderState.submitGuiElement(new GeometryRenderState( + CustomRenderLayers.COLOR_LINE, + new Matrix3x2f(graphics.pose()), + vertices, + colors, + graphics.scissorStack.peek() + )); } - /** * Draws a outline quadrant * @@ -445,7 +421,7 @@ public static void drawOutlineTriangle(DrawContext drawContext, int x1, int y1, * @param color color of the quadrant * @param quadrant Integer value of the quadrant of the circle. 1 == Top Right, 2 == Top Left, 3 == Bottom Right, 4 == Bottom Left */ - public static void drawOutlineQuadrant(DrawContext drawContext, float xCenter, float yCenter, float radius, int quadrant, int color) { + public static void drawOutlineQuadrant(GuiGraphics graphics, float xCenter, float yCenter, float radius, int quadrant, int color) { int startAngle = 0; int endAngle = 0; @@ -462,7 +438,7 @@ public static void drawOutlineQuadrant(DrawContext drawContext, float xCenter, f endAngle = 90; } - drawArc(drawContext, xCenter, yCenter, radius, 1f, color, startAngle, endAngle); + drawArc(graphics, xCenter, yCenter, radius, 1f, color, startAngle, endAngle); } /** @@ -475,8 +451,8 @@ public static void drawOutlineQuadrant(DrawContext drawContext, float xCenter, f * @param radius Radius of the quadrants / the rounded.fsh rectangle * @param color Color of the rounded.fsh rectangle */ - public static void drawRoundedRectangle(DrawContext drawContext, float x, float y, float width, float height, float radius, int color) { - drawRoundedRectangle(drawContext, x, y, true, true, true, true, width, height, radius, color); + public static void drawRoundedRectangle(GuiGraphics graphics, float x, float y, float width, float height, float radius, int color) { + drawRoundedRectangle(graphics, x, y, true, true, true, true, width, height, radius, color); } /* ==== Drawing Rounded Rectangles ==== */ @@ -495,17 +471,17 @@ public static void drawRoundedRectangle(DrawContext drawContext, float x, float * @param radius Radius of the quadrants / the rounded.fsh rectangle * @param color Color of the rounded.fsh rectangle */ - public static void drawRoundedRectangle(DrawContext drawContext, float x, float y, boolean TL, boolean TR, boolean BL, boolean BR, float width, float height, float radius, int color) { - Vector4f radii = new Vector4f(TR ? radius : 0.0f, BR ? radius : 0.0f, TL ? radius : 0.0f, BL ? radius : 0.0f); + public static void drawRoundedRectangle(GuiGraphics graphics, float x, float y, boolean TL, boolean TR, boolean BL, boolean BR, float width, float height, float radius, int color) { + Vector4f radii = new Vector4f(TL ? radius : 0.0f, TR ? radius : 0.0f, BR ? radius : 0.0f, BL ? radius : 0.0f); // Turns out Color class takes rgb by default not rgba Color c = new Color(color, true); - drawRoundedRectangle(drawContext,x,y,width, height, radii, c,c,c,c); + drawRoundedRectangle(graphics,x,y,width, height, radii, c,c,c,c); } /** * Draws a rounded rectangle with customizable corner radii, corner colors, and selective corner rounding. - * @param drawContext DrawContext for rendering + * @param graphics GuiGraphics for rendering * @param x X position * @param y Y position * @@ -514,7 +490,7 @@ public static void drawRoundedRectangle(DrawContext drawContext, float x, float * @param height Height of the rectangle * @param radii Vector4f specifying radii for top-left, top-right, bottom-right, bottom-left corners */ - public static void drawRoundedRectangle(DrawContext drawContext, float x, float y, float width, float height, + public static void drawRoundedRectangle(GuiGraphics graphics, float x, float y, float width, float height, Vector4f radii, Color tl, Color tr, Color br, Color bl) { if (width <= 0 || height <= 0) return; float maxRadius = Math.min(width, height) / 2; @@ -523,17 +499,16 @@ public static void drawRoundedRectangle(DrawContext drawContext, float x, float Math.min(radii.z, maxRadius), // bottom-right Math.min(radii.w, maxRadius) // bottom-left ); - drawContext.draw(vcp -> { - VertexConsumer dvc = vcp.getBuffer(CustomRenderLayers.ROUNDED_RECT.apply(new CustomRenderLayers.RoundedParameters(radii, new float[]{width, height}))); - Matrix4f matrix4f = drawContext.getMatrices().peek().getPositionMatrix(); + int[] intColors = {tl.getRGB(),tr.getRGB(),br.getRGB(),bl.getRGB()}; - dvc.vertex(matrix4f, x, y + height, 0).texture(0, 0).color(bl.getRGB()); - dvc.vertex(matrix4f, x + width, y + height, 0).texture(width, 0).color(br.getRGB()); - dvc.vertex(matrix4f, x + width, y, 0).texture(width, height).color(tr.getRGB()); - dvc.vertex(matrix4f, x, y, 0).texture(0, height).color(tl.getRGB()); - }); + graphics.guiRenderState.submitGuiElement(new RoundedRectRenderState( + RenderPipelines.DEBUG_QUADS, + new Matrix3x2f(graphics.pose()), + x, y, width, height, -1f, intColors, radii, graphics.scissorStack.peek() + )); } + /** * Draws an outline rounded.fsh gradient rectangle * @@ -547,15 +522,8 @@ public static void drawRoundedRectangle(DrawContext drawContext, float x, float * @param height Height of rounded.fsh gradient rectangle * @param radius Radius of the quadrants / the rounded.fsh gradient rectangle */ - public static void drawOutlineGradientRoundedBox(DrawContext drawContext, float x, float y, float width, float height, float radius, float thickness, Color tl, Color tr, Color br, Color bl) { - drawOutlineRoundedBox(drawContext,x,y,width,height,new Vector4f(radius),thickness,tl, tr, br,bl); - } - - public static void drawCutRectangle(DrawContext drawContext, int x1, int y1, int x2, int y2, int z, int color, int cornerRadius) { - // Draw the rectangles - drawContext.fill(x1 + cornerRadius, y1, x2 - cornerRadius, y1 + cornerRadius, z, color); - drawContext.fill(x1 + cornerRadius, y2 - cornerRadius, x2 - cornerRadius, y2, z, color); - drawContext.fill(x1, y1 + cornerRadius, x2, y2 - cornerRadius, z, color); + public static void drawOutlineGradientRoundedBox(GuiGraphics graphics, float x, float y, float width, float height, float radius, float thickness, Color tl, Color tr, Color br, Color bl) { + drawOutlineRoundedBox(graphics,x,y,width,height,new Vector4f(radius),thickness,tl, tr, br,bl); } /** @@ -571,12 +539,12 @@ public static void drawCutRectangle(DrawContext drawContext, int x1, int y1, int * @param shadowOffsetX X offset of the shadow * @param shadowOffsetY Y offset of the shadow */ - public static void drawRoundedRectangleWithShadowBadWay(DrawContext drawContext, float x, float y, float width, float height, float radius, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { + public static void drawRoundedRectangleWithShadowBadWay(GuiGraphics graphics, float x, float y, float width, float height, float radius, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { // First, render the shadow - drawRoundedRectangle(drawContext, x + shadowOffsetX, y + shadowOffsetY, width, height, radius, ColorHelper.getColor(0, 0, 0, shadowOpacity)); + drawRoundedRectangle(graphics, x + shadowOffsetX, y + shadowOffsetY, width, height, radius, ColorHelper.getColor(0, 0, 0, shadowOpacity)); // Then, render the rounded.fsh rectangle - drawRoundedRectangle(drawContext, x, y, width, height, radius, color); + drawRoundedRectangle(graphics, x, y, width, height, radius, color); } /** @@ -592,8 +560,8 @@ public static void drawRoundedRectangleWithShadowBadWay(DrawContext drawContext, * @param height Height of rounded.fsh gradient rectangle * @param radius Radius of the quadrants / the rounded.fsh gradient rectangle */ - public static void drawRoundedGradientRectangle(DrawContext drawContext, Color tl, Color tr, Color br, Color bl, float x, float y, float width, float height, float radius) { - drawRoundedGradientRectangle(drawContext, tl,tr,br,bl, x, y, width, height, radius, true, true, true, true); + public static void drawRoundedGradientRectangle(GuiGraphics graphics, Color tl, Color tr, Color br, Color bl, float x, float y, float width, float height, float radius) { + drawRoundedGradientRectangle(graphics, tl,tr,br,bl, x, y, width, height, radius, true, true, true, true); } /** @@ -609,19 +577,19 @@ public static void drawRoundedGradientRectangle(DrawContext drawContext, Color t * @param height Height of rounded.fsh gradient rectangle * @param radius Radius of the quadrants / the rounded.fsh gradient rectangle */ - public static void drawRoundedGradientRectangle(DrawContext drawContext, Color tl, Color tr, Color br, Color bl, float x, float y, float width, float height, float radius, boolean TL, boolean TR, boolean BL, boolean BR) { - drawRoundedRectangle(drawContext, x, y, width, height, + public static void drawRoundedGradientRectangle(GuiGraphics graphics, Color tl, Color tr, Color br, Color bl, float x, float y, float width, float height, float radius, boolean TL, boolean TR, boolean BL, boolean BR) { + drawRoundedRectangle(graphics, x, y, width, height, new Vector4f(TR ? radius : 0.0f, BR ? radius : 0.0f, TL ? radius : 0.0f, BL ? radius : 0.0f), tl,tr,br,bl); } /* ==== Drawing Lines ==== */ - public static void drawVerticalLine(DrawContext drawContext, float x, float y1, float height, float thickness, int color) { - drawRectangle(drawContext, x, y1, thickness, height, color); + public static void drawVerticalLine(GuiGraphics graphics, float x, float y1, float height, float thickness, int color) { + drawRectangle(graphics, x, y1, thickness, height, color); } - public static void drawHorizontalLine(DrawContext drawContext, float x1, float width, float y, float thickness, int color) { - drawRectangle(drawContext, x1, y, width, thickness, color); + public static void drawHorizontalLine(GuiGraphics graphics, float x1, float width, float y, float thickness, int color) { + drawRectangle(graphics, x1, y, width, thickness, color); } /** @@ -633,23 +601,16 @@ public static void drawHorizontalLine(DrawContext drawContext, float x1, float w * @param y2 The y position of the bottom right corner of the box * @param color The color to draw the box with */ - public static void drawOutlinedBox(DrawContext drawContext, int x1, int y1, int x2, int y2, int color) { - drawContext.fill(x1, y1, x2, y1 + 1, color); - drawContext.fill(x1, y2 - 1, x2, y2, color); - drawContext.fill(x1, y1 + 1, x1 + 1, y2 - 1, color); - drawContext.fill(x2 - 1, y1 + 1, x2, y2 - 1, color); - } - - public static void unscaledProjection() { - RenderSystem.setProjectionMatrix(new Matrix4f().setOrtho(0, mc.getWindow().getFramebufferWidth(), mc.getWindow().getFramebufferHeight(), 0, 1000, 21000), ProjectionType.ORTHOGRAPHIC); - } - - public static void scaledProjection() { - RenderSystem.setProjectionMatrix(new Matrix4f().setOrtho(0, (float) (mc.getWindow().getFramebufferWidth() / mc.getWindow().getScaleFactor()), (float) (mc.getWindow().getFramebufferHeight() / mc.getWindow().getScaleFactor()), 0, 1000, 21000), ProjectionType.ORTHOGRAPHIC); + public static void drawOutlinedBox(GuiGraphics graphics, int x1, int y1, int x2, int y2, int color) { + graphics.fill(x1, y1, x2, y1 + 1, color); + graphics.fill(x1, y2 - 1, x2, y2, color); + graphics.fill(x1, y1 + 1, x1 + 1, y2 - 1, color); + graphics.fill(x2 - 1, y1 + 1, x2, y2 - 1, color); } - public static void customScaledProjection(float scale) { - RenderSystem.setProjectionMatrix(new Matrix4f().setOrtho(0, mc.getWindow().getFramebufferWidth() / scale, mc.getWindow().getFramebufferHeight() / scale, 0, 1000, 21000), ProjectionType.ORTHOGRAPHIC); + public static void scaledProjection(float scale, GuiGraphics graphics) { + graphics.pose().pushMatrix(); + graphics.pose().scale(scale/mc.getWindow().getGuiScale()); } /** @@ -659,16 +620,23 @@ public static void customScaledProjection(float scale) { * @param y Y position of widget * @param scale Scale the matrices */ - public static void scaleAndPosition(MatrixStack matrices, float x, float y, float scale) { - matrices.push(); // Save the current transformation state + public static void scaleAndPosition(Matrix3x2fStack matrices, float x, float y, float scale) { + matrices.pushMatrix(); // Translate the origin back to the desired position - matrices.translate(x, y, 0); + matrices.translate(x, y); - // Scale the matrix - matrices.scale(scale, scale, 1.0F); + matrices.scale(scale, scale); - matrices.translate(-x, -y, 0); + matrices.translate(-x, -y); + } + + /** + * Creating bounds for Render states + */ + public static ScreenRectangle createBounds(Matrix3x2f pose, ScreenRectangle scissor, float x, float y, float w, float h) { + ScreenRectangle bounds = new ScreenRectangle((int) x, (int) y, (int) w, (int) h).transformAxisAligned(pose); + return scissor == null ? bounds : scissor.intersection(bounds); } /** @@ -680,48 +648,51 @@ public static void scaleAndPosition(MatrixStack matrices, float x, float y, floa * @param width width of widget * @param scale Scale the matrices */ - public static void scaleAndPosition(MatrixStack matrices, float x, float y, float width, float height, float scale) { - matrices.push(); // Save the current transformation state + public static void scaleAndPosition(Matrix3x2fStack matrices, float x, float y, float width, float height, float scale) { + matrices.pushMatrix(); // Translate the origin back to the desired position - matrices.translate(x + width / 2.0f, y + height / 2.0f, 0); + matrices.translate(x + width / 2.0f, y + height / 2.0f); - // Scale the matrix - matrices.scale(scale, scale, 1.0F); + matrices.scale(scale, scale); - matrices.translate(-(x + width / 2.0f), -(y + height / 2.0f), 0); + matrices.translate(-(x + width / 2.0f), -(y + height / 2.0f)); } - public static void stopScaling(MatrixStack matrices) { - matrices.pop(); // Restore the previous transformation state + public static void stopScaling(Matrix3x2fStack matrices) { + matrices.popMatrix(); // Restore the previous transformation state } /** * From minecraft */ - public static void drawScrollableText(DrawContext context, TextRenderer textRenderer, Text text, int centerX, int startX, int startY, int endX, int endY, int color) { - int i = textRenderer.getWidth(text); + public static void drawScrollableText(GuiGraphics graphics, Font font, Component Component, int centerX, int startX, int startY, int endX, int endY, int color) { + int i = font.width(Component); int var10000 = startY + endY; - Objects.requireNonNull(textRenderer); + Objects.requireNonNull(font); int j = (var10000 - 9) / 2 + 1; int k = endX - startX; int l; if (i > k) { l = i - k; - double d = (double) Util.getMeasuringTimeMs() / 1000.0; + double d = (double) Util.getMillis() / 1000.0; double e = Math.max((double) l * 0.5, 3.0); double f = Math.sin(1.5707963267948966 * Math.cos(6.283185307179586 * d / e)) / 2.0 + 0.5; - double g = MathHelper.lerp(f, 0.0, l); - context.enableScissor(startX, startY, endX, endY); - context.drawTextWithShadow(textRenderer, text, startX - (int) g, j, color); - context.disableScissor(); + double g = org.joml.Math.lerp(f, 0.0, l); + graphics.enableScissor(startX, startY, endX, endY); + graphics.drawString(font, Component, startX - (int) g, j, color,true); + graphics.disableScissor(); } else { - l = MathHelper.clamp(centerX, startX + i / 2, endX - i / 2); - context.drawCenteredTextWithShadow(textRenderer, text, l, j, color); + l = Math.clamp(centerX, startX + i / 2, endX - i / 2); + graphics.drawCenteredString(font, Component, l, j, color); } } + public static float floorMod(float x, float y) { + return x - y * (float) Math.floor(x / y); + } + public enum Direction { /* LEFT_RIGHT means from left to right. Same for others */ LEFT_RIGHT, TOP_BOTTOM, RIGHT_LEFT, BOTTOM_TOP diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/MouseColorQuery.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/MouseColorQuery.java index 095bbf9..7626aba 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/MouseColorQuery.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/MouseColorQuery.java @@ -1,9 +1,10 @@ package com.tanishisherewith.dynamichud.helpers; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.Window; import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.util.Window; +import com.tanishisherewith.dynamichud.DynamicHUD; +import net.minecraft.client.Minecraft; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; @@ -22,18 +23,20 @@ public static void request(double mouseX, double mouseY, Consumer callbac pendingRequest = new SampleRequest(mouseX, mouseY, callback); } } + public static void request(Consumer callback) { + request(DynamicHUD.MC.mouseHandler.xpos(), DynamicHUD.MC.mouseHandler.ypos(), callback); + } public static void processIfPending() { if (pendingRequest == null) return; - MinecraftClient client = MinecraftClient.getInstance(); - Framebuffer framebuffer = client.getFramebuffer(); - Window window = client.getWindow(); + RenderTarget framebuffer = DynamicHUD.MC.getMainRenderTarget(); + Window window = DynamicHUD.MC.getWindow(); int windowWidth = window.getWidth(); int windowHeight = window.getHeight(); - int framebufferWidth = framebuffer.textureWidth; - int framebufferHeight = framebuffer.textureHeight; + int framebufferWidth = framebuffer.width; + int framebufferHeight = framebuffer.height; double scaleX = (double) framebufferWidth / windowWidth; double scaleY = (double) framebufferHeight / windowHeight; @@ -50,11 +53,9 @@ public static void processIfPending() { // Make sure rendering is complete RenderSystem.assertOnRenderThread(); - // buffer to store the pixel data ByteBuffer buffer = BufferUtils.createByteBuffer(4); - GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); int red = buffer.get(0) & 0xFF; diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java index 48b5779..3dfaa37 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java @@ -1,8 +1,8 @@ package com.tanishisherewith.dynamichud.helpers; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.texture.NativeImage; -import net.minecraft.util.Identifier; +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.Identifier; import java.io.IOException; import java.io.InputStream; @@ -11,11 +11,11 @@ * This class is entirely untested so some issues may occur which in case should be reported immediately. */ public class TextureHelper { - static MinecraftClient mc = MinecraftClient.getInstance(); + static Minecraft mc = Minecraft.getInstance(); public static NativeImage loadTexture(Identifier textureId) { if (mc.getResourceManager().getResource(textureId).isPresent()) { - try (InputStream inputStream = mc.getResourceManager().getResource(textureId).get().getInputStream()) { + try (InputStream inputStream = mc.getResourceManager().getResource(textureId).get().open()) { return NativeImage.read(inputStream); } catch (IOException e) { throw new RuntimeException("Failed to load texture " + textureId, e); @@ -35,7 +35,7 @@ public static NativeImage resizeTexture(NativeImage image, int newWidth, int new int srcX = x * oldWidth / newWidth; int srcY = y * oldHeight / newHeight; - result.setColorArgb(x, y, image.getColorArgb(srcX, srcY)); + result.setPixelABGR(x, y, image.getPixel(srcX, srcY)); } } @@ -58,10 +58,10 @@ public static NativeImage resizeTextureUsingBilinearInterpolation(NativeImage im y_diff = (y_ratio * i) - y; // Indexes of the 4 surrounding pixels - a = image.getColorArgb(x, y); - b = image.getColorArgb(x + 1, y); - c = image.getColorArgb(x, y + 1); - d = image.getColorArgb(x + 1, y + 1); + a = image.getPixel(x, y); + b = image.getPixel(x + 1, y); + c = image.getPixel(x, y + 1); + d = image.getPixel(x + 1, y + 1); // Blue element blue = (a & 0xff) * (1 - x_diff) * (1 - y_diff) + (b & 0xff) * (x_diff) * (1 - y_diff) + @@ -75,7 +75,7 @@ public static NativeImage resizeTextureUsingBilinearInterpolation(NativeImage im red = ((a >> 16) & 0xff) * (1 - x_diff) * (1 - y_diff) + ((b >> 16) & 0xff) * (x_diff) * (1 - y_diff) + ((c >> 16) & 0xff) * (y_diff) * (1 - x_diff) + ((d >> 16) & 0xff) * (x_diff * y_diff); - result.setColorArgb(j, i, + result.setPixelABGR(j, i, ((((int) red) << 16) & 0xff0000) | ((((int) green) << 8) & 0xff00) | ((int) blue) & 0xff); @@ -92,7 +92,7 @@ public static NativeImage invertTexture(NativeImage image) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - int argb = image.getColorArgb(x, y); + int argb = image.getPixel(x, y); int alpha = (argb >> 24) & 0xFF; int red = 255 - ((argb >> 16) & 0xFF); @@ -101,7 +101,7 @@ public static NativeImage invertTexture(NativeImage image) { int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; - result.setColorArgb(x, y, newArgb); + result.setPixelABGR(x, y, newArgb); } } @@ -123,7 +123,7 @@ public static NativeImage rotateTexture(NativeImage image, int degrees) { int newY = (int) ((x - centerX) * Math.sin(angle) + (y - centerY) * Math.cos(angle) + centerY); if (newX >= 0 && newX < width && newY >= 0 && newY < height) { - result.setColorArgb(newY, newX, image.getColorArgb(x, y)); + result.setPixelABGR(newY, newX, image.getPixel(x, y)); } } } @@ -138,7 +138,7 @@ private static NativeImage flipTextureHorizontally(NativeImage image) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - result.setColorArgb(width - x - 1, y, image.getColorArgb(x, y)); + result.setPixelABGR(width - x - 1, y, image.getPixel(x, y)); } } @@ -152,7 +152,7 @@ private static NativeImage flipTextureVertically(NativeImage image) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - result.setColorArgb(x, height - y - 1, image.getColorArgb(x, y)); + result.setPixelABGR(x, height - y - 1, image.getPixel(x, y)); } } @@ -174,7 +174,7 @@ public static NativeImage applyGrayScaleFilter(NativeImage image) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - int argb = image.getColorArgb(x, y); + int argb = image.getPixel(x, y); int alpha = (argb >> 24) & 0xFF; int red = (argb >> 16) & 0xFF; @@ -184,7 +184,7 @@ public static NativeImage applyGrayScaleFilter(NativeImage image) { int gray = (red + green + blue) / 3; int newArgb = (alpha << 24) | (gray << 16) | (gray << 8) | gray; - result.setColorArgb(x, y, newArgb); + result.setPixelABGR(x, y, newArgb); } } @@ -196,7 +196,7 @@ public static NativeImage cropTexture(NativeImage image, int x, int y, int width for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { - result.setColorArgb(j, i, image.getColorArgb(x + j, y + i)); + result.setPixelABGR(j, i, image.getPixel(x + j, y + i)); } } @@ -210,7 +210,7 @@ public static NativeImage tintTexture(NativeImage image, int color) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - int argb = image.getColorArgb(x, y); + int argb = image.getPixel(x, y); int alpha = (argb >> 24) & 0xFF; int red = ((argb >> 16) & 0xFF) * ((color >> 16) & 0xFF) / 255; @@ -219,7 +219,7 @@ public static NativeImage tintTexture(NativeImage image, int color) { int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; - result.setColorArgb(x, y, newArgb); + result.setPixelABGR(x, y, newArgb); } } @@ -233,8 +233,8 @@ public static NativeImage overlayTexture(NativeImage image, NativeImage overlay) for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - int argb1 = image.getColorArgb(x, y); - int argb2 = overlay.getColorArgb(x, y); + int argb1 = image.getPixel(x, y); + int argb2 = overlay.getPixel(x, y); int alpha = Math.max((argb1 >> 24) & 0xFF, (argb2 >> 24) & 0xFF); int red = Math.min(255, ((argb1 >> 16) & 0xFF) + ((argb2 >> 16) & 0xFF)); @@ -243,7 +243,7 @@ public static NativeImage overlayTexture(NativeImage image, NativeImage overlay) int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; - result.setColorArgb(x, y, newArgb); + result.setPixelABGR(x, y, newArgb); } } @@ -258,7 +258,7 @@ public static int getAverageColor(NativeImage image) { for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { - int argb = image.getColorArgb(x, y); + int argb = image.getPixel(x, y); redTotal += (argb >> 16) & 0xFF; greenTotal += (argb >> 8) & 0xFF; diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java deleted file mode 100644 index 20a80b0..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.tanishisherewith.dynamichud.helpers.animationhelper; - -// AnimationProperty.java -public interface AnimationProperty { - T get(); - - void set(T value); -} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/MathAnimations.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/MathAnimations.java index 6850fff..1500ad8 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/MathAnimations.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/MathAnimations.java @@ -2,112 +2,168 @@ import com.tanishisherewith.dynamichud.helpers.animationhelper.Easing; import com.tanishisherewith.dynamichud.helpers.animationhelper.EasingType; -import net.minecraft.util.math.Vec2f; -import net.minecraft.util.math.random.Random; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.phys.Vec2; + +import java.util.Random; public class MathAnimations { - /// SHAKE: Random offset animation with smooth decay + private static final RandomSource RANDOM = RandomSource.create(); + + /** + * SHAKE: Random offset animation with smooth decay. + * @param intensity max displacement + * @param frequency in radians per second (e.g. 20 rad/s = ~3 oscillations/sec) + * @param decay decay rate per second (e.g. 2.0 means amplitude halves every 0.35s) + */ public static float shake(float intensity, float frequency, float decay) { - long time = System.currentTimeMillis(); - return (float) (Math.sin(time * frequency) * - Math.exp(-decay * time) * intensity); + double time = System.currentTimeMillis() / 1000.0; + return (float) (Math.sin(time * frequency) * Math.exp(-decay * time) * intensity); } - /// 2D Shake with different X/Y frequencies - public static Vec2f shake2D(float intensity, float freqX, float freqY) { - return new Vec2f( - (float) Math.sin(System.currentTimeMillis() * freqX) * intensity, - (float) Math.cos(System.currentTimeMillis() * freqY) * intensity + /** + * 2D Shake with different X/Y frequencies. + */ + public static Vec2 shake2D(float intensity, float freqX, float freqY) { + double time = System.currentTimeMillis() / 1000.0; + return new Vec2( + (float) Math.sin(time * freqX) * intensity, + (float) Math.cos(time * freqY) * intensity ); } - /// FLICKER: Random flashing effect + /** + * FLICKER: Random flashing effect. + * @param chance probability (0..1) to return a random value in [min, max]; otherwise returns max. + */ public static float flicker(float min, float max, float chance) { - Random rand = Random.create(); - return rand.nextFloat() < chance ? - min + (max - min) * rand.nextFloat() : + return RANDOM.nextFloat() < chance ? + min + (max - min) * RANDOM.nextFloat() : max; } - /// CIRCULAR MOTION: Perfect for rotation/orbital animations - public static Vec2f circularMotion(float radius, float speed, float phase) { - double angle = Math.toRadians((System.currentTimeMillis() * speed) % 360 + phase); - return new Vec2f( + /** + * CIRCULAR MOTION: Orbital animation. + * @param radius orbit radius + * @param speed radians per second + * @param phase initial angle offset in radians + */ + public static Vec2 circularMotion(float radius, float speed, float phase) { + double time = System.currentTimeMillis() / 1000.0; + double angle = time * speed + phase; + return new Vec2( (float) (Math.cos(angle) * radius), (float) (Math.sin(angle) * radius) ); } - /// SAWTOOTH WAVE: Linear rise with sudden drop + /** + * SAWTOOTH WAVE: Linear rise with sudden drop. + * @param period in seconds + */ public static float sawtooth(float period, float min, float max) { - float phase = (System.currentTimeMillis() % period) / period; - return min + (max - min) * phase; + double time = System.currentTimeMillis() / 1000.0; + double phase = (time % period) / period; + return (float) (min + (max - min) * phase); } - /// TRIANGULAR WAVE: Linear rise and fall + /** + * TRIANGULAR WAVE: Linear rise and fall. + */ public static float triangleWave(float period, float min, float max) { - float halfPeriod = period / 2; - float phase = (System.currentTimeMillis() % period); - float value = phase < halfPeriod ? + double time = System.currentTimeMillis() / 1000.0; + double halfPeriod = period / 2; + double phase = time % period; + double value = phase < halfPeriod ? (phase / halfPeriod) : 2 - (phase / halfPeriod); - return min + (max - min) * value; + return (float) (min + (max - min) * value); } - /// BOUNCE: Simulates physical bouncing + /** + * BOUNCE: Simulates physical bouncing. + * @param dropHeight initial height + * @param gravity acceleration (e.g. 9.8) + * @param dampening damping factor (e.g. 0.5) + */ public static float bounce(float dropHeight, float gravity, float dampening) { - float t = System.currentTimeMillis() / 1000f; + double t = System.currentTimeMillis() / 1000.0; return (float) (dropHeight * Math.abs(Math.sin(t * Math.sqrt(gravity))) * Math.exp(-dampening * t)); } - /// PULSE: Smooth heartbeat-like effect + /** + * PULSE (sine): Smooth heartbeat-like. + * @param frequency in radians per second + */ public static float pulse1(float base, float amplitude, float frequency) { - return (float) (base + amplitude * - (0.5 + 0.5 * Math.sin(System.currentTimeMillis() * frequency))); + double time = System.currentTimeMillis() / 1000.0; + return (float) (base + amplitude * (0.5 + 0.5 * Math.sin(time * frequency))); } - /// SPIRAL: Circular motion with expanding radius - public static Vec2f spiral(float baseRadius, float expansionRate, float speed) { - float t = System.currentTimeMillis() / 1000f; - return new Vec2f( + /** + * SPIRAL: Circular motion with expanding radius. + */ + public static Vec2 spiral(float baseRadius, float expansionRate, float speed) { + double t = System.currentTimeMillis() / 1000.0; + return new Vec2( (float) ((baseRadius + expansionRate * t) * Math.cos(t * speed)), (float) ((baseRadius + expansionRate * t) * Math.sin(t * speed)) ); } - /// Continuous pulsating effect using sine wave + /** + * PULSE (sine) with explicit min/max. + * @param speed radians per second + */ public static float pulse2(float speed, float min, float max) { - return (float) ((Math.sin(System.currentTimeMillis() * speed) + 1) / 2 * (max - min) + min); + double time = System.currentTimeMillis() / 1000.0; // seconds + double val = (float) Math.sin(time * speed); // -1 to 1 + return (float) (val + 1) / 2 * (max - min) + min; // remap to [min, max] } - /// Linear interpolation between values over time + /** + * Linear interpolation between values over time. + * @param startTime time when animation started (System.currentTimeMillis()) + * @param duration in milliseconds + */ public static float lerp(float start, float end, long startTime, float duration) { return lerp(start, end, startTime, duration, EasingType.LINEAR); } - /// Linear interpolation between values over time with easing public static float lerp(float start, float end, long startTime, float duration, EasingType easing) { - float progress = (System.currentTimeMillis() - startTime) / duration; - progress = Math.min(1, Math.max(0, progress)); // Clamp 0-1 - return start + (end - start) * Easing.apply(easing, progress); + double progress = (System.currentTimeMillis() - startTime) / duration; + progress = Mth.clamp(progress, 0f, 1f); + return start + (end - start) * Easing.apply(easing, (float) progress); } - /// Bouncing animation using quadratic ease-out - public static float bounce(float start, float end, long startTime, float duration) { - float time = System.currentTimeMillis() - startTime; - time /= duration; - return end * (1 - (time - 1) * (time - 1)) + start; + /** + * Bounce animation (quadratic ease-out) over time. + * @param startTime when animation started + * @param duration in milliseconds + */ + public static float bounceAnim(float start, float end, long startTime, float duration) { + double time = (System.currentTimeMillis() - startTime) / duration; + time = Mth.clamp(time, 0f, 1f); + return (float) (end * (1 - (time - 1) * (time - 1)) + start); } - /// Continuous rotation using modulo - public static float continuousRotation(float speed) { - return (System.currentTimeMillis() % (360_000 / speed)) * (speed / 1000); + /** + * Continuous rotation (returns degrees 0..360) based on speed in degrees per second. + */ + public static float continuousRotation(float speedDegPerSec) { + double time = System.currentTimeMillis() / 1000.0; + return (float) ((time * speedDegPerSec) % 360); } - /// Elastic wobble effect + /** + * Elastic wobble (damped sine). + * @param speed in radians per second + * @param magnitude initial amplitude + */ public static float elasticWobble(float speed, float magnitude) { - return (float) (Math.sin(System.currentTimeMillis() * speed) * - Math.exp(-0.001 * System.currentTimeMillis()) * magnitude); + double t = System.currentTimeMillis() / 1000.0; + return (float) (Math.sin(t * speed) * Math.exp(-t * 0.5) * magnitude); } } \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/SquishAnimator.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/SquishAnimator.java new file mode 100644 index 0000000..8d78f73 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/SquishAnimator.java @@ -0,0 +1,51 @@ +package com.tanishisherewith.dynamichud.helpers.animationhelper.animations; + +import com.tanishisherewith.dynamichud.helpers.animationhelper.EasingType; + +import java.util.function.Consumer; + +public class SquishAnimator extends ValueAnimation { + private boolean isPressed = false; + private final float normalScale; + private final float pressedScale; + private final ScaleHolder holder; + + private SquishAnimator(ScaleHolder holder, float normalScale, float pressedScale) { + super(holder, normalScale, pressedScale); + this.holder = holder; + this.holder.value = normalScale; + this.normalScale = normalScale; + this.pressedScale = pressedScale; + + // default polished easing configurations + this.easing(EasingType.EASE_OUT_BACK); + this.duration(125); + } + + public SquishAnimator(float normalScale, float pressedScale) { + this(new ScaleHolder(), normalScale, pressedScale); + } + + public SquishAnimator(){ + this(1.0f,0.95f); + } + + public void update(boolean pressed) { + if (this.isPressed != pressed) { + this.isPressed = pressed; + this.set(holder.value,pressed ? pressedScale : normalScale).start(); + } + this.update(); + } + + public float getScale() { return holder.value; } + + private static class ScaleHolder implements Consumer { + float value; + + @Override + public void accept(Float v) { + this.value = v; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/ValueAnimation.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/ValueAnimation.java index 9d19d3b..e0bdea7 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/ValueAnimation.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/ValueAnimation.java @@ -1,32 +1,56 @@ package com.tanishisherewith.dynamichud.helpers.animationhelper.animations; import com.tanishisherewith.dynamichud.helpers.animationhelper.Animation; -import com.tanishisherewith.dynamichud.helpers.animationhelper.AnimationProperty; import com.tanishisherewith.dynamichud.helpers.animationhelper.Easing; import com.tanishisherewith.dynamichud.helpers.animationhelper.EasingType; +import java.util.function.Consumer; + public class ValueAnimation extends Animation { - private final AnimationProperty property; + private final Consumer setter; private float startValue; private float endValue; private EasingType easing; private float value; - public ValueAnimation(AnimationProperty property, float start, float end, EasingType easingType) { - this.property = property; + /** + * Creates a new ValueAnimation with an easing type. + * + * @param setter A functional callback (such as a lambda expression) to receive the updated float. + * @param start The starting float value. + * @param end The target ending float value. + * @param easingType The mathematical easing equation to apply. + */ + + public ValueAnimation(Consumer setter, float start, float end, EasingType easingType) { + this.setter = setter; this.startValue = start; this.endValue = end; this.easing = easingType; + this.value = startValue; } - public ValueAnimation(AnimationProperty property, float start, float end) { - this(property, start, end, EasingType.LINEAR); + /** + * Creates a new ValueAnimation with linear easing. + * + * @param setter A functional callback (such as a lambda expression) to receive the updated float. + * @param start The starting float value. + * @param end The target ending float value. + */ + public ValueAnimation(Consumer setter, float start, float end) { + this(setter, start, end, EasingType.LINEAR); } @Override protected void applyAnimation(float progress) { this.value = startValue + (endValue - startValue) * Easing.apply(easing, progress); - property.set(value); + setter.accept(value); + } + + public ValueAnimation set(float startValue, float endValue){ + this.startValue = startValue; + this.endValue = endValue; + return this; } public ValueAnimation easing(EasingType easing) { @@ -44,6 +68,11 @@ public ValueAnimation endValue(float endValue) { return this; } + public void setValue(float value) { + this.value = value; + setter.accept(value); + } + public float getValue() { return value; } diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java index 33ae2b7..50bc203 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java +++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java @@ -4,6 +4,7 @@ import com.tanishisherewith.dynamichud.widgets.GraphWidget; import com.tanishisherewith.dynamichud.widgets.ItemWidget; import com.tanishisherewith.dynamichud.widgets.TextWidget; +import net.minecraft.client.KeyMapping; /** * The default implementation for included widgets. @@ -27,4 +28,9 @@ public void registerCustomWidgets() { GraphWidget.DATA ); } + + @Override + public KeyMapping getKeyBind() { + return null; + } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java index 7340598..61f80e5 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java +++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java @@ -121,7 +121,7 @@ public void setupSaveEvents(File widgetsFile) { ServerPlayConnectionEvents.DISCONNECT.register((handler, packetSender) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); //When minecraft closes - ClientLifecycleEvents.CLIENT_STOPPING.register((mc)-> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); + ClientLifecycleEvents.CLIENT_STOPPING.register((mc)-> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); } @ApiStatus.Internal diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java index bcc32de..156ccb8 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java +++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java @@ -1,5 +1,6 @@ package com.tanishisherewith.dynamichud.integration; +import com.mojang.blaze3d.platform.InputConstants; import com.tanishisherewith.dynamichud.IntegrationTest; import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; import com.tanishisherewith.dynamichud.widget.WidgetData; @@ -7,8 +8,8 @@ import com.tanishisherewith.dynamichud.widget.WidgetRenderer; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.option.KeyBinding; -import net.minecraft.client.util.InputUtil; +import net.minecraft.client.KeyMapping; +import net.minecraft.resources.Identifier; import org.lwjgl.glfw.GLFW; import java.io.File; @@ -19,16 +20,6 @@ * @see DefaultIntegrationImpl */ public interface DynamicHudIntegration { - /** - * The key binding for opening the editor screen. - */ - KeyBinding EDITOR_SCREEN_KEY_BINDING = KeyBindingHelper.registerKeyBinding(new KeyBinding( - "DynamicHud Editor Screen", - InputUtil.Type.KEYSYM, - GLFW.GLFW_KEY_RIGHT_SHIFT, - "DynamicHud" - )); - /** * The filename for the widgets file. */ @@ -97,7 +88,5 @@ default File getWidgetsFile() { * * @return The keybind. */ - default KeyBinding getKeyBind() { - return EDITOR_SCREEN_KEY_BINDING; - } + KeyMapping getKeyBind(); } diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java b/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java index 485a616..a264c1b 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java +++ b/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java @@ -13,8 +13,8 @@ import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.entrypoint.EntrypointContainer; import net.fabricmc.loader.api.metadata.ModMetadata; -import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.gui.screens.TitleScreen; import java.io.File; import java.io.IOException; @@ -32,7 +32,7 @@ public final class IntegrationManager { public static final Map> FILE_MAP = new HashMap<>(); private static final List widgetRenderers = new ArrayList<>(); - private static boolean enableTestIntegration = false; + public static boolean IS_TEST_MODE = false; public static void addWidgetRenderer(WidgetRenderer widgetRenderer) { @@ -49,8 +49,8 @@ public static List getWidgetRenderers() { * @param key The key to listen for * @param screen The AbstractMoveableScreen instance to use to set the screen */ - public static void openScreen(KeyBinding key, AbstractMoveableScreen screen) { - if (key.wasPressed()) { + public static void openScreen(KeyMapping key, AbstractMoveableScreen screen) { + if (key.isDown()) { DynamicHUD.MC.setScreen(screen); } } @@ -59,7 +59,7 @@ private static void checkToEnableTestIntegration() { String[] args = FabricLoader.getInstance().getLaunchArguments(true); for (int i = 0; i < args.length; i++) { if (args[i].equals("--dynamicHudTest") && i + 1 < args.length) { - enableTestIntegration = Boolean.parseBoolean(args[i + 1]); + IS_TEST_MODE = Boolean.parseBoolean(args[i + 1]); break; } } @@ -72,7 +72,7 @@ public static void integrate() { var integrations = new ArrayList<>(getRegisteredIntegrations()); - if (enableTestIntegration) { + if (IS_TEST_MODE) { EntrypointContainer testIntegration = getTestIntegration(); if (testIntegration != null) { integrations.add(testIntegration); @@ -92,7 +92,7 @@ public static void integrate() { String modId = metadata.getId(); AbstractMoveableScreen screen; - KeyBinding binding; + KeyMapping binding; WidgetRenderer widgetRenderer; File widgetsFile; try { @@ -161,7 +161,7 @@ public static void integrate() { ClientTickEvents.START_CLIENT_TICK.register((client) -> { if (BooleanPool.get("WarningScreenFlag")) return; - if (DynamicHUD.MC.currentScreen instanceof TitleScreen) { + if (DynamicHUD.MC.screen instanceof TitleScreen) { DynamicHUD.MC.setScreen(new WarningScreen(bad_implementations)); BooleanPool.put("WarningScreenFlag", true); } @@ -185,13 +185,13 @@ private static List> getRegisteredInt } /** - * This makes it so that if minecraft is launched with the program arguments + * If minecraft is launched with the program arguments *

* {@code --dynamicHudTest true} *

- * then it will - * load the {@link com.tanishisherewith.dynamichud.IntegrationTest} class as an entrypoint, eliminating any errors due to human incapacity of - * adding/removing a single line from the `fabric.mod.json` + * then it wil load the {@link com.tanishisherewith.dynamichud.IntegrationTest} class as an entrypoint, + * eliminating any errors due to human incapacity of adding/removing a single line from the `fabric.mod.json` + * This is for myself. */ private static EntrypointContainer getTestIntegration() { DynamicHudIntegration testIntegration; diff --git a/src/main/java/com/tanishisherewith/dynamichud/internal/IBufferBuilder.java b/src/main/java/com/tanishisherewith/dynamichud/internal/IBufferBuilder.java new file mode 100644 index 0000000..0aaabea --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/internal/IBufferBuilder.java @@ -0,0 +1,8 @@ +package com.tanishisherewith.dynamichud.internal; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormatElement; + +public interface IBufferBuilder { + VertexConsumer dynamicHUD$writeGenericFloats(VertexFormatElement element, float... values); +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/internal/IRenderLayer.java b/src/main/java/com/tanishisherewith/dynamichud/internal/IRenderLayer.java deleted file mode 100644 index 3dbc1a2..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/internal/IRenderLayer.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.tanishisherewith.dynamichud.internal; - -import org.joml.Vector4f; - -public interface IRenderLayer { - /** - * Set uniform u to the value described by v4f, formatted as a vec4 (4 floats) - * @param u Name - * @param v4f Value - */ - default void dynamichud$setUniform(String u, Vector4f v4f) { - float[] v = new float[]{v4f.x, v4f.y, v4f.z, v4f.w}; - dynamichud$setUniform(u, v); - } - - /** - * Set uniform u to the value of v, should be an int[], float[] or Matrix4f - * @param u Name - * @param v Value - */ - void dynamichud$setUniform(String u, Object v); -} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/internal/WarningScreen.java b/src/main/java/com/tanishisherewith/dynamichud/internal/WarningScreen.java index 2b7ef4b..204760e 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/internal/WarningScreen.java +++ b/src/main/java/com/tanishisherewith/dynamichud/internal/WarningScreen.java @@ -1,12 +1,13 @@ package com.tanishisherewith.dynamichud.internal; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.text.OrderedText; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.Skin; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; import net.minecraft.util.Util; import java.awt.*; @@ -17,52 +18,53 @@ public class WarningScreen extends Screen { private final List modErrors; public WarningScreen(List modErrors) { - super(Text.of("DynamicHUD Warning")); + super(Component.literal("DynamicHUD Warning")); this.modErrors = modErrors; } @Override protected void init() { - ButtonWidget confirmButton = ButtonWidget.builder(Text.of("I Understand"), button -> MinecraftClient.getInstance().setScreen(null)) - .dimensions(this.width / 2 - 100, this.height - 40, 200, 20) - .narrationSupplier((e) -> Text.literal("I understand")) + Button confirmButton = Button.builder(Component.literal("I Understand"), button -> Minecraft.getInstance().setScreen(null)) + .bounds(this.width / 2 - 100, this.height - 40, 200, 20) + .createNarration((e) -> Component.literal("I understand")) .build(); - ButtonWidget logs_folder = ButtonWidget.builder(Text.of("Open logs"), button -> { - File logsFolder = new File(MinecraftClient.getInstance().runDirectory, "logs"); - Util.getOperatingSystem().open(logsFolder); + Button logs_folder = Button.builder(Component.literal("Open logs"), button -> { + File logsFolder = new File(Minecraft.getInstance().gameDirectory, "logs"); + Util.getPlatform().openFile(logsFolder); }) - .dimensions(this.width / 2 - 100, this.height - 70, 200, 20) - .narrationSupplier((e) -> Text.literal("Open logs")) + .bounds(this.width / 2 - 100, this.height - 70, 200, 20) + .createNarration((e) -> Component.literal("Open logs")) .build(); // Add "I Understand" button - this.addDrawableChild(confirmButton); - this.addDrawableChild(logs_folder); + this.addRenderableWidget(confirmButton); + this.addRenderableWidget(logs_folder); } @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + super.render(graphics, mouseX, mouseY, delta); - context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 20, 0xFFFFFF); - context.drawCenteredTextWithShadow(this.textRenderer, "Mods with bad implementation of DynamicHUD found!", this.width / 2, 35, Color.ORANGE.getRGB()); + graphics.drawCenteredString(this.font, this.title, this.width / 2, 20, 0xFFFFFF); + graphics.drawCenteredString(this.font, "Mods with bad implementation of DynamicHUD found!", this.width / 2, 35, Color.ORANGE.getRGB()); int y = 60; for (ModError error : modErrors) { - Text modName = Text.literal("> \"" + error.modName() + "\"").formatted(Formatting.RED); - context.drawText(this.textRenderer, modName, this.width / 2 - 100, y, -1, false); - List errorMessage = this.textRenderer.wrapLines(Text.literal("Error: " + error.errorMessage()), this.width / 2); + Component modName = Component.literal("> \"" + error.modName() + "\"").withStyle(ChatFormatting.RED); + graphics.drawString(this.font, modName, this.width / 2 - 100, y, -1, false); + List errorMessage = + this.font.split(Component.literal("Error: " + error.errorMessage()), this.width / 2); - if (mouseX >= this.width / 2 - 100 && mouseX <= this.width / 2 - 100 + this.textRenderer.getWidth(modName) && mouseY >= y && mouseY <= y + this.textRenderer.fontHeight) { - context.drawOrderedTooltip(textRenderer, errorMessage, mouseX, mouseY); + if (Skin.isMouseOver(mouseX,mouseY,(double) this.width / 2 - 102, y - 1, this.font.width(modName) + 4,this.font.lineHeight + 2)) { + graphics.setTooltipForNextFrame(errorMessage, mouseX, mouseY); } y += 11; // Space between mod errors } y += 5; - context.drawCenteredTextWithShadow(this.textRenderer, Text.of("Please report this problem to the respective mod owners."), this.width / 2, y, -1); - context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Widgets of these mods won't work.").formatted(Formatting.YELLOW), this.width / 2, y + 10, -1); - context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Check latest.log for more details").formatted(Formatting.ITALIC), this.width / 2, y + 30, -1); + graphics.drawCenteredString(this.font, Component.literal("Please report this problem to the respective mod owners."), this.width / 2, y, -1); + graphics.drawCenteredString(this.font, Component.literal("Widgets of these mods won't work.").withStyle(ChatFormatting.YELLOW), this.width / 2, y + 10, -1); + graphics.drawCenteredString(this.font, Component.literal("Check latest.log for more details").withStyle(ChatFormatting.ITALIC), this.width / 2, y + 30, -1); } } \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/BufferBuilderMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/BufferBuilderMixin.java new file mode 100644 index 0000000..0e25d22 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/mixins/BufferBuilderMixin.java @@ -0,0 +1,30 @@ +package com.tanishisherewith.dynamichud.mixins; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormatElement; +import com.tanishisherewith.dynamichud.internal.IBufferBuilder; +import org.lwjgl.system.MemoryUtil; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(BufferBuilder.class) +public abstract class BufferBuilderMixin implements VertexConsumer, IBufferBuilder { + + @Shadow + protected abstract long beginElement(VertexFormatElement element); + + + @Unique + public VertexConsumer dynamicHUD$writeGenericFloats(VertexFormatElement element, float... values) { + long addr = this.beginElement(element); + if (addr != -1L) { + for (int i = 0; i < values.length; i++) { + // We use the same MemoryUtil Mojang uses in addVertex and setUv + MemoryUtil.memPutFloat(addr + (i * 4L), values[i]); + } + } + return this; + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/MinecraftMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/MinecraftMixin.java new file mode 100644 index 0000000..5a20cd4 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/mixins/MinecraftMixin.java @@ -0,0 +1,15 @@ +package com.tanishisherewith.dynamichud.mixins; + +import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.integration.IntegrationManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.main.GameConfig; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public abstract class MinecraftMixin { + +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java index 5b0367a..b778b05 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java +++ b/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java @@ -2,13 +2,17 @@ import com.llamalad7.mixinextras.sugar.Local; import com.tanishisherewith.dynamichud.config.GlobalConfig; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.option.OptionsScreen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.DirectionalLayoutWidget; -import net.minecraft.text.Text; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.options.OptionsScreen; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -18,21 +22,43 @@ @Mixin(OptionsScreen.class) public abstract class OptionsScreenMixin extends Screen { @Shadow - protected abstract ButtonWidget createButton(Text message, Supplier screenSupplier); + protected abstract Button openScreenButton(Component component, Supplier supplier); - protected OptionsScreenMixin(Text title) { + protected OptionsScreenMixin(Component title) { super(title); } - @Inject(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/GridWidget$Adder;add(Lnet/minecraft/client/gui/widget/Widget;)Lnet/minecraft/client/gui/widget/Widget;", ordinal = 0)) - private void init(CallbackInfo ci, @Local(ordinal = 0) DirectionalLayoutWidget directionalLayoutWidget) { - DirectionalLayoutWidget directionalLayoutWidget2 = directionalLayoutWidget.add(DirectionalLayoutWidget.horizontal()); + @Unique + private LinearLayout header; - directionalLayoutWidget2.getMainPositioner().marginLeft(-26).marginY(-28); + @Unique + private Button dhButton; + + @Inject( + method = "init", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/layouts/LinearLayout;addChild(Lnet/minecraft/client/gui/layouts/LayoutElement;)Lnet/minecraft/client/gui/layouts/LayoutElement;", + ordinal = 1, // this is where the FOV row is added + shift = At.Shift.AFTER + ) + ) + private void injectDHLayout(CallbackInfo ci, @Local(ordinal = 0) LinearLayout header) { + this.header = header; + dhButton = openScreenButton(Component.literal("DH"), () -> GlobalConfig.get().createYACLGUI()); + dhButton.setPosition(this.width / 2 - 150 - 24 - 8, header.getY() + header.getHeight() - 20); + dhButton.setSize(20,20); + this.addRenderableWidget(dhButton); + } - ButtonWidget widget = createButton(Text.of("DH"), () -> GlobalConfig.get().createYACLGUI()); - widget.setDimensions(20, 20); - directionalLayoutWidget2.add(widget); + @Inject( + method = "repositionElements", + at = @At(value = "TAIL") + ) + private void repositionElements(CallbackInfo ci) { + if(dhButton != null && header != null) { + dhButton.setPosition(this.width / 2 - 150 - 24 - 8, header.getY() + header.getHeight() - 20); + } } -} \ No newline at end of file +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/RenderLayerMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/RenderLayerMixin.java deleted file mode 100644 index cc4795f..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/mixins/RenderLayerMixin.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.tanishisherewith.dynamichud.mixins; - - -import com.llamalad7.mixinextras.sugar.Local; -import com.mojang.blaze3d.systems.RenderPass; -import com.tanishisherewith.dynamichud.internal.IRenderLayer; -import net.minecraft.client.render.BuiltBuffer; -import net.minecraft.client.render.RenderLayer; -import org.joml.Matrix4f; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.HashMap; -import java.util.Map; - -@Mixin(RenderLayer.MultiPhase.class) -public class RenderLayerMixin implements IRenderLayer { - @Unique - private final Map uniforms = new HashMap<>(); - - @Override - public void dynamichud$setUniform(String u, Object v) { - if (v == null) uniforms.remove(u); - else uniforms.put(u, v); - } - - @Inject(method = "draw", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderPass;drawIndexed(II)V")) - void beforeDraw(BuiltBuffer par1, CallbackInfo ci, @Local(ordinal = 0) RenderPass pass) { - uniforms.forEach((k, v) -> { - switch (v) { - case float[] fa -> pass.setUniform(k, fa); - case int[] ia -> pass.setUniform(k, ia); - case Matrix4f mat -> pass.setUniform(k, mat); - default -> throw new IllegalStateException("Unknown uniform type " + v.getClass() + " (" + v + ")"); - } - }); - } -} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java index 21446b4..bf685ba 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java +++ b/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java @@ -3,9 +3,8 @@ import com.tanishisherewith.dynamichud.integration.IntegrationManager; import com.tanishisherewith.dynamichud.widget.WidgetManager; import com.tanishisherewith.dynamichud.widget.WidgetRenderer; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -21,19 +20,19 @@ public abstract class ScreenMixin { public int height; @Inject(at = @At("RETURN"), method = "render") - private void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + private void render(GuiGraphics graphics, int mouseX, int mouseY, float delta, CallbackInfo ci) { for (WidgetRenderer widgetRenderer : IntegrationManager.getWidgetRenderers()) { - widgetRenderer.renderWidgets(context, mouseX, mouseY); + widgetRenderer.renderWidgets(graphics, mouseX, mouseY); } } //Injected before the screen is actually resized to get the new and also the old dimensions. @Inject(at = @At("HEAD"), method = "resize") - private void onScreenResize(MinecraftClient client, int width, int height, CallbackInfo ci) { + private void onScreenResize(int i, int j, CallbackInfo ci) { WidgetManager.onScreenResized(width, height, this.width, this.height); } - @Inject(at = @At("HEAD"), method = "close") + @Inject(at = @At("HEAD"), method = "onClose") private void onClose(CallbackInfo ci) { for (WidgetRenderer widgetRenderer : IntegrationManager.getWidgetRenderers()) { widgetRenderer.onCloseScreen(); diff --git a/src/main/java/com/tanishisherewith/dynamichud/renderstates/GeometryRenderState.java b/src/main/java/com/tanishisherewith/dynamichud/renderstates/GeometryRenderState.java new file mode 100644 index 0000000..2f5fbd8 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/renderstates/GeometryRenderState.java @@ -0,0 +1,55 @@ +package com.tanishisherewith.dynamichud.renderstates; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.TextureSetup; +import net.minecraft.client.gui.render.state.GuiElementRenderState; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2f; + +// State for Geometric Shapes (Circles, Arcs, Fans) +public record GeometryRenderState( + RenderPipeline pipeline, + Matrix3x2f pose, + float[] vertices, // Flat array: [x1, y1, x2, y2, ...] + int[] colors, // Parallel array of ARGB colors + @Nullable ScreenRectangle scissorArea +) implements GuiElementRenderState { + + @Override + public void buildVertices(VertexConsumer consumer) { + for (int i = 0; i < vertices.length / 2; i++) { + consumer.addVertexWith2DPose(pose, vertices[i * 2], vertices[i * 2 + 1]) + .setColor(colors[i]); + } + } + + @Override + public TextureSetup textureSetup() { + return TextureSetup.noTexture(); + } + + @Override + public @Nullable ScreenRectangle bounds() { + if (vertices == null || vertices.length < 2) return null; + + float minX = Float.MAX_VALUE; + float minY = Float.MAX_VALUE; + float maxX = -Float.MAX_VALUE; + float maxY = -Float.MAX_VALUE; + + for (int i = 0; i < vertices.length / 2; i++) { + float x = vertices[i * 2]; + float y = vertices[i * 2 + 1]; + + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } + + return DrawHelper.createBounds(pose, scissorArea, minX, minY, maxX - minX, maxY - minY); + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/renderstates/GradientShadowRenderState.java b/src/main/java/com/tanishisherewith/dynamichud/renderstates/GradientShadowRenderState.java new file mode 100644 index 0000000..5a0c3de --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/renderstates/GradientShadowRenderState.java @@ -0,0 +1,52 @@ +package com.tanishisherewith.dynamichud.renderstates; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.TextureSetup; +import net.minecraft.client.gui.render.state.GuiElementRenderState; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2f; +import org.joml.Matrix3x2fc; +import org.jspecify.annotations.NonNull; + +import java.util.List; + +//TODO: just use fillGradient in GuiGraphics +public record GradientShadowRenderState( + List points, + float bottomY, + int startColor, + int endColor, + Matrix3x2f pose, + RenderPipeline pipeline, + int width, + int height, + @Nullable ScreenRectangle scissorArea +) implements GuiElementRenderState { + + @Override + public void buildVertices(@NonNull VertexConsumer consumer) { + for (int i = 0; i < points.size() - 1; i++) { + float x1 = points.get(i)[0]; + float y1 = points.get(i)[1]; + float x2 = points.get(i + 1)[0]; + float y2 = points.get(i + 1)[1]; + + consumer.addVertexWith2DPose(pose, x1, y1).setColor(startColor); // Topleft + consumer.addVertexWith2DPose(pose, x1, bottomY).setColor(endColor); // Bottomleft + consumer.addVertexWith2DPose(pose, x2, bottomY).setColor(endColor); // Bottomright + consumer.addVertexWith2DPose(pose, x2, y2).setColor(startColor); // Topright + } + } + @Override + public @NonNull TextureSetup textureSetup() { + return TextureSetup.noTexture(); + } + + @Override + public @Nullable ScreenRectangle bounds() { + return DrawHelper.createBounds(pose,scissorArea,points.getFirst()[0],points.getFirst()[1],width,height); + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/renderstates/InterpolatedCurveRenderState.java b/src/main/java/com/tanishisherewith/dynamichud/renderstates/InterpolatedCurveRenderState.java new file mode 100644 index 0000000..5d0e115 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/renderstates/InterpolatedCurveRenderState.java @@ -0,0 +1,96 @@ +package com.tanishisherewith.dynamichud.renderstates; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.TextureSetup; +import net.minecraft.client.gui.render.state.GuiElementRenderState; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2f; +import org.jspecify.annotations.NonNull; + +import java.util.List; + +public record InterpolatedCurveRenderState( + List points, + float thickness, + int color, + Matrix3x2f pose, + RenderPipeline pipeline, + int width, + int height, + @Nullable ScreenRectangle scissorArea +) implements GuiElementRenderState { + + @Override + public void buildVertices(@NonNull VertexConsumer consumer) { + int numPoints = points.size(); + if (numPoints < 2) return; + + float halfThickness = thickness * 0.5f; + + // Pre-calculate continuous smooth joint normals + float[] extX = new float[numPoints]; + float[] extY = new float[numPoints]; + + for (int i = 0; i < numPoints; i++) { + float dx, dy; + if (i == 0) { + dx = points.get(1)[0] - points.get(0)[0]; + dy = points.get(1)[1] - points.get(0)[1]; + } else if (i == numPoints - 1) { + dx = points.get(i)[0] - points.get(i - 1)[0]; + dy = points.get(i)[1] - points.get(i - 1)[1]; + } else { + // Blend vector headings from incoming and outgoing segments + float dx1 = points.get(i)[0] - points.get(i - 1)[0]; + float dy1 = points.get(i)[1] - points.get(i - 1)[1]; + float dx2 = points.get(i + 1)[0] - points.get(i)[0]; + float dy2 = points.get(i + 1)[1] - points.get(i)[1]; + + float len1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1); + float len2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2); + + if (len1 > 0) { dx1 /= len1; dy1 /= len1; } + if (len2 > 0) { dx2 /= len2; dy2 /= len2; } + + dx = dx1 + dx2; + dy = dy1 + dy2; + } + + float length = (float) Math.sqrt(dx * dx + dy * dy); + if (length > 0) { + // Preserves exact original winding orientation to completely prevent culling + extX[i] = (dy / length) * halfThickness; + extY[i] = (-dx / length) * halfThickness; + } else { + extX[i] = 0; + extY[i] = 0; + } + } + + // Stitch the continuous mesh strip + for (int i = 0; i < numPoints - 1; i++) { + float[] p1 = points.get(i); + float[] p2 = points.get(i + 1); + + // Winding pattern: TopLeft -> BottomLeft -> BottomRight -> TopRight + consumer.addVertexWith2DPose(pose, p1[0] + extX[i], p1[1] + extY[i]).setColor(color); + consumer.addVertexWith2DPose(pose, p1[0] - extX[i], p1[1] - extY[i]).setColor(color); + consumer.addVertexWith2DPose(pose, p2[0] - extX[i + 1], p2[1] - extY[i + 1]).setColor(color); + consumer.addVertexWith2DPose(pose, p2[0] + extX[i + 1], p2[1] + extY[i + 1]).setColor(color); + } + } + + @Override + public @NonNull TextureSetup textureSetup() { + return TextureSetup.noTexture(); + } + + @Override + public @Nullable ScreenRectangle bounds() { + if (points.isEmpty()) return null; + return DrawHelper.createBounds(pose, scissorArea, points.getFirst()[0], points.getFirst()[1], width, height); + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/renderstates/QuadColorRectRenderState.java b/src/main/java/com/tanishisherewith/dynamichud/renderstates/QuadColorRectRenderState.java new file mode 100644 index 0000000..6edf7ca --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/renderstates/QuadColorRectRenderState.java @@ -0,0 +1,41 @@ +package com.tanishisherewith.dynamichud.renderstates; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.TextureSetup; +import net.minecraft.client.gui.render.state.GuiElementRenderState; +import org.joml.Matrix3x2f; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +public record QuadColorRectRenderState( + RenderPipeline pipeline, + Matrix3x2f pose, + float x, + float y, + float width, + float height, + int[] color, + ScreenRectangle scissorArea +) implements GuiElementRenderState { + + @Override + public void buildVertices(VertexConsumer vertices) { + vertices.addVertexWith2DPose(this.pose(),x, y).setColor(this.color[1]); + vertices.addVertexWith2DPose(this.pose(), x, y + height).setColor(this.color[2]); + vertices.addVertexWith2DPose(this.pose(), x + width, y + height).setColor(this.color[3]); + vertices.addVertexWith2DPose(this.pose(), x + width, y).setColor(this.color[0]); + } + + @Override + public @NonNull TextureSetup textureSetup() { + return TextureSetup.noTexture(); + } + + @Override + public @Nullable ScreenRectangle bounds() { + return DrawHelper.createBounds(pose,scissorArea,x,y,width,height); + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/renderstates/RoundedRectRenderState.java b/src/main/java/com/tanishisherewith/dynamichud/renderstates/RoundedRectRenderState.java new file mode 100644 index 0000000..daf15eb --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/renderstates/RoundedRectRenderState.java @@ -0,0 +1,117 @@ +package com.tanishisherewith.dynamichud.renderstates; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.TextureSetup; +import net.minecraft.client.gui.render.state.GuiElementRenderState; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2f; +import org.joml.Vector4f; +import org.jspecify.annotations.NonNull; + +import static com.tanishisherewith.dynamichud.helpers.DrawHelper.COSA; +import static com.tanishisherewith.dynamichud.helpers.DrawHelper.SINA; + +/** + * Using Matrix3x2f is essential for these shapes to be affected by MatrixStack changes. + */ +public record RoundedRectRenderState( + RenderPipeline pipeline, + Matrix3x2f pose, + float x, float y, + float width, float height, + float thickness, + int[] colors, + Vector4f roundness, + @Nullable ScreenRectangle scissorArea +) implements GuiElementRenderState { + + @Override + public void buildVertices(@NonNull VertexConsumer consumer) { + float fX = 0, fY = 0, fIX = 0, fIY = 0; + int fC = 0; + float pX = 0, pY = 0, pIX = 0, pIY = 0; + int pC = 0; + + float midX = x + (width * 0.5f); + float midY = y + (height * 0.5f); + int midC = (thickness <= 0) ? averageColors(colors) : 0; + + // 9 steps per quadrant to match the 360 precomputed tables + for (int k = 0; k <= 36; k++) { + int i = k % 36; + float r, cx, cy; + int color; + + // Quadrants:- 0-8:BR, 9-17:BL, 18-26:TL, 27-35:TR + if (i < 9) { + r = roundness.z; cx = x + width - r; cy = y + height - r; color = colors[2]; + } else if (i < 18) { + r = roundness.w; cx = x + r; cy = y + height - r; color = colors[3]; + } else if (i < 27) { + r = roundness.x; cx = x + r; cy = y + r; color = colors[0]; + } else { + r = roundness.y; cx = x + width - r; cy = y + r; color = colors[1]; + } + + float cX = cx + (r * COSA[i]); + float cY = cy + (r * SINA[i]); + float cIX = 0, cIY = 0; + + if (thickness > 0) { + // clamping inner radius to 0 + float ir = Math.max(0, r - thickness); + cIX = cx + (ir * COSA[i]); + cIY = cy + (ir * SINA[i]); + } + + if (k == 0) { + // Cache the first vertex set to ensure pixel-perfect loop closure + fX = cX; fY = cY; fIX = cIX; fIY = cIY; fC = color; + } else { + float tX = (k == 36) ? fX : cX; + float tY = (k == 36) ? fY : cY; + float tIX = (k == 36) ? fIX : cIX; + float tIY = (k == 36) ? fIY : cIY; + int tC = (k == 36) ? fC : color; + + if (thickness > 0) { + consumer.addVertexWith2DPose(pose, pX, pY).setColor(pC); + consumer.addVertexWith2DPose(pose, pIX, pIY).setColor(pC); + consumer.addVertexWith2DPose(pose, tIX, tIY).setColor(tC); + consumer.addVertexWith2DPose(pose, tX, tY).setColor(tC); + } else { + consumer.addVertexWith2DPose(pose, midX, midY).setColor(midC); + consumer.addVertexWith2DPose(pose, pX, pY).setColor(pC); + consumer.addVertexWith2DPose(pose, tX, tY).setColor(tC); + consumer.addVertexWith2DPose(pose, tX, tY).setColor(tC); + } + } + + pX = cX; pY = cY; pIX = cIX; pIY = cIY; pC = color; + } + } + + private int averageColors(int[] c) { + int r = 0, g = 0, b = 0, a = 0; + for (int color : c) { + a += (color >> 24) & 0xFF; + r += (color >> 16) & 0xFF; + g += (color >> 8) & 0xFF; + b += color & 0xFF; + } + return ((a / 4) << 24) | ((r / 4) << 16) | ((g / 4) << 8) | (b / 4); + } + + @Override + public @NonNull TextureSetup textureSetup() { + return TextureSetup.noTexture(); + } + + @Override + public @Nullable ScreenRectangle bounds() { + return DrawHelper.createBounds(pose, scissorArea, x, y, width, height); + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java b/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java index 55ad285..8b2860a 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java +++ b/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java @@ -1,12 +1,21 @@ package com.tanishisherewith.dynamichud.screens; import com.tanishisherewith.dynamichud.config.GlobalConfig; +import com.tanishisherewith.dynamichud.utils.Util; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuManager; import com.tanishisherewith.dynamichud.widget.Widget; import com.tanishisherewith.dynamichud.widget.WidgetRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.packs.PackSelectionScreen; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.vehicle.minecart.Minecart; +import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.NonNull; import org.lwjgl.glfw.GLFW; public abstract class AbstractMoveableScreen extends Screen { @@ -15,7 +24,7 @@ public abstract class AbstractMoveableScreen extends Screen { /** * Constructs a AbstractMoveableScreen object. */ - public AbstractMoveableScreen(Text title, WidgetRenderer renderer) { + public AbstractMoveableScreen(Component title, WidgetRenderer renderer) { super(title); this.widgetRenderer = renderer; } @@ -26,92 +35,96 @@ protected void init() { } @Override - public void onDisplayed() { - super.onDisplayed(); + public void added() { + super.added(); widgetRenderer.isInEditor = true; } @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - widgetRenderer.mouseDragged(mouseX, mouseY, button, deltaX, deltaY, GlobalConfig.get().getSnapSize()); - ContextMenuManager.getInstance().mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + public boolean mouseDragged(MouseButtonEvent event, double dx, double dy) { + widgetRenderer.mouseDragged(event.x(), event.y(), event.button(), + dx, dy, + GlobalConfig.get().getSnapSize()); + ContextMenuManager.getInstance().mouseDragged(event.x(), event.y(), event.button(), + dx, dy); + return super.mouseDragged(event, dx, dy); } @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (widgetRenderer.mouseClicked(mouseX, mouseY, button)) { - handleClickOnWidget(widgetRenderer.selectedWidget, mouseX, mouseY, button); + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + if (widgetRenderer.mouseClicked(event.x(), event.y(), event.button())) { + handleClickOnWidget(widgetRenderer.selectedWidget, event.x(), event.y(), event.button()); } - ContextMenuManager.getInstance().mouseClicked(mouseX, mouseY, button); - return super.mouseClicked(mouseX, mouseY, button); + ContextMenuManager.getInstance().mouseClicked(event.x(), event.y(), event.button()); + return super.mouseClicked(event, bl); } @Override - public boolean charTyped(char chr, int modifiers) { - widgetRenderer.charTyped(chr, modifiers); - ContextMenuManager.getInstance().charTyped(chr, modifiers); - return super.charTyped(chr, modifiers); + public boolean charTyped(CharacterEvent event) { + char c = Character.toString(event.codepoint()).charAt(0); + widgetRenderer.charTyped(c, event.modifiers()); + ContextMenuManager.getInstance().charTyped(c, event.modifiers()); + return super.charTyped(event); } @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - widgetRenderer.mouseReleased(mouseX, mouseY, button); - ContextMenuManager.getInstance().mouseReleased(mouseX, mouseY, button); - return super.mouseReleased(mouseX, mouseY, button); + public boolean mouseReleased(MouseButtonEvent event) { + widgetRenderer.mouseReleased(event.x(), event.y(), event.button()); + ContextMenuManager.getInstance().mouseReleased(event.x(), event.y(), event.button()); + return super.mouseReleased(event); } @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - widgetRenderer.keyPressed(keyCode, scanCode, modifiers); - ContextMenuManager.getInstance().keyPressed(keyCode, scanCode, modifiers); - if (widgetRenderer.selectedWidget != null && (keyCode == GLFW.GLFW_KEY_DELETE || keyCode == GLFW.GLFW_KEY_BACKSPACE)) { - // trayWidget.minimizeWidget(widgetRenderer.selectedWidget); + public boolean keyPressed(KeyEvent event) { + widgetRenderer.keyPressed(event.key(), event.scancode(), event.modifiers()); + ContextMenuManager.getInstance().keyPressed(event.key(), event.scancode(), event.modifiers()); + if (widgetRenderer.selectedWidget != null && + (event.key() == GLFW.GLFW_KEY_DELETE || event.key() == GLFW.GLFW_KEY_BACKSPACE)) { + // trayWidget.minimizeWidget(widgetRenderer.selectedWidget); } - - return super.keyPressed(keyCode, scanCode, modifiers); + return super.keyPressed(event); } @Override - public boolean keyReleased(int keyCode, int scanCode, int modifiers) { - widgetRenderer.keyReleased(keyCode, scanCode, modifiers); - ContextMenuManager.getInstance().keyReleased(keyCode, scanCode, modifiers); - return super.keyReleased(keyCode, scanCode, modifiers); + public boolean keyReleased(KeyEvent event) { + widgetRenderer.keyReleased(event.key(), event.scancode(), event.modifiers()); + ContextMenuManager.getInstance().keyReleased(event.key(), event.scancode(), event.modifiers()); + return super.keyReleased(event); } @Override - public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { - widgetRenderer.mouseScrolled(mouseX, mouseY, verticalAmount, horizontalAmount); - ContextMenuManager.getInstance().mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); - return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + public boolean mouseScrolled(double x, double y, double horizontalAmount, double verticalAmount) { + widgetRenderer.mouseScrolled(x, y, verticalAmount, horizontalAmount); + ContextMenuManager.getInstance().mouseScrolled(x, y, horizontalAmount, verticalAmount); + return super.mouseScrolled(x, y, horizontalAmount, verticalAmount); } /** * Renders this screen and its widgets on the screen. * - * @param drawContext The matrix stack used for rendering + * @param graphics The matrix stack used for rendering * @param mouseX The current x position of the mouse cursor * @param mouseY The current y position of the mouse cursor * @param delta The time elapsed since the last frame in seconds */ @Override - public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { - if (this.client.world == null) { - renderInGameBackground(drawContext); + public void render(@NonNull GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (this.minecraft.level == null) { + renderBackground(graphics,mouseX,mouseY,delta); } - drawContext.drawText(client.textRenderer, title, client.getWindow().getScaledWidth() / 2 - client.textRenderer.getWidth(title.getString()) / 2, textRenderer.fontHeight / 2, -1, true); + graphics.drawCenteredString(this.font, this.title, this.width / 2, this.font.lineHeight / 2,-1); // Draw each widget - widgetRenderer.renderWidgets(drawContext, mouseX, mouseY); + widgetRenderer.renderWidgets(graphics, mouseX, mouseY); - ContextMenuManager.getInstance().renderAll(drawContext, mouseX, mouseY); + ContextMenuManager.getInstance().renderAll(graphics, mouseX, mouseY); if (GlobalConfig.get().shouldDisplayDescriptions()) { for (Widget widget : widgetRenderer.getWidgets()) { if (widget == null || widget.isShiftDown) continue; if (widget.getWidgetBox().isMouseOver(mouseX, mouseY)) { - drawContext.drawTooltip(client.textRenderer, widget.tooltipText, mouseX, mouseY); + graphics.setTooltipForNextFrame(this.font, widget.tooltipText, mouseX, mouseY); break; } } @@ -122,16 +135,15 @@ public void handleClickOnWidget(Widget widget, double mouseX, double mouseY, int } @Override - public void close() { + public void onClose() { widgetRenderer.isInEditor = false; widgetRenderer.onCloseScreen(); ContextMenuManager.getInstance().onClose(); - super.close(); + super.onClose(); } @Override - public boolean shouldPause() { + public boolean isPauseScreen() { return false; } } - diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/CustomRenderLayers.java b/src/main/java/com/tanishisherewith/dynamichud/utils/CustomRenderLayers.java index df17c3d..8291909 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/CustomRenderLayers.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/CustomRenderLayers.java @@ -3,94 +3,123 @@ import com.mojang.blaze3d.pipeline.BlendFunction; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.platform.DestFactor; -import com.mojang.blaze3d.platform.LogicOp; import com.mojang.blaze3d.platform.SourceFactor; +import com.mojang.blaze3d.shaders.UniformType; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexFormatElement; -import com.tanishisherewith.dynamichud.internal.IRenderLayer; -import net.minecraft.client.gl.RenderPipelines; -import net.minecraft.client.gl.UniformType; -import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.render.VertexFormats; -import net.minecraft.client.util.math.Vector2f; -import net.minecraft.util.Identifier; -import net.minecraft.util.Util; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.resources.Identifier; import org.joml.Vector4f; -import java.awt.*; -import java.util.function.Function; public class CustomRenderLayers { - public static RenderLayer QUADS_CUSTOM_BLEND = RenderLayer.of( - "dynamichud/quads_custom_blend", - 1536, - false, - true, - RenderPipelines.register(RenderPipeline.builder(RenderPipelines.POSITION_COLOR_SNIPPET) - .withLocation(Identifier.of("dynamichud", "pipeline/quad_custom_blend_func")) - .withVertexFormat(VertexFormats.POSITION_COLOR, VertexFormat.DrawMode.QUADS) - .withBlend(new BlendFunction(SourceFactor.DST_ALPHA, DestFactor.ONE_MINUS_DST_ALPHA)) - .build() - ), - RenderLayer.MultiPhaseParameters.builder().build(false) + public static final RenderPipeline COLOR_LINE = RenderPipeline.builder() + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "color_line")) + .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.DEBUG_LINES) + .withFragmentShader(Identifier.withDefaultNamespace("core/position_color")) + .withVertexShader(Identifier.withDefaultNamespace("core/position_color")) + .build(); + + public static final RenderPipeline COLOR_TRIANGLES = RenderPipeline.builder() + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "color_triangles")) + .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLES) + .withVertexShader(Identifier.withDefaultNamespace("core/position_color")) + .withFragmentShader(Identifier.withDefaultNamespace("core/position_color")) + .build(); + + public static RenderPipeline TRIANGLE_STRIP = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.GUI_SNIPPET) + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "pipeline/triangle_strip")) + .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLES) + .build() ); - public static RenderLayer TRIANGLE_FAN_CUSTOM_BLEND = RenderLayer.of( - "dynamichud/triangle_fan_custom_blend", - 1536, - false, - true, - RenderPipelines.register(RenderPipeline.builder(RenderPipelines.POSITION_COLOR_SNIPPET) - .withLocation(Identifier.of("dynamichud", "pipeline/triangle_fan_custom_blend_func")) - .withVertexFormat(VertexFormats.POSITION_COLOR, VertexFormat.DrawMode.TRIANGLE_FAN) - .withBlend(new BlendFunction(SourceFactor.DST_ALPHA, DestFactor.ONE_MINUS_DST_ALPHA)) + // Width/Height in UV1 + public static final VertexFormatElement ELM_WIDTH_HEIGHT = + new VertexFormatElement(1, 2, VertexFormatElement.Type.FLOAT, VertexFormatElement.Usage.UV, 8); + + // Roundness in UV2 + public static final VertexFormatElement ELM_ROUNDNESS = + new VertexFormatElement(2, 4, VertexFormatElement.Type.FLOAT, VertexFormatElement.Usage.UV, 16); + + public static final VertexFormat ROUNDED_FORMAT = VertexFormat.builder() + .add("Position", VertexFormatElement.POSITION) + .add("Color", VertexFormatElement.COLOR) + .add("UV0", VertexFormatElement.UV0) + .add("WidthHeight", ELM_WIDTH_HEIGHT) + .add("Roundness", ELM_ROUNDNESS) + .build(); + + + public static final RenderPipeline ROUNDED_RECT = RenderPipelines.register( + RenderPipeline.builder() + .withVertexFormat(ROUNDED_FORMAT, VertexFormat.Mode.QUADS) + .withBlend(new BlendFunction(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA)) + .withFragmentShader(Identifier.fromNamespaceAndPath("dynamichud", "core/rounded")) + .withVertexShader(Identifier.fromNamespaceAndPath("dynamichud", "core/rounded")) + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "pipeline/rounded")) + .withUniform("Roundness", UniformType.UNIFORM_BUFFER) + .withUniform("widthHeight", UniformType.UNIFORM_BUFFER) .build() - ), - RenderLayer.MultiPhaseParameters.builder().build(false) ); - public static RenderLayer TRIANGLE_STRIP = RenderLayer.of( - "dynamichud/triangle_strip", - 1536, - false, - true, - RenderPipelines.register(RenderPipeline.builder(RenderPipelines.POSITION_COLOR_SNIPPET) - .withLocation(Identifier.of("dynamichud", "pipeline/triangle_strip")) - .withVertexFormat(VertexFormats.POSITION_COLOR, VertexFormat.DrawMode.TRIANGLE_STRIP) - .withBlend(BlendFunction.TRANSLUCENT) + + public static final RenderPipeline ROUNDED_RECT_OUTLINE = RenderPipelines.register( + RenderPipeline.builder() + .withVertexFormat(ROUNDED_FORMAT, VertexFormat.Mode.QUADS) + .withBlend(new BlendFunction(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA)) + .withFragmentShader(Identifier.fromNamespaceAndPath("dynamichud", "core/rounded_outline")) + .withVertexShader(Identifier.fromNamespaceAndPath("dynamichud", "core/rounded")) + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "pipeline/rounded_outline")) + .withUniform("Roundness", UniformType.UNIFORM_BUFFER) + .withUniform("widthHeight", UniformType.UNIFORM_BUFFER) + .withUniform("Thickness", UniformType.UNIFORM_BUFFER) .build() - ), - RenderLayer.MultiPhaseParameters.builder().build(false) ); - private static final RenderPipeline ROUNDED = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.MATRICES_COLOR_SNIPPET) + public static RenderPipeline QUADS_CUSTOM_BLEND = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.GUI_SNIPPET) + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "pipeline/quad_custom_blend_func")) + .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS) + .withBlend(new BlendFunction(SourceFactor.DST_ALPHA, DestFactor.ONE_MINUS_DST_ALPHA)) + .build() + ); + + public static RenderPipeline TRIANGLE_FAN_CUSTOM_BLEND = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.GUI_SNIPPET) + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "pipeline/triangle_fan_custom_blend_func")) + .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLE_FAN) + // .withBlend(new BlendFunction(SourceFactor.DST_ALPHA, DestFactor.ONE_MINUS_DST_ALPHA)) + .build() + ); + + /* + private static final RenderPipeline ROUNDED = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.MATRICES_PROJECTION_SNIPPET) .withBlend(BlendFunction.TRANSLUCENT) .withVertexFormat(VertexFormat.builder() .add("Position", VertexFormatElement.POSITION) .add("UV0", VertexFormatElement.UV0) .add("Color", VertexFormatElement.COLOR) - .build(), VertexFormat.DrawMode.QUADS) + .build(), VertexFormat.Mode.QUADS) .withCull(true) .withColorLogic(LogicOp.NONE) - .withFragmentShader(Identifier.of("dynamichud", "core/rounded")) - .withVertexShader(Identifier.of("dynamichud", "core/rounded")) - .withLocation(Identifier.of("dynamichud", "pipeline/rounded")) - .withUniform("Roundness", UniformType.VEC4) - .withUniform("widthHeight", UniformType.VEC2) + .withFragmentShader(Identifier.fromNamespaceAndPath("dynamichud", "core/rounded")) + .withVertexShader(Identifier.fromNamespaceAndPath("dynamichud", "core/rounded")) + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "pipeline/rounded")) + .withUniform("Roundness", UniformType.UNIFORM_BUFFER) + .withUniform("widthHeight", UniformType.UNIFORM_BUFFER) .build()); - private static final RenderPipeline ROUNDED_OUTLINE = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.MATRICES_COLOR_SNIPPET) + private static final RenderPipeline ROUNDED_OUTLINE = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.MATRICES_PROJECTION_SNIPPET) .withBlend(BlendFunction.TRANSLUCENT) .withVertexFormat(VertexFormat.builder() .add("Position", VertexFormatElement.POSITION) .add("UV0", VertexFormatElement.UV0) .add("Color", VertexFormatElement.COLOR) - .build(), VertexFormat.DrawMode.QUADS) + .build(), VertexFormat.Mode.QUADS) .withCull(true) .withColorLogic(LogicOp.NONE) - .withFragmentShader(Identifier.of("dynamichud", "core/rounded_outline")) - .withVertexShader(Identifier.of("dynamichud", "core/rounded")) - .withLocation(Identifier.of("dynamichud", "pipeline/rounded_outline")) - .withUniform("Thickness", UniformType.VEC4) + .withFragmentShader(Identifier.fromNamespaceAndPath("dynamichud", "core/rounded_outline")) + .withVertexShader(Identifier.fromNamespaceAndPath("dynamichud", "core/rounded")) + .withLocation(Identifier.fromNamespaceAndPath("dynamichud", "pipeline/rounded_outline")) + .withUniform("Thickness", UniformType) .withUniform("Roundness", UniformType.VEC4) .withUniform("widthHeight", UniformType.VEC2) .build()); @@ -121,14 +150,15 @@ public class CustomRenderLayers { .build(false) ); ((IRenderLayer) rl).dynamichud$setUniform("Roundness", params.roundness); - ((IRenderLayer) rl).dynamichud$setUniform("Thickness", new Vector4f(params.thickness,0f,0f,0f)); + ((IRenderLayer) rl).dynamichud$setUniform("Thickness", new Vector4f(params.thickness, 0f, 0f, 0f)); ((IRenderLayer) rl).dynamichud$setUniform("widthHeight", params.widthHeight); return rl; }); + */ private static int getNewVertexFormatElementsId() { - for(int id = 0; id < 32; id++) { + for (int id = 0; id < 32; id++) { if (VertexFormatElement.byId(id) == null) { return id; } @@ -137,6 +167,9 @@ private static int getNewVertexFormatElementsId() { } - public record RoundedParameters(Vector4f roundness, float[] widthHeight){} - public record OutlineParameters(Vector4f roundness, float thickness, float[] widthHeight){} + public record RoundedParameters(Vector4f roundness, float[] widthHeight) { + } + + public record OutlineParameters(Vector4f roundness, float thickness, float[] widthHeight) { + } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java b/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java index 4210d61..9a10941 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java @@ -1,11 +1,17 @@ package com.tanishisherewith.dynamichud.utils; import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.OptionGroup; +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.Skin; +import org.apache.commons.text.similarity.FuzzyScore; + +import java.util.*; public class Util { public static Quadrant getQuadrant(int x, int y) { - int screenWidth = DynamicHUD.MC.getWindow().getScaledWidth(); - int screenHeight = DynamicHUD.MC.getWindow().getScaledHeight(); + int screenWidth = DynamicHUD.MC.getWindow().getGuiScaledWidth(); + int screenHeight = DynamicHUD.MC.getWindow().getGuiScaledHeight(); if (x < screenWidth / 2) { if (y < screenHeight / 2) { @@ -35,4 +41,95 @@ public static boolean warnIfTrue(boolean expression, String message, Object... o if (expression) DynamicHUD.logger.warn(message, objects); return expression; } + + /** + * Returns a list of options sorted by higher fuzzy score from the query string. + * If minimumScore is -1, then two typos from query string will be tolerated. + */ + public static List> getSearchResults(String query, int minimumScore, List> options) { + if(options.isEmpty()) return new ArrayList<>(); + + if (query == null || query.trim().isEmpty()) { + return new ArrayList<>(options); + } + + FuzzyScore FUZZY_SCORE = new FuzzyScore(Locale.ENGLISH); + + String lowerQuery = query.toLowerCase().trim(); + Map, Integer> scoreMap = new HashMap<>(); + + List> allOptions = Skin.flattenOptions(options); + + for (Option opt : allOptions) { + if (!opt.shouldRender()) continue; + String name = opt.getName().getString(); + String desc = opt.getDescription().getString(); + int nameScore = FUZZY_SCORE.fuzzyScore(name, lowerQuery); + int descScore = FUZZY_SCORE.fuzzyScore(desc, lowerQuery); + int best = Math.max(nameScore, descScore); + scoreMap.put(opt, best); + } + + + if (scoreMap.isEmpty()) return new ArrayList<>(); + + //Allow 2 typos for a min score of -1 using the query length + int threshold = (minimumScore == -1) ? lowerQuery.length() - 2: minimumScore; + + return filterAndSortOptions(options, threshold, scoreMap); + } + + /** + * Recursively processes a list of options, returning a filtered and sorted copy. + * Groups are re‑created with only matching children, and are expanded. + */ + private static List> filterAndSortOptions(List> source, int threshold, Map, Integer> scoreMap) { + List> result = new ArrayList<>(); + for (Option opt : source) { + if (opt instanceof OptionGroup group) { + // Process children first + List> filteredChildren = filterAndSortOptions(group.getGroupOptions(), threshold, scoreMap); + int groupScore = scoreMap.getOrDefault(group, 0); + boolean groupMatches = groupScore >= threshold; + + if (groupMatches || !filteredChildren.isEmpty()) { + OptionGroup newGroup = new OptionGroup(group.name); + newGroup.setExpanded(true); + for (Option child : filteredChildren) { + newGroup.addOption(child); + } + result.add(newGroup); + } + } else if (scoreMap.getOrDefault(opt, 0) >= threshold) { + result.add(opt); + } + } + + // sort by score descending + result.sort((a, b) -> { + int sa = getEffectiveScore(a, scoreMap); + int sb = getEffectiveScore(b, scoreMap); + return Integer.compare(sb, sa); + }); + return result; + } + + /** + * Returns the highest score among all options inside a group (or the group's own score). + */ + private static int getEffectiveScore(Option opt, Map, Integer> scoreMap) { + if (opt instanceof OptionGroup group) { + int max = scoreMap.getOrDefault(group, 0); + for (Option child : group.getGroupOptions()) { + max = Math.max(max, getEffectiveScore(child, scoreMap)); + } + return max; + } + return scoreMap.getOrDefault(opt, 0); + } + + + public static boolean isSafeToContinue() { + return DynamicHUD.MC.getWindow() != null && DynamicHUD.MC.font != null; + } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java index 48a2cf6..7b06d6c 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java @@ -1,26 +1,27 @@ package com.tanishisherewith.dynamichud.utils.contextmenu; import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.config.GlobalConfig; import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import com.tanishisherewith.dynamichud.helpers.animationhelper.EasingType; +import com.tanishisherewith.dynamichud.helpers.animationhelper.animations.ValueAnimation; import com.tanishisherewith.dynamichud.internal.System; import com.tanishisherewith.dynamichud.utils.Input; -import com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen.ContextMenuScreenFactory; -import com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen.ContextMenuScreenRegistry; -import com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen.DefaultContextMenuScreenFactory; +import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutEngine; +import com.tanishisherewith.dynamichud.utils.contextmenu.screen.factory.ContextMenuScreenFactory; +import com.tanishisherewith.dynamichud.utils.contextmenu.screen.ContextMenuScreenRegistry; +import com.tanishisherewith.dynamichud.utils.contextmenu.screen.factory.DefaultContextMenuScreenFactory; import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option; import com.tanishisherewith.dynamichud.widget.WidgetBox; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.util.math.MathHelper; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; import java.awt.*; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; import java.util.List; -import java.util.Objects; @SuppressWarnings({"rawtypes", "unchecked"}) public class ContextMenu implements Input { @@ -31,14 +32,18 @@ public class ContextMenu implements Input { protected final List> options = new ArrayList<>(); // The list of options in the context menu protected final ContextMenuScreenFactory screenFactory; public int x, y; - // Width is counted while the options are being rendered. - // FinalWidth is the width at the end of the count. + + protected LayoutEngine layoutEngine; + protected int width = 0; protected int height = 0, widgetHeight = 0; protected boolean shouldDisplay = false; - protected float scale = 0.0f; + protected float animScale = 0.0f; protected Screen parentScreen = null; protected boolean newScreenFlag = false; + protected float menuScale = 1.0f; + + private final ValueAnimation scaleAnimation; @Nullable private final ContextMenu parentMenu; @@ -62,6 +67,18 @@ public ContextMenu(int x, int y, @NotNull T properties, ContextMenuScreenFactory this.darkerBackgroundColor = properties.getBackgroundColor().darker().darker().darker().darker().darker().darker(); this.parentMenu = parentMenu; this.properties.getSkin().setContextMenu(this); + this.layoutEngine = new LayoutEngine(); + + this.scaleAnimation = new ValueAnimation(val-> animScale = val, 0.0f, 1.0f); + this.scaleAnimation.easing(EasingType.EASE_IN_CUBIC); + this.scaleAnimation.duration(GlobalConfig.get().getCmAnimationTimeInMs()); + this.scaleAnimation.onComplete(() -> { + if (animScale <= 0.0f && parentScreen != null && properties.getSkin().shouldCreateNewScreen()) { + DynamicHUD.MC.setScreen(parentScreen); + parentScreen = null; + } + }); + Screen dummy = screenFactory.create(this, properties); System.registerInstance(new ContextMenuScreenRegistry(dummy.getClass()), DynamicHUD.MOD_ID); @@ -72,39 +89,47 @@ public void addOption(Option option) { options.add(option); } - public void render(DrawContext drawContext, int x, int y, int mouseX, int mouseY) { + public float getMenuScale() { + return animScale * menuScale; + } + + public void setMenuScale(float menuScale) { + this.menuScale = Math.clamp(menuScale, 0.3f, 2.0f); + } + + public void render(GuiGraphics graphics, int xPos, int yPos, int mouseX, int mouseY) { if (newScreenFlag && screenFactory != null) { DynamicHUD.MC.setScreen(screenFactory.create(this, properties)); + newScreenFlag = false; return; } - this.x = x; - this.y = y + properties.getHeightOffset() + widgetHeight; + this.x = xPos; + this.y = yPos + properties.getHeightOffset() + widgetHeight; + update(); - if (scale <= 0.0f || newScreenFlag) return; - DrawHelper.scaleAndPosition(drawContext.getMatrices(), x, y, scale); + if (animScale <= 0.0f || newScreenFlag) return; + + DrawHelper.scaleAndPosition(graphics.pose(), this.x, this.y,this.width,this.height, getMenuScale()); properties.getSkin().setContextMenu(this); - properties.getSkin().renderContextMenu(drawContext, this, mouseX, mouseY); + properties.getSkin().renderContextMenu(graphics, this, (int)getTMouseX(mouseX),(int) getTMouseY(mouseY)); - DrawHelper.stopScaling(drawContext.getMatrices()); + DrawHelper.stopScaling(graphics.pose()); } public void update() { - if (!properties.enableAnimations()) { - scale = 1.0f; - return; + if (layoutEngine != null) { + layoutEngine.applyLayout(this); } - // Update the scale - if (shouldDisplay) { - scale += 0.1f; - } else { - scale -= 0.1f; + if (!properties.enableAnimations()) { + animScale = shouldDisplay ? 1.0f : 0.0f; + return; } - scale = MathHelper.clamp(scale, 0, 1.0f); + scaleAnimation.update(); } public void close() { @@ -113,18 +138,31 @@ public void close() { for (Option option : options) { option.onClose(); } - if (properties.getSkin().shouldCreateNewScreen() && scale <= 0 && parentScreen != null) { - DynamicHUD.MC.setScreen(parentScreen); + if (properties.enableAnimations()) { + scaleAnimation.set(animScale,0.0f).start(); + } else { + scaleAnimation.setValue(0.0f); + if (properties.getSkin().shouldCreateNewScreen() && parentScreen != null) { + DynamicHUD.MC.setScreen(parentScreen); + parentScreen = null; + } } } public void open() { shouldDisplay = true; update(); - parentScreen = DynamicHUD.MC.currentScreen; + if(parentScreen == null) { + parentScreen = DynamicHUD.MC.screen; + } if (properties.getSkin().shouldCreateNewScreen()) { newScreenFlag = true; } + if (properties.enableAnimations()) { + scaleAnimation.set(animScale, 1.0f).start(); + } else { + scaleAnimation.setValue(1.0f); + } } public void toggleDisplay() { @@ -135,10 +173,20 @@ public void toggleDisplay() { } } - public void toggleDisplay(WidgetBox widgetBox, double mouseX, double mouseY, int button) { + protected double getTMouseX(double mouseX) { + return mouseX / getMenuScale(); + } + + protected double getTMouseY(double mouseY) { + return mouseY / getMenuScale(); + } + + public boolean toggleDisplay(WidgetBox widgetBox, double mouseX, double mouseY, int button) { if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT && widgetBox.isMouseOver(mouseX, mouseY)) { toggleDisplay(); + return true; } + return false; } public void resetAllOptions() { @@ -147,11 +195,22 @@ public void resetAllOptions() { } } + public LayoutEngine getLayoutEngine() { + return layoutEngine; + } + + public void setLayoutEngine(LayoutEngine layoutEngine) { + this.layoutEngine = layoutEngine; + } + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (!shouldDisplay) return false; + for (Option option : options) { - option.getRenderer().mouseClicked(option, mouseX, mouseY, button); + if (option.shouldRender() && option.getRenderer().mouseClicked(option ,getTMouseX(mouseX), getTMouseY(mouseY), button)) { + return true; + } } return properties.getSkin().mouseClicked(this, mouseX, mouseY, button); } @@ -160,7 +219,9 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { public boolean mouseReleased(double mouseX, double mouseY, int button) { if (!shouldDisplay) return false; for (Option option : options) { - option.getRenderer().mouseReleased(option, mouseX, mouseY, button); + if(option.shouldRender()){ + option.getRenderer().mouseReleased(option, getTMouseX(mouseX), getTMouseY(mouseY), button); + } } return properties.getSkin().mouseReleased(this, mouseX, mouseY, button); } @@ -168,8 +229,14 @@ public boolean mouseReleased(double mouseX, double mouseY, int button) { @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { if (!shouldDisplay) return false; + + double tDeltaX = deltaX / getMenuScale(); + double tDeltaY = deltaY / getMenuScale(); + for (Option option : options) { - option.getRenderer().mouseDragged(option, mouseX, mouseY, button, deltaX, deltaY); + if(option.shouldRender() && option.getRenderer().mouseDragged(option, getTMouseX(mouseX), getTMouseY(mouseY), button, tDeltaX, tDeltaY)){ + return true; + } } return properties.getSkin().mouseDragged(this, mouseX, mouseY, button, deltaX, deltaY); } @@ -178,7 +245,9 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double del public void keyPressed(int key, int scanCode, int modifiers) { if (!shouldDisplay) return; for (Option option : options) { - option.getRenderer().keyPressed(option, key, scanCode, modifiers); + if(option.shouldRender()){ + option.getRenderer().keyPressed(option, key, scanCode, modifiers); + } } properties.getSkin().keyPressed(this, key, scanCode, modifiers); @@ -188,7 +257,9 @@ public void keyPressed(int key, int scanCode, int modifiers) { public void keyReleased(int key, int scanCode, int modifiers) { if (!shouldDisplay) return; for (Option option : options) { - option.getRenderer().keyReleased(option, key, scanCode, modifiers); + if(option.shouldRender()) { + option.getRenderer().keyReleased(option, key, scanCode, modifiers); + } } properties.getSkin().keyReleased(this, key, scanCode, modifiers); @@ -198,7 +269,9 @@ public void keyReleased(int key, int scanCode, int modifiers) { public void mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { if (!shouldDisplay) return; for (Option option : options) { - option.getRenderer().mouseScrolled(option, mouseX, mouseY, horizontalAmount, verticalAmount); + if(option.shouldRender()) { + option.getRenderer().mouseScrolled(option, mouseX, mouseY, horizontalAmount, verticalAmount); + } } properties.getSkin().mouseScrolled(this, mouseX, mouseY, horizontalAmount, verticalAmount); } @@ -206,9 +279,14 @@ public void mouseScrolled(double mouseX, double mouseY, double horizontalAmount, @Override public void charTyped(char c, int modifiers) { if (!shouldDisplay) return; + for (Option option : options) { - option.charTyped(c, modifiers); + if(option.shouldRender()) { + option.charTyped(c, modifiers); + } } + + properties.getSkin().charTyped(this, c, modifiers); } public void set(int x, int y, int widgetHeight) { @@ -262,10 +340,6 @@ public ContextMenu createSubMenu(int x, int return new ContextMenu<>(x, y, properties, screenFactory, this); } - public float getScale() { - return scale; - } - public boolean isVisible() { return shouldDisplay; } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuManager.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuManager.java index 70edde9..c2b05f7 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuManager.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuManager.java @@ -1,7 +1,7 @@ package com.tanishisherewith.dynamichud.utils.contextmenu; import com.tanishisherewith.dynamichud.utils.Input; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.GuiGraphics; import java.util.ArrayList; import java.util.List; @@ -21,11 +21,11 @@ public void registerProvider(ContextMenuProvider provider) { providers.add(provider); } - public void renderAll(DrawContext drawContext, int mouseX, int mouseY) { + public void renderAll(GuiGraphics graphics, int mouseX, int mouseY) { for (ContextMenuProvider provider : providers) { ContextMenu contextMenu = provider.getContextMenu(); if (contextMenu != null) { - contextMenu.render(drawContext, contextMenu.getX(), contextMenu.getY(), mouseX, mouseY); + contextMenu.render(graphics, contextMenu.getX(), contextMenu.getY(), mouseX, mouseY); } } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProperties.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProperties.java index 3e45992..bbb23b9 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProperties.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProperties.java @@ -10,6 +10,7 @@ */ public class ContextMenuProperties { protected Color backgroundColor = new Color(107, 112, 126, 124); + protected Color accentColor = new Color(104, 151, 187, 255); // Color.CYAN.darker().darker(); protected Color borderColor = Color.BLACK; protected float borderWidth = 1f; protected int padding = 5; // The amount of padding around the rectangle @@ -91,6 +92,10 @@ public Skin getSkin() { return skin; } + public Color getAccentColor() { + return accentColor; + } + /** * @return Cloned object of every property except skin */ @@ -144,6 +149,11 @@ public Builder backgroundColor(Color backgroundColor) { return this; } + public Builder accentColor(Color accentColor) { + properties.accentColor = accentColor; + return this; + } + public Builder borderColor(Color borderColor) { properties.borderColor = borderColor; return this; diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreen.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreen.java deleted file mode 100644 index 1ccfce4..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreen.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen; - -import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; - -public class ContextMenuScreen extends Screen { - ContextMenu contextMenu; - ContextMenuProperties properties; - - protected ContextMenuScreen(ContextMenu menu, ContextMenuProperties properties) { - super(Text.of("ContextMenu screen")); - this.contextMenu = menu; - this.properties = properties; - } - - @Override - public void onDisplayed() { - super.onDisplayed(); - contextMenu.setVisible(true); - } - - @Override - public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { - contextMenu.update(); - DrawHelper.scaleAndPosition(drawContext.getMatrices(), (float) width / 2, (float) height / 2, contextMenu.getScale()); - - properties.getSkin().setContextMenu(contextMenu); - properties.getSkin().renderContextMenu(drawContext, contextMenu, mouseX, mouseY); - - DrawHelper.stopScaling(drawContext.getMatrices()); - - if (contextMenu.getScale() <= 0 && !contextMenu.isVisible()) { - contextMenu.close(); - } - } - - @Override - protected void renderDarkening(DrawContext context) { - - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - contextMenu.mouseClicked(mouseX, mouseY, button); - return super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - contextMenu.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { - contextMenu.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); - return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); - } - - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - contextMenu.mouseReleased(mouseX, mouseY, button); - return super.mouseReleased(mouseX, mouseY, button); - } - - @Override - public boolean keyReleased(int keyCode, int scanCode, int modifiers) { - contextMenu.keyReleased(keyCode, scanCode, modifiers); - return super.keyReleased(keyCode, scanCode, modifiers); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - contextMenu.keyPressed(keyCode, scanCode, modifiers); - return super.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public void close() { - contextMenu.close(); - contextMenu.setVisible(false); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/layout/LayoutContext.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/layout/LayoutContext.java deleted file mode 100644 index fb3d782..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/layout/LayoutContext.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu.layout; - -import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option; - -//This was supposed to be used for something bigger but that got scraped. -public class LayoutContext { - private final Offset indent; - private final Option parentOption; - - public LayoutContext() { - this(Offset.zero(), null); - } - - public LayoutContext(Offset margin, Option parentOption) { - this.indent = margin; - this.parentOption = parentOption; - } - - public Offset getIndent() { - return indent; - } - - public Option getParentOption() { - return parentOption; - } - - // Builder-style methods for creating new contexts - public LayoutContext withIndent(Offset indent) { - return new LayoutContext(indent, this.parentOption); - } - - public LayoutContext withParent(Option parent) { - return new LayoutContext(this.indent, parent); - } - - public LayoutContext addIndent(Offset additionalIndent) { - return new LayoutContext( - this.indent.add(additionalIndent), - this.parentOption - ); - } - - // Utility methods for calculating positions - public int getEffectiveX(int baseX) { - return baseX + indent.left; - } - - public int getEffectiveY(int baseY) { - return baseY + indent.top; - } - - public int getEffectiveWidth(int baseWidth) { - return baseWidth + indent.left; - } - - public int getEffectiveHeight(int baseHeight) { - return baseHeight + indent.top; - } - - public static class Offset { - public final int left; - public final int top; - - public Offset(int all) { - this(all, all); - } - - public Offset(int horizontal, int vertical) { - this.left = horizontal; - this.top = vertical; - } - - public static Offset zero() { - return new Offset(0); - } - - public Offset add(Offset other) { - return new Offset( - this.left + other.left, - this.top + other.top - ); - } - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/layout/LayoutEngine.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/layout/LayoutEngine.java new file mode 100644 index 0000000..7009c54 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/layout/LayoutEngine.java @@ -0,0 +1,149 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.layout; +import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option; +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.Skin; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import org.joml.Vector2d; + +import java.util.List; + +public class LayoutEngine { + private int horizontalPadding = 8; + private int verticalPadding = 4; + private int itemSpacing = 2; + private int minWidth = 80; + private LayoutStrategy activeStrategy; + + public LayoutEngine() {} + + public LayoutEngine(int horizontalPadding, int verticalPadding, int itemSpacing, int minWidth) { + this(horizontalPadding,verticalPadding,itemSpacing,minWidth,new VerticalFlowStrategy()); + } + + public LayoutEngine(int horizontalPadding, int verticalPadding, int itemSpacing, int minWidth, LayoutStrategy layoutStrategy) { + this.horizontalPadding = horizontalPadding; + this.verticalPadding = verticalPadding; + this.itemSpacing = itemSpacing; + this.minWidth = minWidth; + this.activeStrategy = layoutStrategy; + } + + @FunctionalInterface + public interface LayoutStrategy { + /** + * Computes dimensions and positions options within the context menu. + */ + void layout(ContextMenu menu, LayoutEngine engine); + } + + /** + * Executes the currently active layout strategy to position and size options dynamically. + */ + public void applyLayout(ContextMenu menu) { + if (activeStrategy != null) { + activeStrategy.layout(menu, this); + } + } + + /** + * Helper function for immediate layouts + * Automatically updates the option's dimensions and returns the updated Y position for the next element. + * + * @param option option to arrange. + * @param x start X coordinate. + * @param y start Y coordinate. + * @param targetWidth horizontal width for the option. + * @return next Y coordinate. + */ + public int layoutOption(Option option, int x, int y, int targetWidth) { + if (option == null || !option.shouldRender()) return y; + + int itemHeight = option.getHeight() > 0 ? option.getHeight() : DynamicHUD.MC.font.lineHeight; + + option.setPosition(x, y); + option.setWidth(targetWidth); + option.setHeight(itemHeight); + + return y + itemHeight + itemSpacing; + } + + + public LayoutStrategy getActiveStrategy() { + return activeStrategy; + } + + public void setActiveStrategy(LayoutStrategy activeStrategy) { + this.activeStrategy = activeStrategy; + } + + public int getHorizontalPadding() { return horizontalPadding; } + public void setHorizontalPadding(int horizontalPadding) { this.horizontalPadding = horizontalPadding; } + + public int getVerticalPadding() { return verticalPadding; } + public void setVerticalPadding(int verticalPadding) { this.verticalPadding = verticalPadding; } + + public int getItemSpacing() { return itemSpacing; } + public void setItemSpacing(int itemSpacing) { this.itemSpacing = itemSpacing; } + + public int getMinWidth() { return minWidth; } + public void setMinWidth(int minWidth) { this.minWidth = minWidth; } + + /** + * Default layout strategy that arranges items in a single vertical column. + * Automatically scales the menu width to match the longest option. + */ + public static class VerticalFlowStrategy implements LayoutStrategy { + @Override + public void layout(ContextMenu menu, LayoutEngine engine) { + if (menu == null) return; + + Font font = Minecraft.getInstance().font; + List> visibleOptions = menu.getProperties().getSkin().getOptions(menu); + int paddingValue = menu.getProperties().getPadding(); + + // width calc + int maxInnerWidth = engine.getMinWidth(); + for (Option option : visibleOptions) { + if (!option.shouldRender()) continue; + int preferredWidth = option.getWidth() > 0 ? option.getWidth() : font.width(option.getName()); + maxInnerWidth = Math.max(maxInnerWidth, preferredWidth); + } + + int totalMenuWidth = maxInnerWidth + (engine.getHorizontalPadding() * 2) + paddingValue; + + // height calc + int currentY = menu.getY() + engine.getVerticalPadding() + 3; + int currentX = menu.getX() + engine.getHorizontalPadding(); + int targetWidth = totalMenuWidth - (engine.getHorizontalPadding() * 2) - paddingValue; + + for (Option option : visibleOptions) { + if (!option.shouldRender()) continue; + currentY = engine.layoutOption(option, currentX, currentY, targetWidth); + } + + menu.setWidth(totalMenuWidth); + menu.setHeight(currentY - menu.getY()); + } + } + + public record Offset(int left, int top) { + public Offset(int all) { + this(all, all); + } + + public static Offset zero() { + return new Offset(0); + } + + public Offset add(Offset other) { + return new Offset( + this.left + other.left, + this.top + other.top + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/BooleanOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/BooleanOption.java index d5f3b55..f0f0056 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/BooleanOption.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/BooleanOption.java @@ -1,8 +1,8 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options; import com.tanishisherewith.dynamichud.utils.BooleanPool; -import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Text; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import org.lwjgl.glfw.GLFW; import java.util.function.Consumer; @@ -12,21 +12,20 @@ public class BooleanOption extends Option { private final BooleanType booleanType; - public BooleanOption(Text name, Supplier getter, Consumer setter, BooleanType booleanType) { + public BooleanOption(Component name, Supplier getter, Consumer setter, BooleanType booleanType) { super(name, getter, setter); this.booleanType = booleanType; - this.renderer.init(this); } - public BooleanOption(Text name, Supplier getter, Consumer setter) { + public BooleanOption(Component name, Supplier getter, Consumer setter) { this(name, getter, setter, BooleanType.TRUE_FALSE); } - public BooleanOption(Text name, boolean defaultValue) { + public BooleanOption(Component name, boolean defaultValue) { this(name, defaultValue, BooleanType.TRUE_FALSE); } - public BooleanOption(Text name, boolean defaultValue, BooleanType type) { + public BooleanOption(Component name, boolean defaultValue, BooleanType type) { this(name, () -> BooleanPool.get(name.getString()), value -> BooleanPool.put(name.getString(), value), type); BooleanPool.put(name.getString(), defaultValue); } @@ -46,17 +45,17 @@ public BooleanType getBooleanType() { } public enum BooleanType { - ON_OFF(ScreenTexts::onOrOff), - TRUE_FALSE(aBoolean -> aBoolean ? Text.of("True") : Text.of("False")), - YES_NO(aBoolean -> aBoolean ? ScreenTexts.YES : ScreenTexts.NO); + ON_OFF(aBoolean -> aBoolean ? CommonComponents.OPTION_ON : CommonComponents.OPTION_OFF), + TRUE_FALSE(aBoolean -> aBoolean ? Component.literal("True") : Component.literal("False")), + YES_NO(aBoolean -> aBoolean ? CommonComponents.GUI_YES : CommonComponents.GUI_NO); - private final Function function; + private final Function function; - BooleanType(Function function) { + BooleanType(Function function) { this.function = function; } - public Text getText(boolean val) { + public Component getText(boolean val) { return function.apply(val); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ColorOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ColorOption.java index 9471f7c..9f4eb93 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ColorOption.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ColorOption.java @@ -2,7 +2,7 @@ import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; import com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption.ColorGradient; -import net.minecraft.text.Text; +import net.minecraft.network.chat.Component; import java.awt.*; import java.util.function.Consumer; @@ -13,11 +13,10 @@ public class ColorOption extends Option { private ContextMenu parentMenu = null; private ColorGradient colorGradient = null; - public ColorOption(Text name, Supplier getter, Consumer setter, ContextMenu parentMenu) { + public ColorOption(Component name, Supplier getter, Consumer setter, ContextMenu parentMenu) { super(name, getter, setter); this.parentMenu = parentMenu; this.colorGradient = new ColorGradient(x + this.parentMenu.getWidth(), y - 10, get(), this::set, 50, 100); - this.renderer.init(this); } @Override @@ -30,21 +29,19 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } else { colorGradient.close(); } + return true; } - colorGradient.mouseClicked(mouseX, mouseY, button); - return super.mouseClicked(mouseX, mouseY, button); + return colorGradient.mouseClicked(mouseX, mouseY, button); } @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { - colorGradient.mouseReleased(mouseX, mouseY, button); - return super.mouseReleased(mouseX, mouseY, button); + return colorGradient.mouseReleased(mouseX, mouseY, button); } @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - colorGradient.mouseDragged(mouseX, mouseY, button); - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + return colorGradient.mouseDragged(mouseX, mouseY, button); } public ColorGradient getColorGradient() { diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/CycleOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/CycleOption.java new file mode 100644 index 0000000..fa3a158 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/CycleOption.java @@ -0,0 +1,60 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.options; + +import net.minecraft.network.chat.Component; +import org.lwjgl.glfw.GLFW; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class CycleOption extends Option { + protected final List values; + protected int currentIndex; + + @SafeVarargs + public CycleOption(Component name, Supplier getter, Consumer setter, T... values) { + this(name, getter, setter, Arrays.asList(values)); + } + + public CycleOption(Component name, Supplier getter, Consumer setter, List values) { + super(name, getter, setter); + this.values = values; + this.currentIndex = values.indexOf(value); + if (currentIndex == -1) { + for (int i = 0; i < values.size(); i++) { + if (values.get(i).toString().equals(value)) { + currentIndex = i; + break; + } + } + } + } + + public void cycle(int direction) { + currentIndex = (currentIndex + direction + values.size()) % values.size(); + if (currentIndex < 0) currentIndex += values.size(); + + set(values.get(currentIndex)); + } + + public T getCurrentValue() { return value; } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) { + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + cycle(1); + } else if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { + cycle(-1); + } + set(value); + return true; + } + return false; + } + + public List getValues() { + return values; + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/DoubleOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/DoubleOption.java index 27d8ec6..e7e5d25 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/DoubleOption.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/DoubleOption.java @@ -2,8 +2,8 @@ import com.tanishisherewith.dynamichud.helpers.DrawHelper; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.text.Text; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; import org.apache.commons.lang3.Validate; import org.lwjgl.glfw.GLFW; @@ -18,7 +18,7 @@ public class DoubleOption extends Option { ContextMenu parentMenu; private boolean isDragging = false; - public DoubleOption(Text name, double minValue, double maxValue, float step, Supplier getter, Consumer setter, ContextMenu parentMenu) { + public DoubleOption(Component name, double minValue, double maxValue, float step, Supplier getter, Consumer setter, ContextMenu parentMenu) { super(name, getter, setter); this.value = get(); this.minValue = minValue; @@ -28,13 +28,12 @@ public DoubleOption(Text name, double minValue, double maxValue, float step, Sup this.step = step; this.parentMenu = parentMenu; Validate.isTrue(this.step > 0.0f, "Step cannot be less than or equal to 0 (zero)"); - this.renderer.init(this); } - public void drawSlider(DrawContext drawContext, int sliderX, int sliderY, int sliderWidth, double handleX) { - DrawHelper.drawRectangle(drawContext, sliderX, sliderY, sliderWidth, 2, 0xFFFFFFFF); + public void drawSlider(GuiGraphics graphics, int sliderX, int sliderY, int sliderWidth, double handleX) { + DrawHelper.drawRectangle(graphics, sliderX, sliderY, sliderWidth, 2, 0xFFFFFFFF); if (handleX - sliderX > 0) { - DrawHelper.drawRectangle(drawContext, (float) sliderX, (float) sliderY, (float) ((value - minValue) / (maxValue - minValue) * (width - 3)), 2, Color.ORANGE.getRGB()); + DrawHelper.drawRectangle(graphics, (float) sliderX, (float) sliderY, (float) ((value - minValue) / (maxValue - minValue) * (width - 3)), 2, Color.ORANGE.getRGB()); } } @@ -43,8 +42,9 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { if (super.mouseClicked(mouseX, mouseY, button) && button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { step(mouseX); isDragging = true; + return true; } - return true; + return false; } @Override @@ -57,7 +57,7 @@ private void step(double mouseX) { this.step(mouseX, x); } - public void step(double mouseX, double x) { + public void step(double mouseX, double x, double width) { double newValue = minValue + (float) (mouseX - x) / width * (maxValue - minValue); // Round the new value to the nearest step newValue = Math.round(newValue / step) * step; @@ -66,10 +66,16 @@ public void step(double mouseX, double x) { } + public void step(double mouseX, double x) { + this.step(mouseX, x, width); + } + + @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { if (isMouseOver(mouseX, mouseY) && isDragging) { step(mouseX); + return true; } return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/EnumOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/EnumOption.java deleted file mode 100644 index 1eab358..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/EnumOption.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu.options; - -import net.minecraft.text.Text; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class EnumOption> extends Option { - private final E[] values; - private int currentIndex = 0; - - public EnumOption(Text name, Supplier getter, Consumer setter, E[] values) { - super(name, getter, setter); - this.values = values; - this.value = get(); - for (int i = 0; i < values.length; i++) { - if (values[i] == value) { - currentIndex = i; - break; - } - } - this.renderer.init(this); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (super.mouseClicked(mouseX, mouseY, button)) { - if (button == 0) { - currentIndex = (currentIndex + 1) % values.length; - if (currentIndex > values.length - 1) { - currentIndex = 0; - } - value = values[currentIndex]; - } else if (button == 1) { - currentIndex = (currentIndex - 1) % values.length; - if (currentIndex < 0) { - currentIndex = values.length - 1; - } - value = values[currentIndex]; - } - set(value); - } - return true; - } - - public E[] getValues() { - return values; - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ListOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ListOption.java deleted file mode 100644 index d407242..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ListOption.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu.options; - -import net.minecraft.text.Text; - -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class ListOption extends Option { - private final List values; - private int currentIndex = 0; - - public ListOption(Text name, Supplier getter, Consumer setter, List values) { - super(name, getter, setter); - this.values = values; - this.value = getter.get(); - for (int i = 0; i < values.size(); i++) { - if (values.get(i).toString().equals(value)) { - currentIndex = i; - break; - } - } - this.renderer.init(this); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (super.mouseClicked(mouseX, mouseY, button)) { - if (button == 0) { - currentIndex = (currentIndex + 1) % values.size(); - if (currentIndex > values.size() - 1) { - currentIndex = 0; - } - value = values.get(currentIndex); - } else if (button == 1) { - currentIndex = (currentIndex - 1) % values.size(); - if (currentIndex < 0) { - currentIndex = values.size() - 1; - } - value = values.get(currentIndex); - } - set(value); - } - return true; - } - - public List getValues() { - return values; - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/Option.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/Option.java index be4bf94..a42d598 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/Option.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/Option.java @@ -5,15 +5,15 @@ import com.tanishisherewith.dynamichud.utils.Input; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.SkinRenderer; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.text.Text; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; import java.util.function.Consumer; import java.util.function.Supplier; public abstract class Option implements Input { - public Text name, description = Text.empty(); + public Component name, description = Component.empty(); public T value = null; protected int x, y; protected int width = 0; @@ -22,26 +22,26 @@ public abstract class Option implements Input { protected Supplier getter; protected Consumer setter; protected T defaultValue = null; - protected MinecraftClient mc = MinecraftClient.getInstance(); + protected Minecraft mc = Minecraft.getInstance(); protected ContextMenuProperties properties; protected SkinRenderer> renderer; protected Complexity complexity = Complexity.Simple; - public Option(Text name, Supplier getter, Consumer setter) { + public Option(Component name, Supplier getter, Consumer setter) { this(name, getter, setter, () -> true); } - public Option(Text name, Supplier getter, Consumer setter, Supplier shouldRender, ContextMenuProperties properties) { + public Option(Component name, Supplier getter, Consumer setter, Supplier shouldRender, ContextMenuProperties properties) { this.name = name; this.getter = getter; this.setter = setter; this.shouldRender = shouldRender; this.value = get(); this.defaultValue = get(); - updateProperties(properties); + this.updateProperties(properties); } - public Option(Text name, Supplier getter, Consumer setter, Supplier shouldRender) { + public Option(Component name, Supplier getter, Consumer setter, Supplier shouldRender) { this(name, getter, setter, shouldRender, ContextMenuProperties.createGenericSimplified()); } @@ -61,15 +61,16 @@ public void updateProperties(ContextMenuProperties properties) { DynamicHUD.logger.error("Renderer not found for class: {} in the following skin: {}", this.getClass().getName(), properties.getSkin()); throw new RuntimeException(); } + this.renderer.init(this); } - public void render(DrawContext drawContext, int x, int y, int mouseX, int mouseY) { + public void render(GuiGraphics graphics, int x, int y, int mouseX, int mouseY) { this.x = x; this.y = y; this.value = get(); // Retrieve the renderer and ensure it is not null - renderer.render(drawContext, this, x, y, mouseX, mouseY); + renderer.render(graphics, this, x, y, mouseX, mouseY); } public boolean mouseClicked(double mouseX, double mouseY, int button) { @@ -163,7 +164,7 @@ public void setPosition(int x, int y) { this.y = y; } - public Option description(Text description) { + public Option description(Component description) { this.description = description; return this; } @@ -177,11 +178,11 @@ public SkinRenderer> getRenderer() { return renderer; } - public Text getName() { + public Component getName() { return name; } - public Text getDescription() { + public Component getDescription() { return description; } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/OptionGroup.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/OptionGroup.java index 99097fd..d2fea9c 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/OptionGroup.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/OptionGroup.java @@ -2,19 +2,20 @@ import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.SkinRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.text.Text; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; import java.util.ArrayList; import java.util.Collections; import java.util.List; // A group is just another type of Option that contains other options +@SuppressWarnings({"rawtypes", "unchecked"}) public class OptionGroup extends Option { private final List> groupOptions = new ArrayList<>(); protected boolean expanded; // Skins can choose to use this or ignore it - public OptionGroup(Text name) { + public OptionGroup(Component name) { super(name, () -> null, (v) -> {}, () -> true); this.expanded = false; } @@ -27,6 +28,7 @@ public List> getGroupOptions() { return Collections.unmodifiableList(groupOptions); } + @Override public void updateProperties(ContextMenuProperties properties) { super.updateProperties(properties); @@ -38,8 +40,8 @@ public void updateProperties(ContextMenuProperties properties) { } @Override - public void render(DrawContext drawContext, int x, int y, int mouseX, int mouseY) { - super.render(drawContext,x,y,mouseX,mouseY); + public void render(GuiGraphics graphics, int x, int y, int mouseX, int mouseY) { + super.render(graphics,x,y,mouseX,mouseY); } public boolean isExpanded() { @@ -65,16 +67,20 @@ public int getHeight() { public static class OptionGroupRenderer implements SkinRenderer> { @Override - public void render(DrawContext drawContext, Option option, int x, int y, int mouseX, int mouseY) {} + public void render(GuiGraphics graphics, Option option, int x, int y, int mouseX, int mouseY) {} @Override public boolean mouseClicked(Option option2, double mouseX, double mouseY, int button) { OptionGroup option = (OptionGroup) option2; - for (Option subOption : option.getGroupOptions()) { - subOption.getRenderer().mouseClicked(subOption, mouseX, mouseY, button); + if (option.isExpanded()) { + for (Option subOption : option.getGroupOptions()) { + if (subOption.getRenderer().mouseClicked(subOption, mouseX, mouseY, button)) { + return true; + } + } } - return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); + return false; } @Override @@ -82,9 +88,9 @@ public boolean mouseReleased(Option option2, double mouseX, double OptionGroup option = (OptionGroup) option2; for (Option subOption : option.getGroupOptions()) { - subOption.getRenderer().mouseReleased(subOption, mouseX, mouseY, button); + if(subOption.getRenderer().mouseReleased(subOption, mouseX, mouseY, button)) return true; } - return SkinRenderer.super.mouseReleased(option, mouseX, mouseY, button); + return false; } @Override @@ -92,9 +98,9 @@ public boolean mouseDragged(Option option2, double mouseX, double m OptionGroup option = (OptionGroup) option2; for (Option subOption : option.getGroupOptions()) { - subOption.getRenderer().mouseDragged(subOption, mouseX, mouseY, button, deltaX, deltaY); + if(subOption.getRenderer().mouseDragged(subOption, mouseX, mouseY, button, deltaX, deltaY)) return true; } - return SkinRenderer.super.mouseDragged(option, mouseX, mouseY, button, deltaX, deltaY); + return false; } @Override diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/RunnableOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/RunnableOption.java index 8bc80a1..21a5ed0 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/RunnableOption.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/RunnableOption.java @@ -2,7 +2,7 @@ import com.tanishisherewith.dynamichud.DynamicHUD; import com.tanishisherewith.dynamichud.utils.BooleanPool; -import net.minecraft.text.Text; +import net.minecraft.network.chat.Component; import java.util.function.Consumer; import java.util.function.Supplier; @@ -18,14 +18,13 @@ public class RunnableOption extends Option { * @param setter Return a boolean based on if the task is running or not. * @param task The task to run */ - public RunnableOption(Text name, Supplier getter, Consumer setter, Runnable task) { + public RunnableOption(Component name, Supplier getter, Consumer setter, Runnable task) { super(name, getter, setter); this.name = name; this.task = task; - this.renderer.init(this); } - public RunnableOption(Text name, boolean defaultValue, Runnable task) { + public RunnableOption(Component name, boolean defaultValue, Runnable task) { this(name, () -> BooleanPool.get(name.getString()), value -> BooleanPool.put(name.getString(), value), task); BooleanPool.put(name.getString(), defaultValue); } @@ -33,15 +32,19 @@ public RunnableOption(Text name, boolean defaultValue, Runnable task) { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (super.mouseClicked(mouseX, mouseY, button)) { - set(true); - try { - task.run(); - } catch (Throwable e) { - DynamicHUD.logger.error("Encountered error while running task for {}", this.name, e); - } - set(false); + toggle(); return true; } - return true; + return false; + } + + public void toggle(){ + set(true); + try { + task.run(); + } catch (Throwable e) { + DynamicHUD.logger.error("Encountered error while running task for {}", this.name, e); + } + set(false); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/SubMenuOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/SubMenuOption.java index 36f7f9b..f9fbee6 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/SubMenuOption.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/SubMenuOption.java @@ -3,7 +3,7 @@ import com.tanishisherewith.dynamichud.utils.BooleanPool; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; -import net.minecraft.text.Text; +import net.minecraft.network.chat.Component; import java.util.Objects; import java.util.function.Consumer; @@ -19,48 +19,48 @@ public class SubMenuOption extends Option { private final ContextMenu subMenu; - public SubMenuOption(Text name, ContextMenu parentMenu, Supplier getter, Consumer setter, T properties) { + public SubMenuOption(Component name, ContextMenu parentMenu, Supplier getter, Consumer setter, T properties) { super(name, getter, setter); Objects.requireNonNull(parentMenu, "Parent Context Menu cannot be null in [" + name + "] SubMenu option"); this.subMenu = parentMenu.createSubMenu(parentMenu.x + parentMenu.getWidth(), this.y, properties.cloneSkin()); this.subMenu.getProperties().setHeightOffset(0); this.subMenu.setVisible(get()); - this.renderer.init(this); } - public SubMenuOption(Text name, ContextMenu parentMenu, T properties) { + public SubMenuOption(Component name, ContextMenu parentMenu, T properties) { this(name, parentMenu, () -> BooleanPool.get(name.getString()), value -> BooleanPool.put(name.getString(), value), properties); } - public SubMenuOption(Text name, ContextMenu parentMenu, Supplier getter, Consumer setter) { + public SubMenuOption(Component name, ContextMenu parentMenu, Supplier getter, Consumer setter) { this(name, parentMenu, getter, setter, parentMenu.getProperties().cloneWithSkin()); } - public SubMenuOption(Text name, ContextMenu parentMenu) { + public SubMenuOption(Component name, ContextMenu parentMenu) { this(name, parentMenu, () -> BooleanPool.get(name.getString()), value -> BooleanPool.put(name.getString(), value), parentMenu.getProperties().cloneWithSkin()); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (super.mouseClicked(mouseX, mouseY, button)) { - subMenu.toggleDisplay(); - set(subMenu.isVisible()); + toggle(); return true; } - subMenu.mouseClicked(mouseX, mouseY, button); - return false; + return subMenu.mouseClicked(mouseX, mouseY, button); + } + + public void toggle(){ + subMenu.toggleDisplay(); + set(subMenu.isVisible()); } @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { - subMenu.mouseReleased(mouseX, mouseY, button); - return super.mouseReleased(mouseX, mouseY, button); + return subMenu.mouseReleased(mouseX, mouseY, button); } @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - subMenu.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + return subMenu.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); } public SubMenuOption getOption() { diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/AlphaSlider.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/AlphaSlider.java index ef71488..e3d96c1 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/AlphaSlider.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/AlphaSlider.java @@ -2,7 +2,8 @@ import com.tanishisherewith.dynamichud.helpers.ColorHelper; import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.util.Mth; import java.awt.*; @@ -22,16 +23,16 @@ public AlphaSlider(int x, int y, int width, int height, Color color) { this.color = color; this.x = x; this.y = y; - this.alpha = color.getAlpha() / 255f; + updateAlphaFromColor(color); } - public void render(DrawContext drawContext, int x, int y) { + public void render(GuiGraphics graphics, int x, int y) { this.x = x; this.y = y; - DrawHelper.drawOutlinedBox(drawContext, x - 2, y - 2, x + width + 2, y + height + 2, Color.WHITE.getRGB()); - DrawHelper.drawGradient(drawContext, x, y, width, height, color.getRGB(), ColorHelper.changeAlpha(color, 0).getRGB(), DrawHelper.Direction.TOP_BOTTOM); - drawContext.fill(x - 2, y + alphaHandleY - 1, x + width + 2, y + alphaHandleY + 1, Color.WHITE.getRGB()); + DrawHelper.drawOutlinedBox(graphics, x - 2, y - 2, x + width + 2, y + height + 2, Color.WHITE.getRGB()); + DrawHelper.drawGradient(graphics, x, y, width, height, color.getRGB(), ColorHelper.changeAlpha(color, 0).getRGB(), DrawHelper.Direction.TOP_BOTTOM); + graphics.fill(x - 2, y + alphaHandleY - 1, x + width + 2, y + alphaHandleY + 1, Color.WHITE.getRGB()); } public Color getColor() { @@ -40,21 +41,40 @@ public Color getColor() { public void setColor(Color color) { this.color = color; + updateAlphaFromColor(color); + } + + private void updateAlphaFromColor(Color color) { + this.alpha = color.getAlpha() / 255f; + // Maps the 0.0 - 1.0 float range back into screen pixel offset + this.alphaHandleY = Math.round((1.0f - this.alpha) * this.height); } public void onClick(double mouseX, double mouseY, int button) { if (button == 0 && isMouseOver(mouseX, mouseY)) { - alphaHandleY = (int) mouseY - y; - alpha = 1.0f - (alphaHandleY / (float) height); - if (alpha < 0.0f) { - alpha = 0.0f; - } else if (alpha > 1.0f) { - alpha = 1.0f; - } this.isDragging = true; + handleMouseMovement(mouseY); + } + } + + public void onDrag(double mouseX, double mouseY, int button) { + if (this.isDragging) { + handleMouseMovement(mouseY); + } + } + + public void onRelease(double mouseX, double mouseY, int button) { + if (button == 0) { + this.isDragging = false; } } + private void handleMouseMovement(double mouseY) { + // Clamp mouse delta pos so the handle dooesnt jump out of the bar bounds + this.alphaHandleY = Mth.clamp((int) mouseY - this.y, 0, this.height); + this.alpha = 1.0f - (this.alphaHandleY / (float) this.height); + } + public void setY(int y) { this.y = y; } @@ -74,22 +94,4 @@ public int getHeight() { public boolean isMouseOver(double mouseX, double mouseY) { return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; } - - public void onRelease(double mouseX, double mouseY, int button) { - if (button == 0) { - isDragging = false; - } - } - - public void onDrag(double mouseX, double mouseY, int button) { - if (isDragging && mouseY >= y && mouseY <= y + height) { - alphaHandleY = (int) mouseY - y; - alpha = 1.0f - (alphaHandleY / (float) height); - if (alpha < 0.0f) { - alpha = 0.0f; - } else if (alpha > 1.0f) { - alpha = 1.0f; - } - } - } -} +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradient.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradient.java index 067c45c..209c893 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradient.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradient.java @@ -1,16 +1,19 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; import com.tanishisherewith.dynamichud.config.GlobalConfig; -import com.tanishisherewith.dynamichud.helpers.ColorHelper; import com.tanishisherewith.dynamichud.helpers.MouseColorQuery; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import org.lwjgl.glfw.GLFW; import java.awt.*; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; public class ColorGradient { - final MinecraftClient client = MinecraftClient.getInstance(); + final Minecraft client = Minecraft.getInstance(); private final Consumer onColorSelected; // The callback to call when a color is selected private final HueSlider gradientSlider; private final SaturationHueBox gradientBox; @@ -20,6 +23,8 @@ public class ColorGradient { private int x, y; private boolean display = false; + private Color hoveredColorPreview = null; + public ColorGradient(int x, int y, Color initialColor, Consumer onColorSelected, int boxSize, int colors) { this.x = x; this.y = y; @@ -50,63 +55,108 @@ public void display() { public void close() { display = false; + this.hoveredColorPreview = null; } - public void render(DrawContext drawContext, int x1, int y1, int mouseX, int mouseY) { + public void render(GuiGraphics graphics, int x1, int y1, int mouseX, int mouseY) { setPos(x1, y1); if (!display) { return; } - gradientSlider.render(drawContext, x, y + client.textRenderer.fontHeight + 4); - gradientBox.render(drawContext, x, y + client.textRenderer.fontHeight + gradientSlider.getHeight() + 10); - // colorPickerButton.render(drawContext, x + 24 + boxSize, y + client.textRenderer.fontHeight + gradientSlider.getHeight() + 8); - alphaSlider.render(drawContext, x + 10 + boxSize, y + client.textRenderer.fontHeight + gradientSlider.getHeight() + 10); - - if (colorPickerButton.isPicking() && GlobalConfig.get().showColorPickerPreview()) { - MouseColorQuery.request(mouseX, mouseY, colors -> { - if (colors != null) { - int red = colors[0]; - int green = colors[1]; - int blue = colors[2]; - - //Draw the preview box near the mouse pointer - drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0, 0, 2500); - drawContext.fill(mouseX + 10, mouseY, mouseX + 26, mouseY + 16, -1); - drawContext.fill(mouseX + 11, mouseY + 1, mouseX + 25, mouseY + 15, (red << 16) | (green << 8) | blue | 0xFF000000); - drawContext.getMatrices().pop(); + gradientSlider.render(graphics, x, y + client.font.lineHeight + 4); + gradientBox.render(graphics, x, y + client.font.lineHeight + gradientSlider.getHeight() + 10); + colorPickerButton.render(graphics, x + 24 + boxSize, y + client.font.lineHeight + gradientSlider.getHeight() + 8); + alphaSlider.render(graphics, x + 10 + boxSize, y + client.font.lineHeight + gradientSlider.getHeight() + 10); + + if (colorPickerButton.isPicking()) { + if (GlobalConfig.get().showColorPickerPreview()) { + // Request the pixel color under the exact cursor position + MouseColorQuery.request(colors -> { + if (colors != null) { + this.hoveredColorPreview = new Color(colors[0], colors[1], colors[2]); + } + }); + + MouseColorQuery.processIfPending(); // process immediately + + renderPickerPreview(graphics, mouseX, mouseY); + } + } else { + this.hoveredColorPreview = null; + } + } + + public void renderPickerPreview(GuiGraphics graphics, int mouseX, int mouseY) { + if (hoveredColorPreview != null && colorPickerButton.isPicking() && GlobalConfig.get().showColorPickerPreview()) { + // Temporarily pop all active clipping zones off the scissor stack + // This will allow the preview to render over all screen scissors + List poppedScissors = new ArrayList<>(); + while (graphics.scissorStack.peek() != null) { + poppedScissors.add(graphics.scissorStack.peek()); + graphics.scissorStack.pop(); + } + + graphics.fill(mouseX + 6, mouseY + 6, mouseX + 22, mouseY + 22, 0xFFFFFFFF); + graphics.fill(mouseX + 7, mouseY + 7, mouseX + 21, mouseY + 21, hoveredColorPreview.getRGB() | 0xFF000000); + + // Restore all clipping zones back to the stack in reverse order + if(!poppedScissors.isEmpty()) { + for (int i = poppedScissors.size() - 1; i >= 0; i--) { + graphics.scissorStack.push(poppedScissors.get(i)); } - }); + } } } + + /** + * Updates the internal states of the HSV sliders, alpha bar, and alerts the parent option on selection. + */ + private void updateSelectedColor(Color color) { + float[] hsv = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null); + gradientSlider.setHue(hsv[0]); + gradientBox.setHue(hsv[0]); + gradientBox.setSaturation(hsv[1]); + gradientBox.setValue(hsv[2]); + + int currentAlpha = alphaSlider.getColor().getAlpha(); + alphaSlider.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), currentAlpha)); + onColorSelected.accept(alphaSlider.getColor()); + } + public boolean mouseClicked(double mouseX, double mouseY, int button) { if (!display) { return false; } - /*if (colorPickerButton.onClick(mouseX, mouseY, button)) { + if (colorPickerButton.isPicking()) { + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + if (hoveredColorPreview != null) { + updateSelectedColor(hoveredColorPreview); + } else { + MouseColorQuery.processIfPending(); + MouseColorQuery.request(colors -> { + if (colors != null) { + updateSelectedColor(new Color(colors[0], colors[1], colors[2])); + } + }); + MouseColorQuery.processIfPending(); + } + + colorPickerButton.setPicking(false); + return true; + } + } + + if (colorPickerButton.onClick(mouseX, mouseY, button)) { return true; - } else*/ + } + if (gradientSlider.isMouseOver(mouseX, mouseY)) { gradientSlider.onClick(mouseX, mouseY, button); gradientBox.setHue(gradientSlider.getHue()); } else if (gradientBox.isMouseOver(mouseX, mouseY)) { gradientBox.onClick(mouseX, mouseY, button); - } /* else if (colorPickerButton.isPicking()) { - int[] colors = ColorHelper.getMousePixelColor(mouseX,mouseY); - if(colors != null) { - float[] hsv = Color.RGBtoHSB(colors[0], colors[1], colors[2], null); - gradientSlider.setHue(hsv[0]); - gradientBox.setHue(hsv[0]); - gradientBox.setSaturation(hsv[1]); - gradientBox.setValue(hsv[2]); - - colorPickerButton.setPicking(false); - } else { - DynamicHUD.logger.error("Invalid RGB pixel color at mouse pointer"); - } } - */ alphaSlider.setColor(new Color(gradientBox.getColor(), true)); alphaSlider.onClick(mouseX, mouseY, button); onColorSelected.accept(alphaSlider.getColor()); @@ -114,15 +164,16 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return true; } - public void mouseReleased(double mouseX, double mouseY, int button) { + public boolean mouseReleased(double mouseX, double mouseY, int button) { gradientSlider.onRelease(mouseX, mouseY, button); gradientBox.onRelease(mouseX, mouseY, button); alphaSlider.onRelease(mouseX, mouseY, button); + return false; } - public void mouseDragged(double mouseX, double mouseY, int button) { - if (!display) { - return; + public boolean mouseDragged(double mouseX, double mouseY, int button) { + if (!display || colorPickerButton.isPicking()) { + return false; } gradientSlider.onDrag(mouseX, mouseY, button); gradientBox.setHue(gradientSlider.getHue()); @@ -130,6 +181,7 @@ public void mouseDragged(double mouseX, double mouseY, int button) { alphaSlider.setColor(new Color(gradientBox.getColor(), true)); alphaSlider.onDrag(mouseX, mouseY, button); onColorSelected.accept(alphaSlider.getColor()); + return true; } public int getBoxSize() { diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorPickerButton.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorPickerButton.java index 4d9b711..dcf0242 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorPickerButton.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorPickerButton.java @@ -1,7 +1,7 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; import java.awt.*; @@ -19,15 +19,12 @@ public ColorPickerButton(int x, int y, int width, int height) { this.height = height; } - public void render(DrawContext drawContext, int x, int y) { + public void render(GuiGraphics graphics, int x, int y) { this.x = x; this.y = y; - drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0, 0, 404); // Draw the button - drawContext.fill(x + 2, y + 2, x + width - 2, y + height - 2, isPicking() ? Color.GREEN.getRGB() : 0xFFAAAAAA); - drawContext.drawCenteredTextWithShadow(MinecraftClient.getInstance().textRenderer, "Pick", x + width / 2, y + (height - 8) / 2, 0xFFFFFFFF); - drawContext.getMatrices().pop(); + graphics.fill(x + 2, y + 2, x + width - 2, y + height - 2, isPicking() ? Color.GREEN.getRGB() : 0xFFAAAAAA); + graphics.drawCenteredString(Minecraft.getInstance().font, "Pick", x + width / 2, y + (height - 8) / 2, 0xFFFFFFFF); } public int getHeight() { @@ -41,7 +38,7 @@ public int getWidth() { public boolean onClick(double mouseX, double mouseY, int button) { if (button == 0) { if (mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height) { - isPicking = true; + isPicking = !isPicking; return true; } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/HueSlider.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/HueSlider.java index 1c5097d..7d8c009 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/HueSlider.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/HueSlider.java @@ -1,7 +1,7 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.GuiGraphics; import java.awt.*; @@ -31,20 +31,17 @@ public void setPosition(int x, int y) { this.y = y; } - public void render(DrawContext drawContext, int x, int y) { + public void render(GuiGraphics graphics, int x, int y) { setPosition(x, y); - drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0, 0, 401); - DrawHelper.drawOutlinedBox(drawContext, x - 2, y - 2, x + width + 2, y + height + 2, -1); + DrawHelper.drawOutlinedBox(graphics, x - 2, y - 2, x + width + 2, y + height + 2, -1); // Draw the gradient for (int i = 0; i < width; i++) { float hue = (float) i / width; int color = Color.HSBtoRGB(hue, 1.0f, 1.0f); color = (color & 0x00FFFFFF) | (255 << 24); - drawContext.fill(x + i, y, x + i + 1, y + height, color); + graphics.fill(x + i, y, x + i + 1, y + height, color); } - drawContext.draw(); // Draw the handle @@ -53,8 +50,7 @@ public void render(DrawContext drawContext, int x, int y) { float handleX = x + hue * width - handleWidth / 2.0f; float handleY = y - (handleHeight - height) / 2.0f; - DrawHelper.drawRectangle(drawContext, handleX, handleY, handleWidth, handleHeight, -1); - drawContext.getMatrices().pop(); + DrawHelper.drawRectangle(graphics, handleX, handleY, handleWidth, handleHeight, -1); } public int getHeight() { diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/SaturationHueBox.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/SaturationHueBox.java index 53b8238..348459b 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/SaturationHueBox.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/SaturationHueBox.java @@ -1,7 +1,7 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.GuiGraphics; import java.awt.*; @@ -21,22 +21,19 @@ public SaturationHueBox(int x, int y, int size) { this.size = size; } - public void render(DrawContext drawContext, int x, int y) { + public void render(GuiGraphics graphics, int x, int y) { setPosition(x, y); - drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0, 0, 406); - DrawHelper.drawOutlinedBox(drawContext, x - 2, y - 2, x + size + 2, y + size + 2, -1); + DrawHelper.drawOutlinedBox(graphics, x - 2, y - 2, x + size + 2, y + size + 2, -1); // Draw the gradient - DrawHelper.drawRoundedGradientRectangle(drawContext, Color.WHITE, Color.getHSBColor(hue, 1.0f, 1.0f),Color.BLACK, Color.BLACK, x, y, size, size, 3); + DrawHelper.drawRoundedGradientRectangle(graphics, Color.WHITE, Color.getHSBColor(hue, 1.0f, 1.0f),Color.BLACK, Color.BLACK, x, y, size, size, 3); // Draw the handle float handleSize = 1f; float handleX = x + 2 + saturation * size - handleSize / 2.0f; float handleY = y + 2 + (1.0f - value) * size - handleSize / 2.0f; - DrawHelper.drawFilledCircle(drawContext, handleX, handleY, handleSize, -1); - drawContext.getMatrices().pop(); + DrawHelper.drawFilledCircle(graphics, handleX, handleY, handleSize, -1); } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/ContextMenuScreen.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/ContextMenuScreen.java new file mode 100644 index 0000000..80be8aa --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/ContextMenuScreen.java @@ -0,0 +1,94 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.screen; + +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.network.chat.Component; +import org.jspecify.annotations.NonNull; + +public class ContextMenuScreen extends Screen { + ContextMenu contextMenu; + ContextMenuProperties properties; + + public ContextMenuScreen(ContextMenu menu, ContextMenuProperties properties) { + super(Component.literal("ContextMenu screen")); + this.contextMenu = menu; + this.properties = properties; + } + + @Override + public void added() { + super.added(); + contextMenu.setVisible(true); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + contextMenu.update(); + DrawHelper.scaleAndPosition(graphics.pose(), (float) width / 2, (float) height / 2, contextMenu.getMenuScale()); + + properties.getSkin().setContextMenu(contextMenu); + properties.getSkin().renderContextMenu(graphics, contextMenu, mouseX, mouseY); + + DrawHelper.stopScaling(graphics.pose()); + + if (contextMenu.getMenuScale() <= 0 && !contextMenu.isVisible()) { + this.onClose(); + } + } + + @Override + public void renderBackground(@NonNull GuiGraphics guiGraphics, int i, int j, float f) { + + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + return contextMenu.mouseClicked(event.x(), event.y(), event.button()) || super.mouseClicked(event, bl); + } + + @Override + public boolean mouseDragged(MouseButtonEvent event, double dx, double dy) { + return contextMenu.mouseDragged(event.x(), event.y(), event.button(), dx, dy) || super.mouseDragged(event, dx, dy); + } + + @Override + public boolean mouseScrolled(double x, double y, double horizontalAmount, double verticalAmount) { + contextMenu.mouseScrolled(x, y, horizontalAmount, verticalAmount); + return super.mouseScrolled(x, y, horizontalAmount, verticalAmount); + } + + @Override + public boolean mouseReleased(MouseButtonEvent event) { + return contextMenu.mouseReleased(event.x(), event.y(), event.button()) || super.mouseReleased(event); + } + + @Override + public boolean keyReleased(KeyEvent event) { + contextMenu.keyReleased(event.key(), event.scancode(), event.modifiers()); + return super.keyReleased(event); + } + + @Override + public boolean keyPressed(KeyEvent event) { + contextMenu.keyPressed(event.key(), event.scancode(), event.modifiers()); + return super.keyPressed(event); + } + + @Override + public boolean charTyped(CharacterEvent characterEvent) { + contextMenu.charTyped(characterEvent.codepointAsString().charAt(0), characterEvent.modifiers()); + return super.charTyped(characterEvent); + } + + @Override + public void onClose() { + contextMenu.close(); + contextMenu.setVisible(false); + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenRegistry.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/ContextMenuScreenRegistry.java similarity index 63% rename from src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenRegistry.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/ContextMenuScreenRegistry.java index 3bd3646..058548f 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenRegistry.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/ContextMenuScreenRegistry.java @@ -1,6 +1,6 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen; +package com.tanishisherewith.dynamichud.utils.contextmenu.screen; -import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screens.Screen; public class ContextMenuScreenRegistry { public Class screenKlass; diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenFactory.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/factory/ContextMenuScreenFactory.java similarity index 83% rename from src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenFactory.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/factory/ContextMenuScreenFactory.java index 6dea3ec..66b60da 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenFactory.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/factory/ContextMenuScreenFactory.java @@ -1,8 +1,8 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen; +package com.tanishisherewith.dynamichud.utils.contextmenu.screen.factory; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; -import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screens.Screen; /** * We will use this interface to provide the context menu with the screen required by its skins. diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/DefaultContextMenuScreenFactory.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/factory/DefaultContextMenuScreenFactory.java similarity index 71% rename from src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/DefaultContextMenuScreenFactory.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/factory/DefaultContextMenuScreenFactory.java index b156347..589d38f 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/DefaultContextMenuScreenFactory.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/screen/factory/DefaultContextMenuScreenFactory.java @@ -1,8 +1,9 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen; +package com.tanishisherewith.dynamichud.utils.contextmenu.screen.factory; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; -import net.minecraft.client.gui.screen.Screen; +import com.tanishisherewith.dynamichud.utils.contextmenu.screen.ContextMenuScreen; +import net.minecraft.client.gui.screens.Screen; /** * Default implementation of the {@link ContextMenuScreenFactory} providing a {@link ContextMenuScreen} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ClassicSkin.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ClassicSkin.java index 36ec017..39b51ec 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ClassicSkin.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ClassicSkin.java @@ -1,143 +1,81 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem; +import com.tanishisherewith.dynamichud.helpers.ColorHelper; import com.tanishisherewith.dynamichud.helpers.DrawHelper; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; import com.tanishisherewith.dynamichud.utils.contextmenu.options.*; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.SkinRenderer; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; import java.awt.*; - /** * This is one of the Skins provided by DynamicHUD featuring the classic rendering, * which should be used when you have a low amount of settings and want quicker way of changing the settings. */ public class ClassicSkin extends Skin { + public ClassicSkin() { super(); - addRenderer(BooleanOption.class, ClassicBooleanRenderer::new); - addRenderer(DoubleOption.class, ClassicDoubleRenderer::new); - addRenderer(EnumOption.class, ClassicEnumRenderer::new); - addRenderer(ListOption.class, ClassicListRenderer::new); + addRenderer(ColorOption.class, ClassicColorOptionRenderer::new); + addRenderer(CycleOption.class, ClassicCycleRenderer::new); addRenderer(SubMenuOption.class, ClassicSubMenuRenderer::new); addRenderer(RunnableOption.class, ClassicRunnableRenderer::new); - addRenderer(ColorOption.class, ClassicColorOptionRenderer::new); + addRenderer(DoubleOption.class, ClassicDoubleRenderer::new); setCreateNewScreen(false); } @Override - public void renderContextMenu(DrawContext drawContext, ContextMenu contextMenu, int mouseX, int mouseY) { + public void renderContextMenu(GuiGraphics graphics, ContextMenu contextMenu, int mouseX, int mouseY) { this.contextMenu = contextMenu; - - MatrixStack matrices = drawContext.getMatrices(); ContextMenuProperties properties = contextMenu.getProperties(); - // Draw the background - drawBackground(drawContext, contextMenu, properties); + drawBackground(graphics, contextMenu, properties); - int yOffset = contextMenu.y + 3; - int width = 10; for (Option option : getOptions(contextMenu)) { if (!option.shouldRender()) continue; - // Adjust mouse coordinates based on the scale - if (contextMenu.getProperties().hoverEffect() && contextMenu.isMouseOver(mouseX, mouseY, contextMenu.x + 1, yOffset - 1, contextMenu.getWidth() - 2, option.getHeight())) { - drawBackground(drawContext, contextMenu, properties, yOffset - 1, contextMenu.getWidth(), option.getHeight() + 1, contextMenu.getProperties().getHoverColor().getRGB(), false); + if (properties.hoverEffect() && contextMenu.isMouseOver(mouseX, mouseY, option.getX() - 3, option.getY() - 1, contextMenu.getWidth() - 2, option.getHeight())) { + drawBackground(graphics, contextMenu, properties, option.getY() - 1, contextMenu.getWidth(), option.getHeight() + 1, properties.getHoverColor().getRGB(), false); } - option.render(drawContext, contextMenu.x + 4, yOffset, mouseX, mouseY); - width = Math.max(width, option.getWidth()); - yOffset += option.getHeight() + 1; + option.render(graphics, option.getX(), option.getY(), mouseX, mouseY); } - contextMenu.setWidth(width + properties.getPadding()); - contextMenu.setHeight(yOffset - contextMenu.y); - // Draw the border if needed if (properties.shouldDrawBorder()) { - drawBorder(drawContext, contextMenu, properties); + drawBorder(graphics, contextMenu, properties); } } - private void drawBackground(DrawContext drawContext, ContextMenu contextMenu, ContextMenuProperties properties) { - drawBackground(drawContext, contextMenu, properties, contextMenu.y, contextMenu.getWidth(), contextMenu.getHeight(), properties.getBackgroundColor().getRGB(), properties.shadow()); + private void drawBackground(GuiGraphics graphics, ContextMenu contextMenu, ContextMenuProperties properties) { + drawBackground(graphics, contextMenu, properties, contextMenu.y, contextMenu.getWidth(), contextMenu.getHeight(), properties.getBackgroundColor().getRGB(), properties.shadow()); } - private void drawBackground(DrawContext drawContext, ContextMenu contextMenu, ContextMenuProperties properties, int yOffset, int width, int height, int color, boolean shadow) { + private void drawBackground(GuiGraphics graphics, ContextMenu contextMenu, ContextMenuProperties properties, int yOffset, int width, int height, int color, boolean shadow) { if (properties.roundedCorners()) { - // Rounded if (shadow) { - DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext, - contextMenu.x, - yOffset, - width, - height, - properties.getCornerRadius(), - color, - 150, - 1, - 1 - ); + DrawHelper.drawRoundedRectangleWithShadowBadWay(graphics, contextMenu.x, yOffset, width, height, properties.getCornerRadius(), color, 150, 1, 1); } else { - DrawHelper.drawRoundedRectangle(drawContext, - contextMenu.x, - yOffset, - width, - height, - properties.getCornerRadius(), - color - ); + DrawHelper.drawRoundedRectangle(graphics, contextMenu.x, yOffset, width, height, properties.getCornerRadius(), color); } } else { - // Normal if (shadow) { - DrawHelper.drawRectangleWithShadowBadWay(drawContext, - contextMenu.x, - yOffset, - width, - height, - color, - 150, - 1, - 1 - ); + DrawHelper.drawRectangleWithShadowBadWay(graphics, contextMenu.x, yOffset, width, height, color, 150, 1, 1); } else { - DrawHelper.drawRectangle(drawContext, - contextMenu.x, - yOffset, - width, - height, - color - ); + DrawHelper.drawRectangle(graphics, contextMenu.x, yOffset, width, height, color); } } } - private void drawBorder(DrawContext drawContext, ContextMenu contextMenu, ContextMenuProperties properties) { + private void drawBorder(GuiGraphics graphics, ContextMenu contextMenu, ContextMenuProperties properties) { if (properties.roundedCorners()) { - DrawHelper.drawOutlineRoundedBox(drawContext, - contextMenu.x, - contextMenu.y, - contextMenu.getWidth(), - contextMenu.getHeight(), - properties.getCornerRadius(), - properties.getBorderWidth(), - properties.getBorderColor().getRGB() - ); + DrawHelper.drawOutlineRoundedBox(graphics, contextMenu.x, contextMenu.y, contextMenu.getWidth(), contextMenu.getHeight(), properties.getCornerRadius(), properties.getBorderWidth(), properties.getBorderColor().getRGB()); } else { - DrawHelper.drawOutlineBox(drawContext, - contextMenu.x, - contextMenu.y, - contextMenu.getWidth(), - contextMenu.getHeight(), - properties.getBorderWidth(), - properties.getBorderColor().getRGB() - ); + DrawHelper.drawOutlineBox(graphics, contextMenu.x, contextMenu.y, contextMenu.getWidth(), contextMenu.getHeight(), properties.getBorderWidth(), properties.getBorderColor().getRGB()); } } @@ -146,27 +84,24 @@ public Skin clone() { return new ClassicSkin(); } + public static class ClassicBooleanRenderer implements SkinRenderer { @Override - public void render(DrawContext drawContext, BooleanOption option, int x, int y, int mouseX, int mouseY) { - int color = option.value ? Color.GREEN.getRGB() : Color.RED.getRGB(); - drawContext.drawText(mc.textRenderer, option.name, x, y, color, false); - option.setHeight(mc.textRenderer.fontHeight); - option.setWidth(mc.textRenderer.getWidth(option.name) + 1); + public void render(GuiGraphics graphics, BooleanOption option, int x, int y, int mouseX, int mouseY) { + int color = option.get() ? Color.GREEN.getRGB() : Color.RED.getRGB(); + graphics.drawString(mc.font, option.name, x, y, color, false); } } public static class ClassicColorOptionRenderer implements SkinRenderer { @Override - public void render(DrawContext drawContext, ColorOption option, int x, int y, int mouseX, int mouseY) { + public void render(GuiGraphics graphics, ColorOption option, int x, int y, int mouseX, int mouseY) { int color = option.isVisible ? Color.GREEN.getRGB() : Color.RED.getRGB(); - drawContext.drawText(mc.textRenderer, option.name, x, y, color, false); - option.setHeight(mc.textRenderer.fontHeight); - option.setWidth(mc.textRenderer.getWidth(option.name) + 10); + graphics.drawString(mc.font, option.name, x, y, color, false); int shadowOpacity = Math.min(option.value.getAlpha(), 90); - DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext, - x + option.getWidth() - 10 + 1, + DrawHelper.drawRoundedRectangleWithShadowBadWay(graphics, + x + option.getWidth() - 10, y - 1, 8, 8, @@ -176,75 +111,54 @@ public void render(DrawContext drawContext, ColorOption option, int x, int y, in 1, 1); - option.getColorGradient().render(drawContext, x + option.getParentMenu().getWidth() + 7, y - 10, mouseX, mouseY); + option.getColorGradient().render(graphics, x + option.getParentMenu().getWidth() + 7, y - 10, mouseX, mouseY); } } - public static class ClassicEnumRenderer> implements SkinRenderer> { + public static class ClassicCycleRenderer implements SkinRenderer> { @Override - public void render(DrawContext drawContext, EnumOption option, int x, int y, int mouseX, int mouseY) { - option.setHeight(mc.textRenderer.fontHeight + 1); - option.setWidth(mc.textRenderer.getWidth(option.name + ": " + option.value.name()) + 1); - - drawContext.drawText(mc.textRenderer, option.name.copy().append(": "), x, y, Color.WHITE.getRGB(), false); - drawContext.drawText(mc.textRenderer, option.value.name(), x + mc.textRenderer.getWidth(option.name + ": ") + 1, y, Color.CYAN.getRGB(), false); + public void render(GuiGraphics graphics, CycleOption option, int x, int y, int mouseX, int mouseY) { + graphics.drawString(mc.font, option.name.copy().append(": "), x, y, Color.WHITE.getRGB(), false); + graphics.drawString(mc.font, option.get().toString(), x + mc.font.width(option.name + ": ") + 1, y, Color.CYAN.getRGB(), false); } } public static class ClassicSubMenuRenderer implements SkinRenderer { @Override - public void render(DrawContext drawContext, SubMenuOption option, int x, int y, int mouseX, int mouseY) { + public void render(GuiGraphics graphics, SubMenuOption option, int x, int y, int mouseX, int mouseY) { int color = option.value ? Color.GREEN.getRGB() : Color.RED.getRGB(); - drawContext.drawText(mc.textRenderer, option.name, x, y, color, false); - drawContext.drawText(mc.textRenderer, option.getSubMenu().isVisible() ? "-" : "+", x + Math.max(option.getParentMenu().getWidth() - 12, mc.textRenderer.getWidth(option.name) + 2), y, color, false); + graphics.drawString(mc.font, option.name, x, y, color, false); + graphics.drawString(mc.font, option.getSubMenu().isVisible() ? "-" : "+", x + Math.max(option.getParentMenu().getWidth() - 12, mc.font.width(option.name) + 2), y, color, false); - option.setHeight(mc.textRenderer.fontHeight); - option.setWidth(mc.textRenderer.getWidth(option.name) + 4); - - option.getSubMenu().render(drawContext, x + option.getParentMenu().getWidth(), y - 1, mouseX, mouseY); + option.getSubMenu().render(graphics, x + option.getParentMenu().getWidth(), y - 1, mouseX, mouseY); } } public static class ClassicRunnableRenderer implements SkinRenderer { - Color DARK_RED = new Color(116, 0, 0); - Color DARK_GREEN = new Color(24, 132, 0, 226); - @Override - public void render(DrawContext drawContext, RunnableOption option, int x, int y, int mouseX, int mouseY) { - option.setHeight(mc.textRenderer.fontHeight); - option.setWidth(mc.textRenderer.getWidth("Run: " + option.name)); - int color = option.value ? DARK_GREEN.getRGB() : DARK_RED.getRGB(); - drawContext.drawText(mc.textRenderer, Text.literal("Run: ").append(option.name), x, y, color, false); + public void render(GuiGraphics graphics, RunnableOption option, int x, int y, int mouseX, int mouseY) { + int color = option.value ? ColorHelper.DARK_GREEN.getRGB() : ColorHelper.DARK_RED.getRGB(); + graphics.drawString(mc.font, Component.literal("Run: ").append(option.name), x, y, color, false); } } - public class ClassicDoubleRenderer implements SkinRenderer { + public static class ClassicDoubleRenderer implements SkinRenderer { @Override - public void render(DrawContext drawContext, DoubleOption option, int x, int y, int mouseX, int mouseY) { - option.setWidth(Math.max(35, contextMenu != null ? contextMenu.getWidth() - option.getProperties().getPadding() - 2 : 0)); - option.setHeight(16); - - // Draw the label - TextRenderer textRenderer = mc.textRenderer; - DrawHelper.scaleAndPosition(drawContext.getMatrices(), x, y, 0.7f); - Text labelText = option.name.copy().append(": " + String.format("%.1f", option.value)); - int labelWidth = textRenderer.getWidth(labelText); - - option.setWidth(Math.max(option.getWidth(), labelWidth)); - - drawContext.drawTextWithShadow(textRenderer, labelText, x, y + 1, 0xFFFFFFFF); - DrawHelper.stopScaling(drawContext.getMatrices()); + public void render(GuiGraphics graphics, DoubleOption option, int x, int y, int mouseX, int mouseY) { + Font font = mc.font; + DrawHelper.scaleAndPosition(graphics.pose(), x, y, 0.7f); + Component labelText = option.name.copy().append(": " + String.format("%.1f", option.value)); + graphics.drawString(font, labelText, x, y + 1, 0xFFFFFFFF, true); + DrawHelper.stopScaling(graphics.pose()); float handleWidth = 3; float handleHeight = 8; double handleX = x + (option.value - option.minValue) / (option.maxValue - option.minValue) * (option.getWidth() - handleWidth); - double handleY = y + textRenderer.fontHeight + 1 + ((2 - handleHeight) / 2); + double handleY = y + font.lineHeight + 1 + ((2 - handleHeight) / 2); - // Draw the slider - option.drawSlider(drawContext, x, y + textRenderer.fontHeight + 1, option.getWidth(), handleX); + option.drawSlider(graphics, x, y + font.lineHeight + 1, option.getWidth(), handleX); - // Draw the handle - DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext, + DrawHelper.drawRoundedRectangleWithShadowBadWay(graphics, (float) handleX, (float) handleY, handleWidth, @@ -254,18 +168,6 @@ public void render(DrawContext drawContext, DoubleOption option, int x, int y, i 90, 0.6f, 0.6f); - - } - } - - public static class ClassicListRenderer implements SkinRenderer> { - @Override - public void render(DrawContext drawContext, ListOption option, int x, int y, int mouseX, int mouseY) { - option.setHeight(mc.textRenderer.fontHeight + 1); - option.setWidth(mc.textRenderer.getWidth(option.name + ": " + option.value.toString()) + 1); - - drawContext.drawText(mc.textRenderer, option.name.copy().append(": "), x, y + 1, Color.WHITE.getRGB(), false); - drawContext.drawText(mc.textRenderer, option.value.toString(), x + mc.textRenderer.getWidth(option.name + ": ") + 1, y + 1, Color.CYAN.getRGB(), false); } } } \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/MinecraftSkin.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/MinecraftSkin.java index 0593ed0..c6e5fb3 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/MinecraftSkin.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/MinecraftSkin.java @@ -1,21 +1,28 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem; -import com.mojang.blaze3d.systems.RenderSystem; import com.tanishisherewith.dynamichud.DynamicHUD; import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import com.tanishisherewith.dynamichud.helpers.animationhelper.EasingType; +import com.tanishisherewith.dynamichud.helpers.animationhelper.animations.ValueAnimation; +import com.tanishisherewith.dynamichud.utils.Util; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutContext; +import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutEngine; import com.tanishisherewith.dynamichud.utils.contextmenu.options.*; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.GroupableSkin; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.SkinRenderer; import com.tanishisherewith.dynamichud.utils.handlers.ScrollHandler; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.ButtonTextures; -import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.sound.PositionedSoundInstance; -import net.minecraft.sound.SoundEvents; -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.WidgetSprites; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.input.MouseButtonInfo; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.resources.Identifier; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.network.chat.Component; import org.lwjgl.glfw.GLFW; import java.awt.*; @@ -23,50 +30,62 @@ import java.util.List; import java.util.function.IntSupplier; +import static com.tanishisherewith.dynamichud.helpers.ColorHelper.DARK_GREEN; +import static com.tanishisherewith.dynamichud.helpers.ColorHelper.DARK_RED; + /** * This is one of the Skins provided by DynamicHUD featuring the minecraft-like style rendering. * It runs on a separate screen and provides more complex features like scrolling and larger dimension. * It tries to imitate the minecraft look and provides various form of panel shades {@link PanelColor} */ public class MinecraftSkin extends Skin implements GroupableSkin { - public static final ButtonTextures TEXTURES = new ButtonTextures( - Identifier.ofVanilla("widget/button"), - Identifier.ofVanilla("widget/button_disabled"), - Identifier.ofVanilla("widget/button_highlighted") + public static final WidgetSprites TEXTURES = new WidgetSprites( + Identifier.withDefaultNamespace("widget/button"), + Identifier.withDefaultNamespace("widget/button_disabled"), + Identifier.withDefaultNamespace("widget/button_highlighted") ); + public static final Identifier DEFAULT_BACKGROUND_PANEL = Identifier.withDefaultNamespace("textures/gui/demo_background.png"); + public static final Identifier SCROLLER_TEXTURE = Identifier.withDefaultNamespace("widget/scroller"); + public static final Identifier SCROLL_BAR_BACKGROUND = Identifier.withDefaultNamespace("widget/scroller_background"); + public static final Identifier GROUP_BACKGROUND = Identifier.fromNamespaceAndPath(DynamicHUD.MOD_ID, "textures/minecraftskin/group_panel.png"); + + private final Identifier BACKGROUND_PANEL; + public static final int DEFAULT_SCROLLBAR_WIDTH = 8; public static final int DEFAULT_PANEL_WIDTH = 248; public static final int DEFAULT_PANEL_HEIGHT = 165; - public static final Identifier DEFAULT_BACKGROUND_PANEL = Identifier.ofVanilla("textures/gui/demo_background.png"); - public static final Identifier SCROLLER_TEXTURE = Identifier.ofVanilla("widget/scroller"); - public static final Identifier SCROLL_BAR_BACKGROUND = Identifier.ofVanilla("widget/scroller_background"); - public static final Identifier GROUP_BACKGROUND = Identifier.of(DynamicHUD.MOD_ID, "textures/minecraftskin/group_panel.png"); - - private final Identifier BACKGROUND_PANEL; - private final int panelWidth; - private final int panelHeight; - private PanelColor panelColor; + private int panelWidth; + private int panelHeight; + private int groupPanelWidth = 60; // Width for the group panel private int imageX, imageY; private final ScrollHandler scrollHandler; private List optionGroups; private OptionGroup selectedGroup; + + private PanelColor panelColor; private final ScrollHandler groupScrollHandler; - private final int groupPanelWidth = 60; // Width for the group panel private final IntSupplier groupPanelX = () -> imageX - groupPanelWidth - 15; + private final EditBox searchBox; + private String searchQuery = ""; + private int searchBoxWidth = 0; + private int searchBoxHeight = 14; + private int searchBoxX = 0; + private int searchBoxY = 0; + public MinecraftSkin(PanelColor color) { super(); this.panelColor = color; addRenderer(BooleanOption.class, MinecraftBooleanRenderer::new); addRenderer(DoubleOption.class, MinecraftDoubleRenderer::new); - addRenderer(EnumOption.class, MinecraftEnumRenderer::new); - addRenderer(ListOption.class, MinecraftListRenderer::new); + addRenderer(CycleOption.class, MinecraftCycleRenderer::new); addRenderer(SubMenuOption.class, MinecraftSubMenuRenderer::new); addRenderer(RunnableOption.class, MinecraftRunnableRenderer::new); addRenderer(ColorOption.class, MinecraftColorOptionRenderer::new); + // if in case of different texture support. this.panelHeight = DEFAULT_PANEL_HEIGHT; this.panelWidth = DEFAULT_PANEL_WIDTH; this.BACKGROUND_PANEL = DEFAULT_BACKGROUND_PANEL; @@ -74,14 +93,29 @@ public MinecraftSkin(PanelColor color) { this.groupScrollHandler = new ScrollHandler(); setCreateNewScreen(true); + + searchBox = new EditBox(mc.font, searchBoxX, searchBoxY, searchBoxWidth, searchBoxHeight, Component.empty()); + searchBox.setResponder(query -> { + searchQuery = query; + scrollHandler.setScrollOffset(0); + }); + searchBox.setHint(Component.literal("Search...")); } - private void enableContextMenuScissor() { - DrawHelper.enableScissor(0, imageY + 3, mc.getWindow().getScaledWidth(), panelHeight - 8); + @Override + public List> getOptions(ContextMenu menu) { + if(searchQuery != null && !searchQuery.isEmpty()) { + return Util.getSearchResults(searchQuery,-1, contextMenu.getOptions()); + } + return super.getOptions(menu); + } + + private void enableContextMenuScissor(GuiGraphics graphics) { + DrawHelper.enableScissor(0, imageY + 3, mc.getWindow().getGuiScaledWidth(), panelHeight - 8,graphics); } private void createGroups() { - OptionGroup generalGroup = new OptionGroup(Text.of("General")); + OptionGroup generalGroup = new OptionGroup(Component.literal("General")); for (Option option : getOptions(contextMenu)) { if (option instanceof OptionGroup og) { optionGroups.add(og); @@ -103,14 +137,27 @@ private void initOptionGroups() { } } + private int getOptionHeight(Option option) { + if (option instanceof SubMenuOption) return 20; + if (option instanceof ColorOption colorOption) { + int baseHeight = 25; + if (colorOption.getColorGradient().shouldDisplay()) { + int colorGradientHeight = colorOption.getColorGradient().getBoxSize() + 10 + colorOption.getColorGradient().getGradientSlider().getHeight(); + return baseHeight + colorGradientHeight; + } + return baseHeight; + } + return 25; + } + @Override - public void renderContextMenu(DrawContext drawContext, ContextMenu contextMenu, int mouseX, int mouseY) { + public void renderContextMenu(GuiGraphics graphics, ContextMenu contextMenu, int mouseX, int mouseY) { this.contextMenu = contextMenu; initOptionGroups(); - int screenWidth = mc.getWindow().getScaledWidth(); - int screenHeight = mc.getWindow().getScaledHeight(); + int screenWidth = mc.getWindow().getGuiScaledWidth(); + int screenHeight = mc.getWindow().getGuiScaledHeight(); int centerX = screenWidth / 2; int centerY = screenHeight / 2; @@ -121,64 +168,85 @@ public void renderContextMenu(DrawContext drawContext, ContextMenu contextMen imageX = (screenWidth - panelWidth + 25) / 2; imageY = (screenHeight - panelHeight) / 2; - drawContext.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_PANEL, imageX, imageY, 0, 0, panelWidth, panelHeight, 256, 254,panelColor.getColor()); + graphics.blit(RenderPipelines.GUI_TEXTURED, BACKGROUND_PANEL, imageX, imageY, 0, 0, panelWidth, panelHeight, 256, 254,panelColor.getColor()); - drawSingularButton(drawContext, "X", mouseX, mouseY, imageX + 3, imageY + 3, 14, 14); + drawSingularButton(graphics, "X", mouseX, mouseY, imageX + 3, imageY + 3, 14, 14); //Up and down arrows near the group panel int size = (int) (groupPanelWidth * 0.5f); - drawSingularButton(drawContext, "^", mouseX, mouseY, groupPanelX.getAsInt() + groupPanelWidth / 2 - size / 2, imageY - 14, size, 14, groupScrollHandler.isOffsetWithinBounds(-10)); - drawSingularButton(drawContext, "v", mouseX, mouseY, groupPanelX.getAsInt() + groupPanelWidth / 2 - size / 2, imageY + panelHeight - 2, size, 14, groupScrollHandler.isOffsetWithinBounds(10)); - drawContext.draw(); - // drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(true, isMouseOver(mouseX, mouseY, imageX + 3, imageY + 3, 14, 14)), imageX + 3, imageY + 3, 14, 14); - // drawContext.drawText(mc.textRenderer, "X", imageX + 10 - mc.textRenderer.getWidth("X") / 2, imageY + 6, -1, true); + drawSingularButton(graphics, "^", mouseX, mouseY, groupPanelX.getAsInt() + groupPanelWidth / 2 - size / 2, imageY - 14, size, 14, groupScrollHandler.isOffsetWithinBounds(-10)); + drawSingularButton(graphics, "v", mouseX, mouseY, groupPanelX.getAsInt() + groupPanelWidth / 2 - size / 2, imageY + panelHeight - 2, size, 14, groupScrollHandler.isOffsetWithinBounds(10)); + // graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURES.get(true, isMouseOver(mouseX, mouseY, imageX + 3, imageY + 3, 14, 14)), imageX + 3, imageY + 3, 14, 14); + // graphics.drawString(mc.font, "X", imageX + 10 - mc.font.width("X") / 2, imageY + 6, -1, true); + + searchBoxWidth = panelWidth/2; + searchBoxHeight = mc.font.lineHeight + 5; + searchBoxX = imageX + (panelWidth - searchBoxWidth) / 2; + searchBoxY = imageY - 18; - this.enableContextMenuScissor(); + renderSearchBox(graphics, mouseX, mouseY); contextMenu.setWidth(panelWidth - 4); contextMenu.y = imageY; - renderOptionGroups(drawContext, mouseX, mouseY); - renderSelectedGroupOptions(drawContext, mouseX, mouseY); + renderOptionGroups(graphics, mouseX, mouseY); + + this.enableContextMenuScissor(graphics); + + renderSelectedGroupOptions(graphics, mouseX, mouseY); contextMenu.setHeight(getContentHeight() + 15); - drawScrollbar(drawContext); + drawScrollbar(graphics); scrollHandler.updateScrollOffset(getMaxScrollOffset()); // Disable scissor after rendering - DrawHelper.disableScissor(); + DrawHelper.disableScissor(graphics); + } + + private void renderSearchBox(GuiGraphics graphics, int mouseX, int mouseY) { + searchBox.setX(searchBoxX); + searchBox.setY(searchBoxY); + searchBox.setWidth(searchBoxWidth); + searchBox.setHeight(searchBoxHeight); + + searchBox.render(graphics, mouseX, mouseY, mc.getDeltaTracker().getGameTimeDeltaTicks()); + + String icon = "\uD83D\uDD0D"; + int iconX = searchBoxX - 2 - mc.font.width(icon); + int iconY = searchBoxY + (searchBoxHeight - mc.font.lineHeight) / 2 + 1; + int iconColor = searchBox.isFocused()? Color.WHITE.getRGB() : 0x80FFFFFF; + //DrawHelper.drawRectangle(graphics,iconX - 2, iconY - 1, mc.font.width(icon) + 3, mc.font.lineHeight + 2,Color.BLACK.getRGB()); + //DrawHelper.drawOutlineBox(graphics,iconX - 2, iconY - 1, mc.font.width(icon) + 3,mc.font.lineHeight + 2,1f,Color.GRAY.getRGB()); + graphics.drawString(mc.font, icon, iconX, iconY, iconColor, false); } - public void drawSingularButton(DrawContext drawContext, String text, int mouseX, int mouseY, int x, int y, int width, int height, boolean enabled) { - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(enabled, isMouseOver(mouseX, mouseY, x, y, width, height)), x, y, width, height); - drawContext.drawText(mc.textRenderer, text, x + width / 2 - mc.textRenderer.getWidth(text) / 2, y + mc.textRenderer.fontHeight / 2 - 1, Color.WHITE.getRGB(), true); + public void drawSingularButton(GuiGraphics graphics, String Component, int mouseX, int mouseY, int x, int y, int width, int height, boolean enabled) { + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURES.get(enabled, isMouseOver(mouseX, mouseY, x, y, width, height)), x, y, width, height); + graphics.drawString(mc.font, Component, x + width / 2 - mc.font.width(Component) / 2, y + mc.font.lineHeight / 2 - 1, Color.WHITE.getRGB(), true); } - public void drawSingularButton(DrawContext drawContext, String text, int mouseX, int mouseY, int x, int y, int width, int height) { - this.drawSingularButton(drawContext, text, mouseX, mouseY, x, y, width, height, true); + public void drawSingularButton(GuiGraphics graphics, String Component, int mouseX, int mouseY, int x, int y, int width, int height) { + this.drawSingularButton(graphics, Component, mouseX, mouseY, x, y, width, height, true); } - private void renderOptionGroups(DrawContext drawContext, int mouseX, int mouseY) { + private void renderOptionGroups(GuiGraphics graphics, int mouseX, int mouseY) { int groupX = groupPanelX.getAsInt(); int groupY = imageY; - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); - drawContext.drawTexture(RenderLayer::getGuiTextured, GROUP_BACKGROUND, groupX - 10, groupY + 2, 0,0,groupPanelWidth + 20, panelHeight, 80, 158); - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + graphics.blit(RenderPipelines.GUI_TEXTURED, GROUP_BACKGROUND, groupX - 10, groupY + 2, 0,0,groupPanelWidth + 20, panelHeight - 2, groupPanelWidth + 20, panelHeight - 3); int yOffset = groupY + 12 - groupScrollHandler.getScrollOffset(); for (OptionGroup group : optionGroups) { if (yOffset >= groupY + 12 && yOffset <= groupY + panelHeight - 15) { - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(!group.isExpanded(), isMouseOver(mouseX, mouseY, groupX, yOffset, groupPanelWidth, 20)), groupX, yOffset, groupPanelWidth, 20); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURES.get(!group.isExpanded(), isMouseOver(mouseX, mouseY, groupX, yOffset, groupPanelWidth, 20)), groupX, yOffset, groupPanelWidth, 20); - DrawHelper.drawScrollableText(drawContext, mc.textRenderer, group.getName(), groupX + groupPanelWidth / 2, groupX + 2, yOffset, groupX + groupPanelWidth - 2, yOffset + 20, -1); + DrawHelper.drawScrollableText(graphics, mc.font, group.getName(), groupX + groupPanelWidth / 2, groupX + 2, yOffset, groupX + groupPanelWidth - 2, yOffset + 20, -1); - //Scrollable text uses scissor, so we need to enable the context menu scissor again - this.enableContextMenuScissor(); + //Scrollable Component uses scissor, so we need to enable the context menu scissor again + this.enableContextMenuScissor(graphics); yOffset += 20; // Space for the header } @@ -186,24 +254,33 @@ private void renderOptionGroups(DrawContext drawContext, int mouseX, int mouseY) } groupScrollHandler.updateScrollOffset(yOffset - groupY - 12 + groupScrollHandler.getScrollOffset() - panelHeight); - - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); } - private int renderSelectedGroupOptions(DrawContext drawContext, int mouseX, int mouseY) { + private int renderSelectedGroupOptions(GuiGraphics graphics, int mouseX, int mouseY) { int yOffset = imageY + 12 - scrollHandler.getScrollOffset(); - for (Option option : selectedGroup.getGroupOptions()) { + int targetWidth = panelWidth - 25; + + List> optionsToRender; + if (searchQuery != null && !searchQuery.isEmpty()) { + optionsToRender = getOptions(contextMenu); + } else { + optionsToRender = selectedGroup.getGroupOptions(); + } + + for (Option option : optionsToRender) { if (!option.shouldRender()) continue; - if (yOffset >= imageY - option.getHeight() && yOffset <= imageY + option.getHeight() + panelHeight) { - option.render(drawContext, imageX + 4, yOffset, mouseX, mouseY); + option.setHeight(getOptionHeight(option)); + yOffset = contextMenu.getLayoutEngine().layoutOption(option, imageX + 4, yOffset, targetWidth); + + if (option.getY() >= imageY - option.getHeight() && option.getY() <= imageY + option.getHeight() + panelHeight) { + option.render(graphics, option.getX(), option.getY(), mouseX, mouseY); } - yOffset += option.getHeight() + 1; } return yOffset; } - private void drawScrollbar(DrawContext drawContext) { + private void drawScrollbar(GuiGraphics graphics) { if (getMaxScrollOffset() > 0) { int scrollbarX = imageX + panelWidth + 10; int scrollbarY = imageY; @@ -211,8 +288,8 @@ private void drawScrollbar(DrawContext drawContext) { double handleHeight = panelHeight * ratio; int handleY = (int) (scrollbarY + (panelHeight - handleHeight) * ((double) scrollHandler.getScrollOffset() / getMaxScrollOffset())); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, SCROLL_BAR_BACKGROUND, scrollbarX, scrollbarY, DEFAULT_SCROLLBAR_WIDTH, panelHeight); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, SCROLLER_TEXTURE, scrollbarX, handleY, DEFAULT_SCROLLBAR_WIDTH, (int) handleHeight); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, SCROLL_BAR_BACKGROUND, scrollbarX, scrollbarY, DEFAULT_SCROLLBAR_WIDTH, panelHeight); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, SCROLLER_TEXTURE, scrollbarX, handleY, DEFAULT_SCROLLBAR_WIDTH, (int) handleHeight); } } @@ -236,9 +313,18 @@ public void mouseScrolled(ContextMenu menu, double mouseX, double mouseY, dou @Override public boolean mouseClicked(ContextMenu menu, double mouseX, double mouseY, int button) { + MouseButtonEvent event = new MouseButtonEvent(mouseX, mouseY, new MouseButtonInfo(button, 0)); + if (searchBox.mouseClicked(event, false)) { + searchBox.setFocused(true); + return true; + } else{ + searchBox.setFocused(false); + } + + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { if (isMouseOver(mouseX, mouseY, imageX + 3, imageY + 3, 14, 14)) { - mc.getSoundManager().play(PositionedSoundInstance.master( + mc.getSoundManager().play(SimpleSoundInstance.forUI( SoundEvents.UI_BUTTON_CLICK, 1.0F)); contextMenu.close(); @@ -248,12 +334,12 @@ public boolean mouseClicked(ContextMenu menu, double mouseX, double mouseY, i int size = (int) (groupPanelWidth * 0.5f); //Up and down button if (groupScrollHandler.isOffsetWithinBounds(-10) && isMouseOver(mouseX, mouseY, groupPanelX.getAsInt() + (double) groupPanelWidth / 2 - size / 2, imageY - 14, size, 14)) { - mc.getSoundManager().play(PositionedSoundInstance.master( + mc.getSoundManager().play(SimpleSoundInstance.forUI( SoundEvents.UI_BUTTON_CLICK, 1.0F)); groupScrollHandler.addOffset(-10); } if (groupScrollHandler.isOffsetWithinBounds(10) && isMouseOver(mouseX, mouseY, groupPanelX.getAsInt() + (double) groupPanelWidth / 2 - size / 2, imageY + panelHeight - 2, size, 14)) { - mc.getSoundManager().play(PositionedSoundInstance.master( + mc.getSoundManager().play(SimpleSoundInstance.forUI( SoundEvents.UI_BUTTON_CLICK, 1.0F)); groupScrollHandler.addOffset(10); } @@ -289,11 +375,16 @@ public boolean mouseReleased(ContextMenu menu, double mouseX, double mouseY, scrollHandler.stopDragging(); groupScrollHandler.stopDragging(); } + MouseButtonEvent event = new MouseButtonEvent(mouseX, mouseY, new MouseButtonInfo(button, 0)); + searchBox.mouseReleased(event); return super.mouseReleased(menu, mouseX, mouseY, button); } @Override public boolean mouseDragged(ContextMenu menu, double mouseX, double mouseY, int button, double deltaX, double deltaY) { + MouseButtonEvent event = new MouseButtonEvent(mouseX, mouseY, new MouseButtonInfo(button, 0)); + searchBox.mouseDragged(event, deltaX, deltaY); + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { if (isMouseOver(mouseX, mouseY, imageX + panelWidth + 5, imageY - 5, DEFAULT_SCROLLBAR_WIDTH + 5, panelHeight + 10)) { scrollHandler.updateScrollPosition(mouseY); @@ -304,8 +395,20 @@ public boolean mouseDragged(ContextMenu menu, double mouseX, double mouseY, i } @Override - public LayoutContext.Offset getGroupIndent() { - return LayoutContext.Offset.zero(); + public void keyPressed(ContextMenu menu, int key, int scanCode, int modifiers) { + searchBox.keyPressed(new KeyEvent(key, scanCode, modifiers)); + super.keyPressed(menu, key, scanCode, modifiers); + } + + @Override + public void charTyped(ContextMenu menu, char c, int modifiers) { + searchBox.charTyped(new CharacterEvent(c, modifiers)); + super.charTyped(menu, c, modifiers); + } + + @Override + public LayoutEngine.Offset getGroupIndent() { + return LayoutEngine.Offset.zero(); } public void setPanelColor(PanelColor panelColor) { @@ -336,7 +439,7 @@ public int getImageY() { * Group rendering handled already */ @Override - public void renderGroup(DrawContext drawContext, OptionGroup group, int groupX, int groupY, int mouseX, int mouseY) { + public void renderGroup(GuiGraphics graphics, OptionGroup group, int groupX, int groupY, int mouseX, int mouseY) { } public enum PanelColor { @@ -378,9 +481,6 @@ public static PanelColor custom(float red, float green, float blue, float alpha) return custom; } - public void applyColor() { - RenderSystem.setShaderColor(red, green, blue, alpha); - } public int getColor() { return new Color(red,green,blue,alpha).getRGB(); } @@ -388,155 +488,158 @@ public int getColor() { public class MinecraftBooleanRenderer implements SkinRenderer { @Override - public void render(DrawContext drawContext, BooleanOption option, int x, int y, int mouseX, int mouseY) { - drawContext.drawText(mc.textRenderer, option.name, x + 15, y + 25 / 2 - 5, -1, true); - - option.setPosition(x + panelWidth - 75, y); + public void render(GuiGraphics graphics, BooleanOption option, int x, int y, int mouseX, int mouseY) { + graphics.drawString(mc.font, option.name, x + 15, y + 25 / 2 - 5, -1, true); int width = 50; - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(true, option.isMouseOver(mouseX, mouseY)), option.getX(), y, width, 20); + option.setPosition(x + panelWidth - 75, y); + option.setWidth(width); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURES.get(true, isMouseOver(mouseX, mouseY,x + panelWidth - 75, y, width, 20)), x + panelWidth - 75, y, width, 20); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - Text text = option.getBooleanType().getText(option.value); + Component Component = option.getBooleanType().getText(option.value); int color = option.value ? Color.GREEN.getRGB() : Color.RED.getRGB(); - drawContext.drawText(mc.textRenderer, text, (int) (option.getX() + (width / 2.0f) - (mc.textRenderer.getWidth(text) / 2.0f)), y + 5, color, true); - - option.setHeight(25); - - //Widths don't matter in this skin - option.setWidth(width); + graphics.drawString(mc.font, Component, (int) (x + panelWidth - 75 + (width / 2.0f) - (mc.font.width(Component) / 2.0f)), y + 5, color, true); } + } + + public class MinecraftColorOptionRenderer implements SkinRenderer { + private ValueAnimation scaleAnimation; + private float scale = 0.0f; @Override - public boolean mouseClicked(BooleanOption option, double mouseX, double mouseY, int button) { - return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); + public void init(ColorOption option) { + this.scaleAnimation = new ValueAnimation(val-> scale = val, 0.0f, 1.0f); + scaleAnimation.easing(EasingType.EASE_OUT_BACK); + scaleAnimation.duration(200); + this.scaleAnimation.onComplete(() -> { + if (scale <= 0.0f) { + option.getColorGradient().close(); + } + }); } - } - public class MinecraftColorOptionRenderer implements SkinRenderer { @Override - public void render(DrawContext drawContext, ColorOption option, int x, int y, int mouseX, int mouseY) { - drawContext.drawText(mc.textRenderer, option.name, x + 15, y + 25 / 2 - 5, -1, true); + public void render(GuiGraphics graphics, ColorOption option, int x, int y, int mouseX, int mouseY) { + if (scaleAnimation != null) { + scaleAnimation.update(); + } - option.setPosition(x + panelWidth - 45, y); + graphics.drawString(mc.font, option.name, x + 15, y + 25 / 2 - 5, -1, true); int width = 20; - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(!option.isVisible, option.isMouseOver(mouseX, mouseY)), option.getX(), y, width, 20); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURES.get(!option.isVisible, isMouseOver(mouseX, mouseY,x + panelWidth - 45, y, width, 20)), x + panelWidth - 45, y, width, 20); int shadowOpacity = Math.min(option.value.getAlpha(), 45); - drawContext.draw(); - DrawHelper.drawRectangleWithShadowBadWay(drawContext, - option.getX() + 4, + DrawHelper.drawRectangleWithShadowBadWay(graphics, + x + panelWidth - 45 + 4, y + 4, width - 8, - 20 - 8, + 12, option.value.getRGB(), shadowOpacity, 1, 1); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - - option.setHeight(25); option.setWidth(width); if (option.getColorGradient().getColorPickerButton().isPicking()) { - DrawHelper.disableScissor(); // Disable scissor test for the colorpicker + // DrawHelper.disableScissor(graphics); } - //TODO: WHAT IS THISSSSS + int colorGradientWidth = option.getColorGradient().getBoxSize() + option.getColorGradient().getAlphaSlider().getWidth() + option.getColorGradient().getColorPickerButton().getWidth(); - option.getColorGradient().render(drawContext, x + panelWidth / 2 - colorGradientWidth / 2, y + 12, mouseX, mouseY); + int pickerX = x + panelWidth / 2 - colorGradientWidth / 2; + int pickerY = y + 12; - if (option.getColorGradient().shouldDisplay()) { + if (scale > 0.0f) { + DrawHelper.scaleAndPosition(graphics.pose(), pickerX + colorGradientWidth / 2.0f, pickerY, scale); + option.getColorGradient().render(graphics, pickerX, pickerY, mouseX, mouseY); + DrawHelper.stopScaling(graphics.pose()); + } + + int baseHeight = 25; + if (option.getColorGradient().shouldDisplay() || (scaleAnimation != null && !scaleAnimation.isFinished())) { int colorGradientHeight = option.getColorGradient().getBoxSize() + 10 + option.getColorGradient().getGradientSlider().getHeight(); - option.setHeight(option.getHeight() + colorGradientHeight); + option.setHeight(baseHeight + (int) (colorGradientHeight * scale)); + } else { + option.setHeight(baseHeight); } if (option.getColorGradient().getColorPickerButton().isPicking()) { - DrawHelper.enableScissor(imageX, imageY + 2, panelWidth, panelHeight - 4); + // DrawHelper.enableScissor(imageX, imageY + 2, panelWidth, panelHeight - 4, graphics); } } - } - public class MinecraftDoubleRenderer implements SkinRenderer { - private static final Identifier TEXTURE = Identifier.ofVanilla("widget/slider"); - private static final Identifier HIGHLIGHTED_TEXTURE = Identifier.ofVanilla("widget/slider_highlighted"); - private static final Identifier HANDLE_TEXTURE = Identifier.ofVanilla("widget/slider_handle"); - private static final Identifier HANDLE_HIGHLIGHTED_TEXTURE = Identifier.ofVanilla("widget/slider_handle_highlighted"); + @Override + public boolean mouseClicked(ColorOption option, double mouseX, double mouseY, int button) { + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, option.getX() + panelWidth - 45, option.getY(), 20, 20)) { + boolean isOpening = !option.getColorGradient().shouldDisplay(); + scaleAnimation.startValue(scale); + if (isOpening) { + option.getColorGradient().display(); + scaleAnimation.endValue(1.0f); + } else { + scaleAnimation.endValue(0.0f); + } + scaleAnimation.start(); + return true; + } + + if (option.getColorGradient().shouldDisplay()) { + option.getColorGradient().mouseClicked(mouseX, mouseY, button); + } + return false; + } @Override - public void init(DoubleOption option) { - SkinRenderer.super.init(option); + public boolean mouseDragged(ColorOption option, double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (option.getColorGradient().shouldDisplay()) { + option.getColorGradient().mouseDragged(mouseX, mouseY, button); + } + return SkinRenderer.super.mouseDragged(option, mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public boolean mouseReleased(ColorOption option, double mouseX, double mouseY, int button) { + if (option.getColorGradient().shouldDisplay()) { + option.getColorGradient().mouseReleased(mouseX, mouseY, button); + } + return SkinRenderer.super.mouseReleased(option, mouseX, mouseY, button); } + } + + public class MinecraftDoubleRenderer implements SkinRenderer { + private static final Identifier TEXTURE = Identifier.withDefaultNamespace("widget/slider"); + private static final Identifier HIGHLIGHTED_TEXTURE = Identifier.withDefaultNamespace("widget/slider_highlighted"); + private static final Identifier HANDLE_TEXTURE = Identifier.withDefaultNamespace("widget/slider_handle"); + private static final Identifier HANDLE_HIGHLIGHTED_TEXTURE = Identifier.withDefaultNamespace("widget/slider_handle_highlighted"); @Override - public void render(DrawContext drawContext, DoubleOption option, int x, int y, int mouseX, int mouseY) { - drawContext.drawText(mc.textRenderer, option.name, x + 15, y + 25 / 2 - 5, -1, true); + public void render(GuiGraphics graphics, DoubleOption option, int x, int y, int mouseX, int mouseY) { + graphics.drawString(mc.font, option.name, x + 15, y + 25 / 2 - 5, -1, true); option.setWidth(panelWidth - 150); - option.setHeight(25); option.setPosition(x + panelWidth - 122, y); double sliderX = option.getX() + ((option.value - option.minValue) / (option.maxValue - option.minValue)) * (option.getWidth() - 8); boolean isMouseOverHandle = isMouseOver(mouseX, mouseY, sliderX, y, 10, 20); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, option.isMouseOver(mouseX, mouseY) ? HIGHLIGHTED_TEXTURE : TEXTURE, option.getX(), y, option.getWidth(), 20); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, isMouseOverHandle ? HANDLE_HIGHLIGHTED_TEXTURE : HANDLE_TEXTURE, (int) Math.round(sliderX), y, 8, 20); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - // Determine the number of decimal places in option.step + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, option.isMouseOver(mouseX, mouseY) ? HIGHLIGHTED_TEXTURE : TEXTURE, option.getX(), y, option.getWidth(), 20); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, isMouseOverHandle ? HANDLE_HIGHLIGHTED_TEXTURE : HANDLE_TEXTURE, (int) Math.round(sliderX), y, 8, 20); + int decimalPlaces = String.valueOf(option.step).split("\\.")[1].length(); // Format option.value to the determined number of decimal places String formattedValue = String.format("%." + decimalPlaces + "f", option.value); - drawContext.drawText(mc.textRenderer, formattedValue, option.getX() + option.getWidth() / 2 - mc.textRenderer.getWidth(formattedValue) / 2, y + 5, 16777215, false); - } - - @Override - public boolean mouseClicked(DoubleOption option, double mouseX, double mouseY, int button) { - return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); - } - } - - public class MinecraftEnumRenderer> implements SkinRenderer> { - private int maxWidth = 50; - - private void calculateMaxWidth(EnumOption option) { - for (E enumConstant : option.getValues()) { - int width = mc.textRenderer.getWidth(enumConstant.name()) + 5; - if (width > maxWidth) { - maxWidth = width; - } - } - } - - @Override - public void render(DrawContext drawContext, EnumOption option, int x, int y, int mouseX, int mouseY) { - calculateMaxWidth(option); - option.setHeight(25); - option.setWidth(maxWidth); - - drawContext.drawText(mc.textRenderer, option.name.copy().append(": "), x + 15, y + 25 / 2 - 5, -1, true); - - option.setPosition(x + panelWidth - maxWidth - 25, y); - - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(true, option.isMouseOver(mouseX, mouseY)), option.getX(), y, maxWidth, 20); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - String text = option.get().toString(); - drawContext.drawText(mc.textRenderer, text, option.getX() + maxWidth / 2 - mc.textRenderer.getWidth(text) / 2, y + 5, Color.CYAN.getRGB(), true); + graphics.drawCenteredString(mc.font, formattedValue, option.getX() + option.getWidth() / 2, y + Math.round((float) mc.font.lineHeight /2), -1); } } - public class MinecraftListRenderer implements SkinRenderer> { + public class MinecraftCycleRenderer implements SkinRenderer> { private int maxWidth = 50; - private void calculateMaxWidth(ListOption option) { + private void calculateMaxWidth(CycleOption option) { for (E listValues : option.getValues()) { - int width = mc.textRenderer.getWidth(listValues.toString()) + 5; + int width = mc.font.width(listValues.toString()) + 5; if (width > maxWidth) { maxWidth = width; } @@ -544,60 +647,49 @@ private void calculateMaxWidth(ListOption option) { } @Override - public void render(DrawContext drawContext, ListOption option, int x, int y, int mouseX, int mouseY) { + public void render(GuiGraphics graphics, CycleOption option, int x, int y, int mouseX, int mouseY) { calculateMaxWidth(option); - option.setHeight(25); option.setWidth(maxWidth); - drawContext.drawText(mc.textRenderer, option.name.copy().append(": "), x + 15, y + 25 / 2 - 5, -1, true); + graphics.drawString(mc.font, option.name.copy().append(": "), x + 15, y + 25 / 2 - 5, -1, true); option.setPosition(x + panelWidth - maxWidth - 25, y); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(true, option.isMouseOver(mouseX, mouseY)), option.getX(), y, maxWidth, 20); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - String text = option.get().toString(); - drawContext.drawText(mc.textRenderer, text, option.getX() + maxWidth / 2 - mc.textRenderer.getWidth(text) / 2, y + 5, Color.CYAN.getRGB(), true); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURES.get(true, option.isMouseOver(mouseX, mouseY)), option.getX(), y, maxWidth, 20); + String Component = option.get().toString(); + graphics.drawString(mc.font, Component, option.getX() + maxWidth / 2 - mc.font.width(Component) / 2, y + 5, Color.CYAN.getRGB(), true); } } public class MinecraftSubMenuRenderer implements SkinRenderer { @Override - public void render(DrawContext drawContext, SubMenuOption option, int x, int y, int mouseX, int mouseY) { - option.setHeight(20); + public void render(GuiGraphics graphics, SubMenuOption option, int x, int y, int mouseX, int mouseY) { option.setWidth(30); - drawContext.drawText(mc.textRenderer, option.name, x + 15, y + 25 / 2 - 5, -1, true); + graphics.drawString(mc.font, option.name, x + 15, y + 25 / 2 - 5, -1, true); option.setPosition(x + panelWidth - 55, y); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(true, option.isMouseOver(mouseX, mouseY)), option.getX(), y, option.getWidth(), 20); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - String text = "Open"; - drawContext.drawText(mc.textRenderer, text, option.getX() + option.getWidth() / 2 - mc.textRenderer.getWidth(text) / 2, y + 5, Color.YELLOW.getRGB(), true); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURES.get(true, option.isMouseOver(mouseX, mouseY)), option.getX(), y, option.getWidth(), 20); + String Component = "Open"; + graphics.drawString(mc.font, Component, option.getX() + option.getWidth() / 2 - mc.font.width(Component) / 2, y + 5, Color.YELLOW.getRGB(), true); - option.getSubMenu().render(drawContext, x + option.getParentMenu().getWidth(), y, mouseX, mouseY); + option.getSubMenu().render(graphics, x + option.getParentMenu().getWidth(), y, mouseX, mouseY); } } public class MinecraftRunnableRenderer implements SkinRenderer { - Color DARK_RED = new Color(116, 0, 0); - Color DARK_GREEN = new Color(24, 132, 0, 226); @Override - public void render(DrawContext drawContext, RunnableOption option, int x, int y, int mouseX, int mouseY) { - option.setHeight(25); + public void render(GuiGraphics graphics, RunnableOption option, int x, int y, int mouseX, int mouseY) { option.setWidth(26); - drawContext.drawText(mc.textRenderer, option.name.copy().append(": "), x + 15, y + 25 / 2 - 5, -1, true); + graphics.drawString(mc.font, option.name.copy().append(": "), x + 15, y + 25 / 2 - 5, -1, true); option.setPosition(x + panelWidth - 51, y); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(!option.value, option.isMouseOver(mouseX, mouseY)), option.getX(), y, option.getWidth(), 20); - drawContext.drawText(mc.textRenderer, "Run", option.getX() + option.getWidth() / 2 - mc.textRenderer.getWidth("Run") / 2, y + 5, option.value ? DARK_GREEN.getRGB() : DARK_RED.getRGB(), true); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURES.get(!option.value, option.isMouseOver(mouseX, mouseY)), option.getX(), y, option.getWidth(), 20); + graphics.drawString(mc.font, "Run", option.getX() + option.getWidth() / 2 - mc.font.width("Run") / 2, y + 5, option.value ? DARK_GREEN.getRGB() : DARK_RED.getRGB(), true); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ModernSkin.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ModernSkin.java index 17fc98d..b227536 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ModernSkin.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ModernSkin.java @@ -1,60 +1,78 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem; -import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.platform.cursor.CursorTypes; import com.tanishisherewith.dynamichud.helpers.ColorHelper; import com.tanishisherewith.dynamichud.helpers.DrawHelper; import com.tanishisherewith.dynamichud.helpers.animationhelper.EasingType; import com.tanishisherewith.dynamichud.helpers.animationhelper.animations.MathAnimations; +import com.tanishisherewith.dynamichud.helpers.animationhelper.animations.SquishAnimator; +import com.tanishisherewith.dynamichud.helpers.animationhelper.animations.ValueAnimation; +import com.tanishisherewith.dynamichud.utils.Util; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutContext; +import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutEngine; import com.tanishisherewith.dynamichud.utils.contextmenu.options.*; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.GroupableSkin; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.SkinRenderer; import com.tanishisherewith.dynamichud.utils.handlers.ScrollHandler; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.sound.PositionedSoundInstance; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.sound.SoundEvents; -import net.minecraft.text.OrderedText; -import net.minecraft.text.StringVisitable; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.input.MouseButtonInfo; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.Mth; +import org.jspecify.annotations.NonNull; import org.lwjgl.glfw.GLFW; import java.awt.*; -import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import static com.tanishisherewith.dynamichud.helpers.ColorHelper.DARK_GREEN; +import static com.tanishisherewith.dynamichud.helpers.ColorHelper.DARK_RED; public class ModernSkin extends Skin implements GroupableSkin { - static Color DARK_GRAY = new Color(20, 20, 20, 229); + static Color DARK_GRAY = new Color(20, 20, 20, 180); static Color DARKER_GRAY = new Color(10, 10, 10, 243); static Color DARKER_GRAY_2 = new Color(12, 12, 12, 246); - private final Color themeColor; private final float radius; - private final Text defaultToolTipHeader; - private final Text defaultToolTipText; + private final Component defaultToolTipHeader; + private final Component defaultToolTipText; private int contextMenuX = 0, contextMenuY = 0; private int width = 0, height = 0; private float scaledWidth = 0, scaledHeight = 0; - private Text TOOLTIP_TEXT; - private Text TOOLTIP_HEAD; + private Component TOOLTIP_TEXT; + private Component TOOLTIP_HEAD; private static int SCALE_FACTOR = 4; private final ScrollHandler scrollHandler; + int searchBoxWidth = 0; + int searchBoxHeight = 14; + int searchBoxX = 0; + int searchBoxY = 0; + + private final Map groupAnimations = new HashMap<>(); - public ModernSkin(Color themeColor, float radius, Text defaultToolTipHeader, Text defaultToolTipText) { - this.themeColor = themeColor; + private final ModernSearchBox searchBox; + private String searchQuery = ""; + + public ModernSkin(float radius, Component defaultToolTipHeader, Component defaultToolTipText) { this.radius = radius; - TOOLTIP_TEXT = defaultToolTipText; - TOOLTIP_HEAD = defaultToolTipHeader; + this.TOOLTIP_TEXT = defaultToolTipText; + this.TOOLTIP_HEAD = defaultToolTipHeader; this.defaultToolTipText = defaultToolTipText; this.defaultToolTipHeader = defaultToolTipHeader; addRenderer(BooleanOption.class, ModernBooleanRenderer::new); addRenderer(DoubleOption.class, ModernDoubleRenderer::new); - addRenderer(EnumOption.class, ModernEnumRenderer::new); - addRenderer(ListOption.class, ModernListRenderer::new); + addRenderer(CycleOption.class, ModernCycleRenderer::new); addRenderer(SubMenuOption.class, ModernSubMenuRenderer::new); addRenderer(RunnableOption.class, ModernRunnableRenderer::new); addRenderer(ColorOption.class, ModernColorOptionRenderer::new); @@ -62,102 +80,177 @@ public ModernSkin(Color themeColor, float radius, Text defaultToolTipHeader, Tex this.scrollHandler = new ScrollHandler(); setCreateNewScreen(true); + + searchBox = new ModernSearchBox(searchBoxX, searchBoxY, searchBoxWidth, searchBoxHeight); + searchBox.setResponder(query -> { + searchQuery = query; + // Reset scroll so results show from top + scrollHandler.setScrollOffset(0); + }); } - public ModernSkin(Color themeColor, float radius) { - this(themeColor, radius, Text.of("Example Tip"), Text.of("Hover over a setting to see its tool-tip (if present) here!")); + @Override + public List> getOptions(ContextMenu menu) { + if(searchQuery != null && !searchQuery.isEmpty()) { + return Util.getSearchResults(searchQuery,-1, contextMenu.getOptions()); + } + return super.getOptions(menu); } - public ModernSkin(Color themeColor) { - this(themeColor, 4); + public ModernSkin(float radius) { + this(radius, Component.literal("Example Tip"), Component.literal("Hover over a setting to see its tool-tip (if present) here!")); } public ModernSkin() { - this(Color.CYAN.darker().darker()); + this(4); } @Override - public LayoutContext.Offset getGroupIndent() { - return new LayoutContext.Offset(2, 2); + public LayoutEngine.Offset getGroupIndent() { + return new LayoutEngine.Offset(4, 2); } - public void enableSkinScissor() { - DrawHelper.enableScissor(contextMenuX + (int) (width * 0.2f) + 10, contextMenuY + 19, (int) (width * 0.8f - 14), height - 23, SCALE_FACTOR); + public void enableSkinScissor(GuiGraphics graphics) { + DrawHelper.enableScissor(contextMenuX + (int) (width * 0.2f) + 10, contextMenuY + 19, (int) (width * 0.8f - 14), height - 23, SCALE_FACTOR, graphics); } @Override - public void renderGroup(DrawContext drawContext, OptionGroup group, int groupX, int groupY, int mouseX, int mouseY) { - mouseX = (int) (mc.mouse.getX() / SCALE_FACTOR); - mouseY = (int) (mc.mouse.getY() / SCALE_FACTOR); + public void renderGroup(GuiGraphics graphics, OptionGroup group, int groupX, int groupY, int mouseX, int mouseY) { + int targetWidth = (int) (width * 0.8f - 18); + renderGroup(graphics, group, groupX, groupY, targetWidth, mouseX, mouseY); + } + private int calcOptionHeight(Option option) { + if (option instanceof BooleanOption || option instanceof DoubleOption) return 14; + if (option instanceof CycleOption) return 14; + if (option instanceof SubMenuOption) return 14; + if (option instanceof RunnableOption) return mc.font.lineHeight + 6; + if (option instanceof ColorOption colorOption) { + return colorOption.getHeight() > 0 ? colorOption.getHeight() : 20; + } + if (option instanceof OptionGroup group) { + return group.isExpanded() ? group.getHeight() : 16; + } + return option.getHeight() > 0 ? option.getHeight() : mc.font.lineHeight; + } + + private void renderSearchBox(GuiGraphics graphics, int mouseX, int mouseY){ + searchBox.setTotalBounds(searchBoxX, searchBoxY, searchBoxWidth, searchBoxHeight); + searchBox.render(graphics, mouseX, mouseY, mc.getDeltaTracker().getGameTimeDeltaTicks()); + } + + private int computeGroupFullHeight(OptionGroup group, int groupX, int groupY, int targetWidth) { + if (!group.isExpanded()) return 16; - if (group.isExpanded() && group.getHeight() > 20) { - DrawHelper.drawRoundedRectangle(drawContext, - groupX + 1, groupY + 14, width - groupX - 8 + contextMenuX, group.getHeight() - 16, radius, DARKER_GRAY_2.getRGB()); + int yOffset = groupY + 16 + getGroupIndent().top(); // header height + indent + int nestedIndent = getGroupIndent().left(); + int subWidth = targetWidth - nestedIndent - 8; + + for (Option option : group.getGroupOptions()) { + if (!option.shouldRender()) continue; + option.setHeight(calcOptionHeight(option)); + yOffset = contextMenu.getLayoutEngine().layoutOption(option, groupX + nestedIndent, yOffset, subWidth); + } + return yOffset - groupY; // total height + } + + // Adds a nice animation while opening and closing + public void renderGroup(GuiGraphics graphics, OptionGroup group, int groupX, int groupY, int targetWidth, int mouseX, int mouseY) { + GroupAnimData animData = groupAnimations.computeIfAbsent(group, g -> new GroupAnimData(16f)); + if (group.isExpanded() && animData.value <= 16) { + int fullHeight = computeGroupFullHeight(group, groupX, groupY, targetWidth); + animData.value = (float) fullHeight; + } + + if (animData.animation != null) { + animData.animation.update(); + if(animData.animation.isFinished()){ + animData.animation = null; + } + } + + int groupHeight = Math.round(animData.value); + + if (group.isExpanded() && groupHeight > 16) { + DrawHelper.drawRoundedRectangle(graphics, + groupX + 1, groupY + 14, width - groupX - 8 + contextMenuX, groupHeight - 16 + getGroupIndent().top(), radius, DARKER_GRAY_2.getRGB()); } - Text groupText = group.name.copy().append(" " + (group.isExpanded() ? "-" : "+")); + Component groupText = group.name.copy().append(" " + (group.isExpanded() ? "-" : "+")); + + DrawHelper.drawRoundedRectangle(graphics, + groupX + 1, groupY + 1, true, true, !group.isExpanded(), !group.isExpanded(), mc.font.width(groupText) + 6, 16, radius, DARKER_GRAY_2.getRGB()); - DrawHelper.drawRoundedRectangle(drawContext, - groupX + 1, groupY + 1, true, true, !group.isExpanded(), !group.isExpanded(), mc.textRenderer.getWidth(groupText) + 6, 16, radius, DARKER_GRAY_2.getRGB()); + graphics.drawString(mc.font, groupText, groupX + 4, groupY + 5, -1, true); - drawContext.drawText(mc.textRenderer, groupText, groupX + 4, groupY + 4, -1, true); + if (group.isExpanded() && groupHeight > 16) { + int clipX = groupX + 1; + int clipY = groupY + 16; + int clipWidth = targetWidth + getGroupIndent().left() + 8; + int clipHeight = groupHeight - 16; + DrawHelper.enableScissor(clipX, clipY, clipWidth, clipHeight, SCALE_FACTOR, graphics); + + int yOffset = groupY + 16 + getGroupIndent().top(); + int nestedIndent = getGroupIndent().left(); + int subWidth = targetWidth - nestedIndent - 8; - if (group.isExpanded()) { - int yOffset = groupY + 16 + getGroupIndent().top; for (Option option : group.getGroupOptions()) { if (!option.shouldRender()) continue; - - option.render(drawContext, groupX + getGroupIndent().left, yOffset, mouseX, mouseY); - yOffset += option.getHeight() + 1; + option.setHeight(calcOptionHeight(option)); + yOffset = contextMenu.getLayoutEngine().layoutOption(option, groupX + nestedIndent, yOffset, subWidth); + option.render(graphics, option.getX(), option.getY(), mouseX, mouseY); } - group.setHeight(yOffset - groupY); - } else { - group.setHeight(20); + DrawHelper.disableScissor(graphics); } + + // actual height for layout + group.setHeight(groupHeight); } - private void drawScrollbar(DrawContext drawContext) { + private void drawScrollbar(GuiGraphics graphics) { if (getMaxScrollOffset() > 0) { - int scrollbarX = contextMenuX + width + 5; // Position at the right of the panel - int scrollbarY = contextMenuY + 19; // Position below the header + int scrollbarX = contextMenuX + width + 5; + int scrollbarY = contextMenuY + 19; int handleHeight = (int) ((float) (height - 23) * ((height - 23) / (float) contextMenu.getHeight())); int handleY = scrollbarY + (int) ((float) ((height - 23) - handleHeight) * ((float) scrollHandler.getScrollOffset() / getMaxScrollOffset())); - DrawHelper.drawRoundedRectangle(drawContext, scrollbarX, scrollbarY, 2, height - 23, 1, DARKER_GRAY.getRGB()); - DrawHelper.drawRoundedRectangle(drawContext, scrollbarX, handleY, 2, handleHeight, 1, Color.LIGHT_GRAY.getRGB()); + DrawHelper.drawRoundedRectangle(graphics, scrollbarX, scrollbarY, 2, height - 23, 1, DARKER_GRAY.getRGB()); + DrawHelper.drawRoundedRectangle(graphics, scrollbarX, handleY, 2, handleHeight, 1, Color.LIGHT_GRAY.getRGB()); } } @Override - public void renderContextMenu(DrawContext drawContext, ContextMenu contextMenu, int mouseX, int mouseY) { - //This is equivalent to "Auto" GUI scale in minecraft options - SCALE_FACTOR = mc.getWindow().calculateScaleFactor(0, mc.forcesUnicodeFont()); + public void renderContextMenu(GuiGraphics graphics, ContextMenu contextMenu, int mouseX, int mouseY) { + SCALE_FACTOR = mc.getWindow().calculateScale(0, mc.isEnforceUnicode()); + this.contextMenu = contextMenu; - mouseX = (int) (mc.mouse.getX() / SCALE_FACTOR); - mouseY = (int) (mc.mouse.getY() / SCALE_FACTOR); - - // Apply custom scaling to counteract Minecraft's default scaling - DrawHelper.customScaledProjection(SCALE_FACTOR); + DrawHelper.scaledProjection(SCALE_FACTOR, graphics); updateContextDimensions(); contextMenu.set(contextMenuX, contextMenuY, 0); - //Background - DrawHelper.drawRoundedRectangle(drawContext, + // Background + DrawHelper.drawRoundedRectangle(graphics, contextMenuX, contextMenuY, width, height, radius, DARKER_GRAY.getRGB()); - drawBackButton(drawContext, mouseX, mouseY); + mouseX = (int) (mc.mouseHandler.xpos() / SCALE_FACTOR); + mouseY = (int) (mc.mouseHandler.ypos() / SCALE_FACTOR); + + renderSearchBox(graphics,mouseX, mouseY); + + drawBackButton(graphics, mouseX, mouseY); - //OptionStartX = Tool-Tip width + padding int optionStartX = contextMenu.x + (int) (width * 0.2f) + 10; + int targetWidth = (int) (width * 0.8f - 18); - //Background behind the options - DrawHelper.drawRoundedRectangle(drawContext, + // Background behind the options scroll area + DrawHelper.drawRoundedRectangle(graphics, optionStartX, contextMenuY + 19, width * 0.8f - 14, height - 23, radius, DARK_GRAY.getRGB()); - enableSkinScissor(); - int yOffset = contextMenu.y + 19 + 3 - scrollHandler.getScrollOffset(); + enableSkinScissor(graphics); + + int yPos = contextMenu.y + 22 - scrollHandler.getScrollOffset(); + for (Option option : getOptions(contextMenu)) { if (!option.shouldRender()) continue; @@ -165,68 +258,72 @@ public void renderContextMenu(DrawContext drawContext, ContextMenu contextMen setTooltipText(option.name, option.description); } + option.setHeight(calcOptionHeight(option)); + int nextY = contextMenu.getLayoutEngine().layoutOption(option, optionStartX + 2, yPos, targetWidth); + if (option instanceof OptionGroup group) { - this.renderGroup(drawContext, group, optionStartX + 2, yOffset, mouseX, mouseY); + this.renderGroup(graphics, group, optionStartX + 2, yPos, mouseX, mouseY); + yPos += group.getHeight() + contextMenu.getLayoutEngine().getItemSpacing(); } else { - option.render(drawContext, optionStartX + 2, yOffset, mouseX, mouseY); + option.render(graphics, option.getX(), option.getY(), mouseX, mouseY); + yPos = nextY; } - - yOffset += option.getHeight() + 1; } - drawContext.draw(); - RenderSystem.disableScissor(); + + DrawHelper.disableScissor(graphics); contextMenu.setWidth(width); - contextMenu.setHeight(yOffset - (contextMenu.y + 19 + 3 - scrollHandler.getScrollOffset()) + 4); + contextMenu.setHeight(yPos - (contextMenu.y + 26 - scrollHandler.getScrollOffset())); scrollHandler.updateScrollOffset(getMaxScrollOffset()); - drawScrollbar(drawContext); + drawScrollbar(graphics); - renderToolTipText(drawContext, mouseX, mouseY); + renderToolTipText(graphics, mouseX, mouseY); - //Reset our scaling so minecraft runs normally\ - DrawHelper.scaledProjection(); + DrawHelper.stopScaling(graphics.pose()); } private void updateContextDimensions() { - scaledWidth = (float) mc.getWindow().getFramebufferWidth() / SCALE_FACTOR; - scaledHeight = (float) mc.getWindow().getFramebufferHeight() / SCALE_FACTOR; + scaledWidth = (float) mc.getWindow().getWidth() / SCALE_FACTOR; + scaledHeight = (float) mc.getWindow().getHeight() / SCALE_FACTOR; contextMenuX = (int) (scaledWidth * 0.1f); contextMenuY = (int) (scaledHeight * 0.1f); width = (int) (scaledWidth * 0.8f); height = (int) (scaledHeight * 0.8f); + searchBoxWidth = (int)(width * 0.35f); + searchBoxHeight = mc.font.lineHeight + 5; + searchBoxX = contextMenuX + searchBoxWidth; + searchBoxY = contextMenuY + 2; } - public void drawBackButton(DrawContext drawContext, int mouseX, int mouseY) { + public void drawBackButton(GuiGraphics graphics, int mouseX, int mouseY) { String backText = "< Back"; - int textWidth = mc.textRenderer.getWidth(backText); + int textWidth = mc.font.width(backText); boolean isHoveringOver = isMouseOver(mouseX, mouseY, contextMenuX + 2, contextMenuY + 2, textWidth + 8, 14); - int color = isHoveringOver ? themeColor.darker().getRGB() : themeColor.getRGB(); + int color = isHoveringOver ? getThemeColor().darker().getRGB() : getThemeColor().getRGB(); - DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext, + DrawHelper.drawRoundedRectangleWithShadowBadWay(graphics, contextMenuX + 2, contextMenuY + 2, textWidth + 8, 14, radius, color, 125, 2, 2); - drawContext.drawText(mc.textRenderer, backText, contextMenuX + 6, contextMenuY + 5, -1, true); - drawContext.draw(); + graphics.drawString(mc.font, backText, contextMenuX + 6, contextMenuY + 5, -1, true); } - public void renderToolTipText(DrawContext drawContext, int mouseX, int mouseY) { + public void renderToolTipText(GuiGraphics graphics, int mouseX, int mouseY) { int tooltipY = contextMenuY + 19; int toolTipWidth = (int) (width * 0.2f) + 4; int toolTipHeight = (int) (height * 0.16f); if (!TOOLTIP_TEXT.getString().isEmpty()) { - toolTipHeight = Math.max(toolTipHeight, mc.textRenderer.getWrappedLinesHeight(TOOLTIP_TEXT, toolTipWidth)) + 18; + toolTipHeight = Math.max(toolTipHeight, mc.font.wordWrapHeight(TOOLTIP_TEXT, toolTipWidth)) + 18; toolTipHeight = Math.min(height - 23, toolTipHeight); } float textScale = 0.8f; - // Draw background DrawHelper.drawRoundedRectangle( - drawContext, + graphics, contextMenuX + 2, tooltipY, toolTipWidth, @@ -235,7 +332,7 @@ public void renderToolTipText(DrawContext drawContext, int mouseX, int mouseY) { DARK_GRAY.getRGB() ); DrawHelper.drawHorizontalLine( - drawContext, + graphics, contextMenuX + 2, toolTipWidth, tooltipY + 16, @@ -248,9 +345,10 @@ public void renderToolTipText(DrawContext drawContext, int mouseX, int mouseY) { return; } - //Draw the head text - drawContext.drawText( - mc.textRenderer, + DrawHelper.enableScissor(contextMenuX + 2, tooltipY,toolTipWidth,toolTipHeight,SCALE_FACTOR,graphics); + + graphics.drawString( + mc.font, TOOLTIP_HEAD, contextMenuX + 4, tooltipY + 4, @@ -258,33 +356,33 @@ public void renderToolTipText(DrawContext drawContext, int mouseX, int mouseY) { true ); - List wrappedText = mc.textRenderer.wrapLines(StringVisitable.styled(TOOLTIP_TEXT.getString(), TOOLTIP_TEXT.getStyle()), toolTipWidth); + List wrappedText = mc.font.split(TOOLTIP_TEXT, toolTipWidth); - DrawHelper.scaleAndPosition(drawContext.getMatrices(), contextMenuX + 4, tooltipY + 19, textScale); + DrawHelper.scaleAndPosition(graphics.pose(), contextMenuX + 4, tooltipY + 19, textScale); - // Draw text int textY = tooltipY + 19; - for (OrderedText line : wrappedText) { - drawContext.drawText( - mc.textRenderer, + for (FormattedCharSequence line : wrappedText) { + graphics.drawString( + mc.font, line, contextMenuX + 2 + 2, textY, -1, false ); - textY += mc.textRenderer.fontHeight; + textY += mc.font.lineHeight; } - drawContext.draw(); - DrawHelper.stopScaling(drawContext.getMatrices()); + DrawHelper.stopScaling(graphics.pose()); setTooltipText(defaultToolTipHeader, defaultToolTipText); + + DrawHelper.disableScissor(graphics); } - public void setTooltipText(Text head_text, Text tooltip_text) { - TOOLTIP_TEXT = tooltip_text; - TOOLTIP_HEAD = head_text; + public void setTooltipText(Component head_text, Component tooltip_text) { + this.TOOLTIP_TEXT = tooltip_text; + this.TOOLTIP_HEAD = head_text; } private int getMaxScrollOffset() { @@ -297,104 +395,146 @@ public void mouseScrolled(ContextMenu menu, double mouseX, double mouseY, dou scrollHandler.mouseScrolled(verticalAmount); } + @Override + public void keyPressed(ContextMenu menu, int key, int scanCode, int modifiers) { + if(searchBox.keyPressed(new KeyEvent(key,scanCode,modifiers))){ + return; + } + + super.keyPressed(menu, key, scanCode, modifiers); + } + + @Override + public void charTyped(ContextMenu menu, char c, int modifiers) { + if (searchBox.charTyped(new CharacterEvent((int) c,modifiers))) { + return; + } + super.charTyped(menu, c, modifiers); + } + @Override public boolean mouseReleased(ContextMenu menu, double mouseX, double mouseY, int button) { scrollHandler.stopDragging(); + MouseButtonEvent event = new MouseButtonEvent(mouseX, mouseY, new MouseButtonInfo(button, 0)); + searchBox.mouseReleased(event); return super.mouseReleased(menu, mouseX, mouseY, button); } @Override public boolean mouseClicked(ContextMenu menu, double mouseX, double mouseY, int button) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; - if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, contextMenuX + width - 5, contextMenuY, 7, height)) { + MouseButtonEvent event = new MouseButtonEvent(mouseX, mouseY, new MouseButtonInfo(button, 0)); + searchBox.mouseClicked(event,false); + + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, contextMenuX + width - 5, contextMenuY + 19, 7, height)) { scrollHandler.startDragging(mouseY); } if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { int optionStartX = contextMenuX + (int) (width * 0.2f) + 10; - int yOffset = contextMenu.y + 22 - scrollHandler.getScrollOffset(); + int yPos = contextMenu.y + 22 - scrollHandler.getScrollOffset(); + int spacing = contextMenu.getLayoutEngine().getItemSpacing(); + for (Option option : getOptions(contextMenu)) { if (!option.shouldRender()) continue; - + int optHeight = calcOptionHeight(option); if (option instanceof OptionGroup group) { - if (isMouseOver(mouseX, mouseY, optionStartX + 2, yOffset, - mc.textRenderer.getWidth(group.name + " " + (group.isExpanded() ? "-" : "+")) + 6, - 16)) { - group.setExpanded(!group.isExpanded()); - break; + Component groupText = group.name.copy().append(" " + (group.isExpanded() ? "-" : "+")); + if (isMouseOver(mouseX, mouseY, optionStartX + 2, yPos, + mc.font.width(groupText) + 6, 16)) { + boolean willBeExpanded = !group.isExpanded(); + group.setExpanded(willBeExpanded); + + GroupAnimData animData = groupAnimations.computeIfAbsent(group, g -> new GroupAnimData(16f)); + float current = animData.value; + float target; + if (willBeExpanded) { + int targetWidthForGroup = (int) (width * 0.8f - 18); + int fullHeight = computeGroupFullHeight(group, optionStartX + 2, yPos, targetWidthForGroup); + target = Math.max(fullHeight, 16f); + } else { + target = 16f; + } + + ValueAnimation anim = new ValueAnimation(val -> animData.value = val, current, target, EasingType.EASE_OUT_QUAD); + anim.duration(200); + anim.start(); + animData.animation = anim; + return true; } + yPos += group.getHeight() + spacing; + } else { + yPos += optHeight + spacing; } - - yOffset += option.getHeight() + 1; } } - if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, contextMenuX + 2, contextMenuY + 2, mc.textRenderer.getWidth("< Back") + 8, 14)) { - mc.getSoundManager().play(PositionedSoundInstance.master( + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, contextMenuX + 1, contextMenuY + 1, mc.font.width("< Back") + 10, 16)) { + mc.getSoundManager().play(SimpleSoundInstance.forUI( SoundEvents.UI_BUTTON_CLICK, 1.0F)); contextMenu.close(); + if (searchBox != null) { + searchBox.setFocused(false); + searchBox.setValue(""); + } + searchQuery = ""; + groupAnimations.clear(); + return true; } return super.mouseClicked(menu, mouseX, mouseY, button); } @Override public boolean mouseDragged(ContextMenu menu, double mouseX, double mouseY, int button, double deltaX, double deltaY) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; + if (searchBox != null) { + MouseButtonEvent event = new MouseButtonEvent(mouseX, mouseY, new MouseButtonInfo(button, 0)); + searchBox.mouseDragged(event, deltaX, deltaY); + } - if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, contextMenuX + width - 5, contextMenuY, 7, height)) { + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; + + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, contextMenuX + width - 5, contextMenuY + 19, 7, height)) { scrollHandler.updateScrollPosition(mouseY); } return super.mouseDragged(menu, mouseX, mouseY, button, deltaX, deltaY); } public Color getThemeColor() { - return themeColor; + return contextMenu.getProperties().getAccentColor(); } public class ModernBooleanRenderer implements SkinRenderer { private long animationStartTime; @Override - public void render(DrawContext drawContext, BooleanOption option, int x, int y, int mouseX, int mouseY) { - int backgroundWidth = (int) (width * 0.8f - 14); - - option.setHeight(14); - option.setPosition(x, y); - option.setWidth(backgroundWidth); - - MatrixStack matrices = drawContext.getMatrices(); - - // Calculate the current progress of the animation - int toggleBgX = x + backgroundWidth - 30; - // Background + public void render(GuiGraphics graphics, BooleanOption option, int x, int y, int mouseX, int mouseY) { + int toggleBgX = x + option.getWidth() - 30; boolean active = option.get(); Color backgroundColor = active ? getThemeColor() : DARKER_GRAY; Color hoveredColor = isMouseOver(mouseX, mouseY, toggleBgX, y + 2, 14, 7) ? backgroundColor.darker() : backgroundColor; DrawHelper.drawRoundedRectangleWithShadowBadWay( - drawContext, + graphics, toggleBgX, y + 2, 14, 7, 3, hoveredColor.getRGB(), 125, 1, 1 ); - // Draw toggle circle float startX = active ? toggleBgX + 4 : toggleBgX + 10; float endX = active ? toggleBgX + 10 : toggleBgX + 4; EasingType easingType = active ? EasingType.EASE_IN_CUBIC : EasingType.EASE_OUT_QUAD; float toggleX = MathAnimations.lerp(startX, endX, animationStartTime, 200f, easingType); - DrawHelper.drawFilledCircle(drawContext, toggleX, y + 2 + 3.3f, 2.8f, Color.WHITE.getRGB()); + DrawHelper.drawFilledCircle(graphics, toggleX, y + 2 + 3.3f, 2.8f, Color.WHITE.getRGB()); - // Draw option name - drawContext.drawText( - mc.textRenderer, + graphics.drawString( + mc.font, option.name, - x + 2, - y + 4, + x + 4, + y + 2, -1, false ); @@ -402,11 +542,10 @@ public void render(DrawContext drawContext, BooleanOption option, int x, int y, @Override public boolean mouseClicked(BooleanOption option, double mouseX, double mouseY, int button) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; - int backgroundWidth = (int) (width * 0.8f - 14); - int toggleBgX = option.getX() + backgroundWidth - 30; + int toggleBgX = option.getX() + option.getWidth() - 30; if (isMouseOver(mouseX, mouseY, toggleBgX, option.getY(), 14, option.getHeight())) { option.set(!option.get()); @@ -417,11 +556,12 @@ public boolean mouseClicked(BooleanOption option, double mouseX, double mouseY, } } - public class ModernColorOptionRenderer implements SkinRenderer { private static final float ANIMATION_SPEED = 0.1f; private float scale = 0f; private boolean display = false; + private final SquishAnimator animator = new SquishAnimator(); + public void update(ColorOption option) { if (option.getColorGradient().shouldDisplay() && display) { @@ -430,39 +570,38 @@ public void update(ColorOption option) { if (!display) { scale -= ANIMATION_SPEED; } - scale = MathHelper.clamp(scale, 0, 1.0f); + scale = Math.clamp(scale, 0, 1.0f); if (scale <= 0) { option.getColorGradient().close(); } } @Override - public void render(DrawContext drawContext, ColorOption option, int x, int y, int mouseX, int mouseY) { + public void render(GuiGraphics graphics, ColorOption option, int x, int y, int mouseX, int mouseY) { update(option); - int backgroundWidth = (int) (width * 0.8f - 14); - - // Draw option name - drawContext.drawText( - mc.textRenderer, + graphics.drawString( + mc.font, option.name, - x + 2, + x + 4, y + 5, -1, false ); - option.setWidth(20); - option.setPosition(x, y); - - int width = 20; int shadowOpacity = Math.min(option.value.getAlpha(), 45); - //The shape behind the preview - Color behindColor = isMouseOver(mouseX, mouseY, x + backgroundWidth - width - 17, y + 1, width + 2, 14) ? getThemeColor().darker().darker() : getThemeColor(); - DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext, - x + backgroundWidth - width - 17, + boolean isHovering = isMouseOver(mouseX, mouseY, x + option.getWidth() - width - 17, y + 1, width + 2, 14); + boolean isDown = isHovering && GLFW.glfwGetMouseButton(mc.getWindow().handle(), GLFW.GLFW_MOUSE_BUTTON_LEFT) == GLFW.GLFW_PRESS; + animator.update(isDown); + + Color behindColor = isHovering ? getThemeColor().darker().darker() : getThemeColor(); + DrawHelper.scaleAndPosition(graphics.pose(), x + option.getWidth() - width - 17, + y + 1,width + 2, 14, animator.getScale()); + + DrawHelper.drawRoundedRectangleWithShadowBadWay(graphics, + x + option.getWidth() - width - 17, y + 1, width + 2, 14, @@ -472,19 +611,17 @@ public void render(DrawContext drawContext, ColorOption option, int x, int y, in 1, 1); - //The letter above the shape behind the preview - drawContext.drawText( - mc.textRenderer, + graphics.drawString( + mc.font, option.getColorGradient().shouldDisplay() ? "^" : "v", - x + backgroundWidth - 21, + x + option.getWidth() - 21, y + 4, -1, false ); - //Preview - DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext, - x + backgroundWidth - width - 15, + DrawHelper.drawRoundedRectangleWithShadowBadWay(graphics, + x + option.getWidth() - width - 15, y + 2, width - 8, 12, @@ -494,28 +631,29 @@ public void render(DrawContext drawContext, ColorOption option, int x, int y, in 1, 1); + DrawHelper.stopScaling(graphics.pose()); + int targetHeight = (int) (option.getColorGradient().getBoxSize() + option.getColorGradient().getGradientBox().getSize() * scale); option.setHeight(option.getColorGradient().shouldDisplay() ? targetHeight : 20); - option.setWidth(width); if (option.getColorGradient().getColorPickerButton().isPicking()) { - RenderSystem.disableScissor(); //Disable scissor so the color picker preview works + DrawHelper.disableScissor(graphics); } - DrawHelper.scaleAndPosition(drawContext.getMatrices(), x + backgroundWidth / 2.0f, y, scale); - option.getColorGradient().render(drawContext, x + backgroundWidth / 2 - 50, y + 6, mouseX, mouseY); - DrawHelper.stopScaling(drawContext.getMatrices()); + DrawHelper.scaleAndPosition(graphics.pose(), x + option.getWidth() / 2.0f, y, scale); + option.getColorGradient().render(graphics, x + option.getWidth() / 2 - 50, y + 6, mouseX, mouseY); + DrawHelper.stopScaling(graphics.pose()); if (option.getColorGradient().getColorPickerButton().isPicking()) { - enableSkinScissor(); // re-enable the scissor + enableSkinScissor(graphics); } } @Override public boolean mouseClicked(ColorOption option, double mouseX, double mouseY, int button) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, option.getX() + (int) (width * 0.8f - 14) - 37, option.getY(), 22, 16)) { + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, option.getX() + option.getWidth() - 37, option.getY(), 22, 16)) { option.isVisible = !option.isVisible; if (option.isVisible) { option.getColorGradient().display(); @@ -525,93 +663,81 @@ public boolean mouseClicked(ColorOption option, double mouseX, double mouseY, in } return true; } - option.getColorGradient().mouseClicked(mouseX, mouseY, button); - return false; + return option.getColorGradient().mouseClicked(mouseX, mouseY, button); } @Override public boolean mouseDragged(ColorOption option, double mouseX, double mouseY, int button, double deltaX, double deltaY) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - option.getColorGradient().mouseDragged(mouseX, mouseY, button); - return SkinRenderer.super.mouseDragged(option, mouseX, mouseY, button, deltaX, deltaY); + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; + return option.getColorGradient().mouseDragged(mouseX, mouseY, button) && SkinRenderer.super.mouseDragged(option, mouseX, mouseY, button, deltaX, deltaY); } @Override public boolean mouseReleased(ColorOption option, double mouseX, double mouseY, int button) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - option.getColorGradient().mouseReleased(mouseX, mouseY, button); - - return SkinRenderer.super.mouseReleased(option, mouseX, mouseY, button); + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; + return option.getColorGradient().mouseReleased(mouseX, mouseY, button) && SkinRenderer.super.mouseReleased(option, mouseX, mouseY, button); } } public class ModernDoubleRenderer implements SkinRenderer { private double displayValue; private static final float ANIMATION_SPEED = 0.1f; + int sliderBackgroundWidth = 120; + int sliderBackgroundHeight = 2; @Override - public void render(DrawContext drawContext, DoubleOption option, int x, int y, int mouseX, int mouseY) { - // Draw option name - drawContext.drawText( - mc.textRenderer, + public void render(GuiGraphics graphics, DoubleOption option, int x, int y, int mouseX, int mouseY) { + graphics.drawString( + mc.font, option.name, - x + 2, - y, + x + 4, + y + 2, -1, false ); + int sliderX = x + option.getWidth() - sliderBackgroundWidth - 10; - int backgroundWidth = (int) (width * 0.8f - 14); - int sliderBackgroundWidth = 120; - int sliderBackgroundHeight = 2; - int sliderX = x + backgroundWidth - sliderBackgroundWidth - 10; - - option.setPosition(x, y); - option.setWidth(sliderBackgroundWidth); - option.setHeight(14); - - // Smoothly interpolate to the new value - displayValue = MathHelper.lerp(ANIMATION_SPEED, displayValue, option.get()); + displayValue = Mth.lerp(ANIMATION_SPEED, displayValue, option.get()); - // Background DrawHelper.drawRoundedRectangle( - drawContext, - sliderX, y, sliderBackgroundWidth, sliderBackgroundHeight, 1, DARKER_GRAY.getRGB() + graphics, + sliderX, y + 2, sliderBackgroundWidth, sliderBackgroundHeight, 1, DARKER_GRAY.getRGB() ); - // Active fill - int activeFillWidth = (int) ((displayValue - option.minValue) / (option.maxValue - option.minValue) * option.getWidth()); + int activeFillWidth = (int) ((displayValue - option.minValue) / (option.maxValue - option.minValue) * sliderBackgroundWidth); Color fillColor = isMouseOver(mouseX, mouseY, sliderX, y, sliderBackgroundWidth, sliderBackgroundHeight + 4) ? getThemeColor().darker().darker() : getThemeColor(); DrawHelper.drawRoundedRectangle( - drawContext, - sliderX, y, activeFillWidth, sliderBackgroundHeight, 2, fillColor.getRGB() + graphics, + sliderX, y + 2, activeFillWidth, sliderBackgroundHeight, 2, fillColor.getRGB() ); - // Draw slider handle float sliderHandleX = sliderX + activeFillWidth - 5; - DrawHelper.drawFilledCircle(drawContext, sliderHandleX + 5, y + 1, 2, Color.WHITE.getRGB()); - - // Draw value text - String text = String.format("%.2f", displayValue); - DrawHelper.scaleAndPosition(drawContext.getMatrices(), sliderX + 120 - mc.textRenderer.getWidth(text), y + 7, 0.6f); - drawContext.drawText( - mc.textRenderer, - text, - sliderX + sliderBackgroundWidth + 10 - mc.textRenderer.getWidth(text), - y + 2, + DrawHelper.drawFilledCircle(graphics, sliderHandleX + 5, y + 3, 2, Color.WHITE.getRGB()); + + int decimalPlaces = String.valueOf(option.step).split("\\.")[1].length(); + + // Format option.value to the determined number of decimal places + String label = String.format("%." + decimalPlaces + "f", displayValue); + DrawHelper.scaleAndPosition(graphics.pose(), sliderX + sliderBackgroundWidth - mc.font.width(label), y + 7, 0.6f); + graphics.drawString( + mc.font, + label, + sliderX + sliderBackgroundWidth + 10 - mc.font.width(label), + y + 4, -1, true ); - drawContext.getMatrices().pop(); + graphics.pose().popMatrix(); } @Override public boolean mouseClicked(DoubleOption option, double mouseX, double mouseY, int button) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, option.getX() + (int) (width * 0.8f - 14) - 125, option.getY() - 1, option.getWidth() + 2, option.getHeight() + 1)) { + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; + int sliderX = option.getX() + option.getWidth() - sliderBackgroundWidth - 10; + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, sliderX - 2, option.getY() - 1, sliderBackgroundWidth + 4, option.getHeight() + 1)) { option.setDragging(true); return true; } @@ -621,11 +747,9 @@ public boolean mouseClicked(DoubleOption option, double mouseX, double mouseY, i @Override public boolean mouseDragged(DoubleOption option, double mouseX, double mouseY, int button, double deltaX, double deltaY) { if (option.isDragging()) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - int backgroundWidth = (int) (width * 0.8f - 14); - int sliderBackgroundWidth = 120; - int sliderX = option.getX() + backgroundWidth - sliderBackgroundWidth - 10; - option.step(mouseX, sliderX); + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + int sliderX = option.getX() + option.getWidth() - sliderBackgroundWidth - 10; + option.step(mouseX, sliderX, sliderBackgroundWidth); return true; } return false; @@ -635,236 +759,166 @@ public boolean mouseDragged(DoubleOption option, double mouseX, double mouseY, i public boolean mouseReleased(DoubleOption option, double mouseX, double mouseY, int button) { if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { option.setDragging(false); - return true; } return false; } } - public class ModernEnumRenderer> implements SkinRenderer> { - @Override - public void render(DrawContext drawContext, EnumOption option, int x, int y, int mouseX, int mouseY) { - // Set dimensions for the main label and dropdown area - option.setHeight(mc.textRenderer.fontHeight + 2); - - // Draw main option name and selected option - Text mainLabel = option.name.copy().append(": "); - String selectedOption = option.get().toString(); - drawContext.drawText(mc.textRenderer, mainLabel, x + 4, y + 2, -1, false); - Color fillColor = isMouseOver(mouseX, mouseY, x + 4 + mc.textRenderer.getWidth(mainLabel), y, mc.textRenderer.getWidth(selectedOption) + 5, mc.textRenderer.fontHeight + 2) ? getThemeColor().darker().darker() : getThemeColor(); - DrawHelper.drawRoundedRectangle( - drawContext, - x + 4 + mc.textRenderer.getWidth(mainLabel), y, mc.textRenderer.getWidth(selectedOption) + 5, mc.textRenderer.fontHeight + 2, 2, - fillColor.getRGB() - ); - // "<" and ">" buttons - int contextMenuWidth = (int) (width * 0.8f - 14); - int leftX = x + contextMenuWidth - 30; - boolean hoveredOverLeft = isMouseOver(mouseX, mouseY, leftX, y, mc.textRenderer.getWidth("<") + 5, mc.textRenderer.fontHeight); - boolean hoveredOverRight = isMouseOver(mouseX, mouseY, leftX + mc.textRenderer.getWidth("<") + 6, y, mc.textRenderer.getWidth(">") + 5, mc.textRenderer.fontHeight); - // Shadow - DrawHelper.drawRoundedRectangle( - drawContext, - leftX + 1, y + 3, - (mc.textRenderer.getWidth("<") * 2) + 10, mc.textRenderer.fontHeight, 2, - ColorHelper.changeAlpha(Color.BLACK, 128).getRGB() - ); - DrawHelper.drawRoundedRectangle( - drawContext, - leftX, y + 2, - true, false, true, false, - mc.textRenderer.getWidth("<") + 5, mc.textRenderer.fontHeight, 2, - hoveredOverLeft ? getThemeColor().darker().darker().getRGB() : getThemeColor().getRGB() - ); - DrawHelper.drawRoundedRectangle( - drawContext, - leftX + mc.textRenderer.getWidth("<") + 6, y + 2, - false, true, false, true, - mc.textRenderer.getWidth(">") + 5, mc.textRenderer.fontHeight, 2, - hoveredOverRight ? getThemeColor().darker().darker().getRGB() : getThemeColor().getRGB() - ); - DrawHelper.drawVerticalLine( - drawContext, - leftX + mc.textRenderer.getWidth("<") + 5, - y + 2, - mc.textRenderer.fontHeight, - 0.7f, - Color.WHITE.getRGB() - ); - drawContext.drawText(mc.textRenderer, "<", leftX + mc.textRenderer.getWidth("<") / 2 + 1, y + 3, -1, false); - drawContext.drawText(mc.textRenderer, ">", leftX + mc.textRenderer.getWidth("<") + 7 + mc.textRenderer.getWidth(">") / 2, y + 3, -1, false); - - drawContext.drawText(mc.textRenderer, selectedOption, x + 6 + mc.textRenderer.getWidth(mainLabel), y + 2, Color.LIGHT_GRAY.getRGB(), false); - } + public class ModernCycleRenderer implements SkinRenderer> { + private final SquishAnimator textAnim = new SquishAnimator(); + private final SquishAnimator leftAnim = new SquishAnimator(1.0f,0.9f); + private final SquishAnimator rightAnim = new SquishAnimator(1.0f,0.9f); @Override - public boolean mouseClicked(EnumOption option, double mouseX, double mouseY, int button) { - if (option.getValues().length == 0) return false; + public void render(GuiGraphics graphics, CycleOption option, int x, int y, int mouseX, int mouseY) { + y += 2; + option.setHeight(mc.font.lineHeight + 2); - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; + Component mainLabel = option.name.copy().append(": "); + graphics.drawString(mc.font, mainLabel, x + 4, y + 2, -1, false); - int x = option.getX(); - int y = option.getY(); - String mainLabel = option.name + ": "; String selectedOption = option.get().toString(); - // Check if the main label is clicked to cycle - // "<" and ">" buttons - int contextMenuWidth = (int) (width * 0.8f - 14); - int leftX = x + contextMenuWidth - 30; - boolean hoveredOverLeft = isMouseOver(mouseX, mouseY, leftX, y, mc.textRenderer.getWidth("<") + 5, mc.textRenderer.fontHeight); - boolean hoveredOverRight = isMouseOver(mouseX, mouseY, leftX + mc.textRenderer.getWidth("<") + 6, y, mc.textRenderer.getWidth(">") + 5, mc.textRenderer.fontHeight); - boolean hoveredOverMainLabel = isMouseOver(mouseX, mouseY, x + 4 + mc.textRenderer.getWidth(mainLabel), y, mc.textRenderer.getWidth(selectedOption) + 5, mc.textRenderer.fontHeight + 2); + int leftX = x + option.getWidth() - 30; - if (hoveredOverLeft || hoveredOverRight || hoveredOverMainLabel) { - E[] values = option.getValues(); - int index = Arrays.asList(values).indexOf(option.value); - - if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT || hoveredOverLeft) { - // Cycle forward - E nextVal = values[(index + 1) % values.length]; - option.set(nextVal); - } else if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT || hoveredOverRight) { - // Cycle backward with wrap-around - E nextVal = values[(index - 1 + values.length) % values.length]; - option.set(nextVal); - } - return true; - } - - return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); - } + int mainLabelWidth = mc.font.width(mainLabel); + int selectedOptionWidth = mc.font.width(selectedOption); + int leftWidth = mc.font.width("<"); + int rightWidth = mc.font.width(">"); - } + boolean hoveredOverText = isMouseOver(mouseX, mouseY, x + 4 + mainLabelWidth, y, selectedOptionWidth + 5, mc.font.lineHeight + 2); + boolean hoveredOverLeft = isMouseOver(mouseX, mouseY, leftX, y, leftWidth + 5, mc.font.lineHeight); + boolean hoveredOverRight = isMouseOver(mouseX, mouseY, leftX + leftWidth + 6, y, rightWidth + 5, mc.font.lineHeight); - public class ModernListRenderer implements SkinRenderer> { + boolean isPressed = GLFW.glfwGetMouseButton(mc.getWindow().handle(), GLFW.GLFW_MOUSE_BUTTON_LEFT) == GLFW.GLFW_PRESS || GLFW.glfwGetMouseButton(mc.getWindow().handle(), GLFW.GLFW_MOUSE_BUTTON_RIGHT) == GLFW.GLFW_PRESS; + boolean isClickingOnText = hoveredOverText && isPressed; + boolean isClickingOnLeft = hoveredOverLeft && isPressed; + boolean isClickingOnRight = hoveredOverRight && isPressed; - @Override - public void render(DrawContext drawContext, ListOption option, int x, int y, int mouseX, int mouseY) { + textAnim.update(isClickingOnText); + leftAnim.update(isClickingOnLeft); + rightAnim.update(isClickingOnRight); - // Set dimensions for the main label and dropdown area - option.setHeight(mc.textRenderer.fontHeight + 2); + DrawHelper.scaleAndPosition(graphics.pose(),x + 4 + mainLabelWidth, y, selectedOptionWidth + 5, mc.font.lineHeight + 2, textAnim.getScale()); - // Draw main option name and selected option - Text mainLabel = option.name.copy().append(": "); - String selectedOption = option.get().toString(); - drawContext.drawText(mc.textRenderer, mainLabel, x + 4, y + 2, -1, false); - Color fillColor = isMouseOver(mouseX, mouseY, x + 4 + mc.textRenderer.getWidth(mainLabel), y, mc.textRenderer.getWidth(selectedOption) + 5, mc.textRenderer.fontHeight + 2) ? getThemeColor().darker().darker() : getThemeColor(); + Color fillColor = hoveredOverText ? getThemeColor().darker().darker() : getThemeColor(); DrawHelper.drawRoundedRectangle( - drawContext, - x + 4 + mc.textRenderer.getWidth(mainLabel), y, mc.textRenderer.getWidth(selectedOption) + 5, mc.textRenderer.fontHeight + 2, 2, + graphics, + x + 4 + mainLabelWidth, y, selectedOptionWidth + 5, mc.font.lineHeight + 2, 2, fillColor.getRGB() ); + graphics.drawString(mc.font, selectedOption, x + 6 + mainLabelWidth, y + 2, Color.WHITE.getRGB(), hoveredOverText); - // "<" and ">" buttons - int contextMenuWidth = (int) (width * 0.8f - 14); - int leftX = x + contextMenuWidth - 30; - boolean hoveredOverLeft = isMouseOver(mouseX, mouseY, leftX, y, mc.textRenderer.getWidth("<") + 5, mc.textRenderer.fontHeight); - boolean hoveredOverRight = isMouseOver(mouseX, mouseY, leftX + mc.textRenderer.getWidth("<") + 6, y, mc.textRenderer.getWidth(">") + 5, mc.textRenderer.fontHeight); - // Shadow + DrawHelper.stopScaling(graphics.pose()); + + //Shadow DrawHelper.drawRoundedRectangle( - drawContext, + graphics, leftX + 1, y + 3, - (mc.textRenderer.getWidth("<") * 2) + 10, mc.textRenderer.fontHeight, 2, + (leftWidth * 2) + 10, mc.font.lineHeight, 2, ColorHelper.changeAlpha(Color.BLACK, 128).getRGB() ); + + DrawHelper.scaleAndPosition(graphics.pose(),leftX, y + 2, leftWidth + 5, mc.font.lineHeight, leftAnim.getScale()); + DrawHelper.drawRoundedRectangle( - drawContext, + graphics, leftX, y + 2, true, false, true, false, - mc.textRenderer.getWidth("<") + 5, mc.textRenderer.fontHeight, 2, + leftWidth + 5, mc.font.lineHeight, 2, hoveredOverLeft ? getThemeColor().darker().darker().getRGB() : getThemeColor().getRGB() ); + graphics.drawString(mc.font, "<", leftX + leftWidth / 2 + 1, y + 3, -1, false); + + DrawHelper.stopScaling(graphics.pose()); + + DrawHelper.scaleAndPosition(graphics.pose(),leftX + leftWidth + 6, y + 2, rightWidth + 5, mc.font.lineHeight, rightAnim.getScale()); + DrawHelper.drawRoundedRectangle( - drawContext, - leftX + mc.textRenderer.getWidth("<") + 6, y + 2, + graphics, + leftX + leftWidth + 6, y + 2, false, true, false, true, - mc.textRenderer.getWidth(">") + 5, mc.textRenderer.fontHeight, 2, + rightWidth + 5, mc.font.lineHeight, 2, hoveredOverRight ? getThemeColor().darker().darker().getRGB() : getThemeColor().getRGB() ); + graphics.drawString(mc.font, ">", leftX + leftWidth + 7 + rightWidth / 2, y + 3, -1, false); + DrawHelper.stopScaling(graphics.pose()); + + //todo: unsure whether to keep this or not? it removes the 3D illusion + /* DrawHelper.drawVerticalLine( - drawContext, - leftX + mc.textRenderer.getWidth("<") + 5, + graphics, + leftX + leftWidth + 5, y + 2, - mc.textRenderer.fontHeight, - 0.7f, + mc.font.lineHeight, + 1f, Color.WHITE.getRGB() ); - drawContext.drawText(mc.textRenderer, "<", leftX + mc.textRenderer.getWidth("<") / 2 + 1, y + 3, -1, false); - drawContext.drawText(mc.textRenderer, ">", leftX + mc.textRenderer.getWidth("<") + 7 + mc.textRenderer.getWidth(">") / 2, y + 3, -1, false); - drawContext.drawText(mc.textRenderer, selectedOption, x + 6 + mc.textRenderer.getWidth(mainLabel), y + 2, Color.LIGHT_GRAY.getRGB(), false); + */ } @Override - public boolean mouseClicked(ListOption option, double mouseX, double mouseY, int button) { + public boolean mouseClicked(CycleOption option, double mouseX, double mouseY, int button) { if (option.getValues().isEmpty()) return false; - mouseX /= SCALE_FACTOR; - mouseY /= SCALE_FACTOR; + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; int x = option.getX(); int y = option.getY(); - Text mainLabel = option.name.copy().append(": "); + Component mainLabel = option.name.copy().append(": "); String selectedOption = option.get().toString(); + y += 2; - // Calculate positions - int contextMenuWidth = (int) (width * 0.8f - 14); - int leftX = x + contextMenuWidth - 30; - - // Check hover states - boolean hoveredOverLeft = isMouseOver(mouseX, mouseY, leftX, y, mc.textRenderer.getWidth("<") + 5, mc.textRenderer.fontHeight); - boolean hoveredOverRight = isMouseOver(mouseX, mouseY, leftX + mc.textRenderer.getWidth("<") + 6, y, mc.textRenderer.getWidth(">") + 5, mc.textRenderer.fontHeight); - boolean hoveredOverMainLabel = isMouseOver(mouseX, mouseY, x + 4 + mc.textRenderer.getWidth(mainLabel), y, mc.textRenderer.getWidth(selectedOption) + 5, mc.textRenderer.fontHeight + 2); + int leftX = x + option.getWidth() - 30; + boolean hoveredOverLeft = isMouseOver(mouseX, mouseY, leftX, y, mc.font.width("<") + 5, mc.font.lineHeight); + boolean hoveredOverRight = isMouseOver(mouseX, mouseY, leftX + mc.font.width("<") + 6, y, mc.font.width(">") + 5, mc.font.lineHeight); + boolean hoveredOverMainLabel = isMouseOver(mouseX, mouseY, x + 4 + mc.font.width(mainLabel), y, mc.font.width(selectedOption) + 5, mc.font.lineHeight + 2); - // Check if any area is clicked if (hoveredOverLeft || hoveredOverRight || hoveredOverMainLabel) { List values = option.getValues(); int currentIndex = values.indexOf(option.value); int nextIndex; - // Determine the next index based on the button clicked if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT || hoveredOverLeft) { - nextIndex = (currentIndex + 1) % values.size(); // Cycle forward + nextIndex = (currentIndex + 1) % values.size(); } else if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT || hoveredOverRight) { - nextIndex = (currentIndex - 1 + values.size()) % values.size(); // Cycle backward + nextIndex = (currentIndex - 1 + values.size()) % values.size(); } else { - return false; // No valid click + return false; } option.set(values.get(nextIndex)); return true; } - return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); + return false; } } public class ModernSubMenuRenderer implements SkinRenderer { - @Override - public void render(DrawContext drawContext, SubMenuOption option, int x, int y, int mouseX, int mouseY) { - mouseX = (int) (mc.mouse.getX() / SCALE_FACTOR); - mouseY = (int) (mc.mouse.getY() / SCALE_FACTOR); - - String text = "Open"; - int contextMenuWidth = (int) (width * 0.8f - 14); - int xPos = x + 4 + contextMenuWidth - 40; + private final SquishAnimator animator = new SquishAnimator(); - option.setPosition(xPos - 1, y); - option.setWidth(mc.textRenderer.getWidth(text) + 5); - option.setHeight(16); + @Override + public void render(GuiGraphics graphics, SubMenuOption option, int x, int y, int mouseX, int mouseY) { + String textLabel = "Open"; + int xPos = x + option.getWidth() - 40; + float width = mc.font.width(textLabel) + 5; - drawContext.drawText(mc.textRenderer, option.name, x + 4, y + 4, -1, false); + boolean isHovering = isMouseOver(mouseX, mouseY, xPos + 2, y + 4, width, mc.font.lineHeight + 4); + boolean isDown = isHovering && GLFW.glfwGetMouseButton(mc.getWindow().handle(), GLFW.GLFW_MOUSE_BUTTON_LEFT) == GLFW.GLFW_PRESS; + animator.update(isDown); - drawContext.drawText(mc.textRenderer, text, xPos + 2, y + 4, Color.WHITE.getRGB(), true); + graphics.drawString(mc.font, option.name, x + 4, y + 4, -1, false); - Color fillColor = isMouseOver(mouseX, mouseY, xPos + 2, y + 4, mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4) ? getThemeColor().darker().darker() : getThemeColor(); + Color fillColor = isHovering ? getThemeColor().darker().darker() : getThemeColor(); + DrawHelper.scaleAndPosition(graphics.pose(),x,y,width, mc.font.lineHeight + 4, animator.getScale()); DrawHelper.drawRoundedRectangleWithShadowBadWay( - drawContext, - xPos - 1, y + 1, - mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4, + graphics, + xPos - 1, y, + mc.font.width(textLabel) + 5, mc.font.lineHeight + 4, 2, fillColor.getRGB(), 180, @@ -872,87 +926,241 @@ public void render(DrawContext drawContext, SubMenuOption option, int x, int y, 1 ); DrawHelper.drawOutlineRoundedBox( - drawContext, - xPos - 1, y + 1, - mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4, + graphics, + xPos - 1, y, + mc.font.width(textLabel) + 5, mc.font.lineHeight + 4, 2, 0.7f, Color.WHITE.getRGB() ); + graphics.drawString(mc.font, textLabel, xPos + 2, y + 3, Color.WHITE.getRGB(), true); - option.getSubMenu().render(drawContext, x + option.getParentMenu().getWidth(), y, mouseX, mouseY); - } - - @Override - public void mouseScrolled(SubMenuOption option, double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - SkinRenderer.super.mouseScrolled(option, mouseX, mouseY, horizontalAmount, verticalAmount); - } + DrawHelper.stopScaling(graphics.pose()); - @Override - public boolean mouseDragged(SubMenuOption option, double mouseX, double mouseY, int button, double deltaX, double deltaY) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - return SkinRenderer.super.mouseDragged(option, mouseX, mouseY, button, deltaX, deltaY); + option.getSubMenu().render(graphics, x + option.getParentMenu().getWidth(), y, mouseX, mouseY); } @Override public boolean mouseReleased(SubMenuOption option, double mouseX, double mouseY, int button) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - return SkinRenderer.super.mouseReleased(option, mouseX, mouseY, button); + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; + return isMouseOver(mouseX, mouseY, option.getX() + option.getWidth() - 40 + 2, option.getY() + 4, mc.font.width("Open") + 5, mc.font.lineHeight + 4); } @Override public boolean mouseClicked(SubMenuOption option, double mouseX, double mouseY, int button) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; + if(isMouseOver(mouseX, mouseY, option.getX() + option.getWidth() - 40 + 2, option.getY() + 4, mc.font.width("Open") + 5, mc.font.lineHeight + 4)){ + option.toggle(); + return true; + } + return false; } } public class ModernRunnableRenderer implements SkinRenderer { - Color DARK_RED = new Color(116, 0, 0); - Color DARK_GREEN = new Color(24, 132, 0, 226); + private final SquishAnimator animator = new SquishAnimator(); @Override - public void render(DrawContext drawContext, RunnableOption option, int x, int y, int mouseX, int mouseY) { - String text = "Run ▶"; - int contextMenuWidth = (int) (width * 0.8f - 14); - int xPos = x + 4 + contextMenuWidth - 45; + public void render(GuiGraphics graphics, RunnableOption option, int x, int y, int mouseX, int mouseY) { + String labelText = "Run ▶"; + float labelWidth = mc.font.width(labelText) + 5; - option.setPosition(xPos - 1, y); - option.setWidth(mc.textRenderer.getWidth(text) + 5); - option.setHeight(mc.textRenderer.fontHeight + 6); + int xPos = x + option.getWidth() - 45; - drawContext.drawText(mc.textRenderer, option.name, x + 4, y + 4, -1, false); + option.setHeight(mc.font.lineHeight + 6); - drawContext.drawText(mc.textRenderer, text, xPos + 2, y + 4, option.value ? DARK_GREEN.getRGB() : DARK_RED.getRGB(), true); + boolean isHovering = isMouseOver(mouseX, mouseY, xPos + 2, y + 4, labelWidth, mc.font.lineHeight + 4); + boolean isDown = isHovering && GLFW.glfwGetMouseButton(mc.getWindow().handle(), GLFW.GLFW_MOUSE_BUTTON_LEFT) == GLFW.GLFW_PRESS; + animator.update(isDown); - Color fillColor = isMouseOver(mouseX, mouseY, xPos + 2, y + 4, mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4) ? getThemeColor().darker().darker() : getThemeColor(); + graphics.drawString(mc.font, option.name, x + 4, y + 4, -1, false); + + Color fillColor = isHovering ? getThemeColor().darker().darker() : getThemeColor(); + + DrawHelper.scaleAndPosition(graphics.pose(),xPos - 1, y + 1,labelWidth, mc.font.lineHeight + 4, animator.getScale()); DrawHelper.drawRoundedRectangleWithShadowBadWay( - drawContext, + graphics, xPos - 1, y + 1, - mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4, + labelWidth, mc.font.lineHeight + 4, 2, fillColor.getRGB(), 180, 1, 1 ); + + graphics.drawString(mc.font, labelText, xPos + 2, y + 4, option.value ? DARK_GREEN.getRGB() : DARK_RED.getRGB(), true); + DrawHelper.stopScaling(graphics.pose()); } @Override public boolean mouseClicked(RunnableOption option, double mouseX, double mouseY, int button) { - mouseX = mc.mouse.getX() / SCALE_FACTOR; - mouseY = mc.mouse.getY() / SCALE_FACTOR; - return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); + mouseX = mc.mouseHandler.xpos() / SCALE_FACTOR; + mouseY = mc.mouseHandler.ypos() / SCALE_FACTOR; + if(isMouseOver(mouseX, mouseY, option.getX() + option.getWidth() - 45 + 2, option.getY() + 4, mc.font.width("Run ▶") + 5, mc.font.lineHeight + 4)){ + option.toggle(); + return true; + } + return false; + } + + @Override + public boolean mouseReleased(RunnableOption option, double mouseX, double mouseY, int button) { + return SkinRenderer.super.mouseReleased(option, mouseX, mouseY, button); } } + @Override public Skin clone() { - return new ModernSkin(themeColor,radius,defaultToolTipHeader,defaultToolTipText); + return new ModernSkin(radius, defaultToolTipHeader, defaultToolTipText); + } + + + private static class GroupAnimData { + float value; + ValueAnimation animation; + + GroupAnimData(float initial) { + this.value = initial; + } + } + + public class ModernSearchBox extends EditBox { + private static final int CORNER_RADIUS = 6; + private static final int ICON_PADDING = 6; + private static final int CLEAR_BUTTON_SIZE = 16; + private static final int TEXT_PADDING = 4; + + private final SquishAnimator clearAnimator = new SquishAnimator(); + private int totalX, totalY, totalWidth, totalHeight; + + public ModernSearchBox(int x, int y, int width, int height) { + super(mc.font, x, y, width, height, Component.empty()); + this.setBordered(false); + this.setCentered(false); + this.setTextShadow(true); + this.setEditable(true); + this.setVisible(true); + this.setMaxLength(50); + this.active = true; + this.setCanLoseFocus(true); + this.setHint(Component.literal("Search...")); + setTotalBounds(x, y, width, height); + } + + public void setTotalBounds(int x, int y, int width, int height) { + this.totalX = x; + this.totalY = y; + this.totalWidth = width; + this.totalHeight = height; + + int iconWidth = mc.font.width("\uD83D\uDD0D"); + int textX = totalX + iconWidth + ICON_PADDING + TEXT_PADDING; + int textWidth = totalWidth - (textX - totalX) - (CLEAR_BUTTON_SIZE + ICON_PADDING + TEXT_PADDING); + if (textWidth < 10) textWidth = 10; + + this.setX(textX); + this.setY(totalY); + this.setWidth(textWidth); + this.setHeight(totalHeight); + } + + @Override + public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (!this.visible) return; + + int x = totalX; + int y = totalY; + int w = totalWidth; + int h = totalHeight; + + // Background + Color bgColor = isFocused() ? new Color(25, 25, 25, 240) : DARKER_GRAY; + DrawHelper.drawRoundedRectangleWithShadowBadWay(graphics, x, y, w, h, CORNER_RADIUS, bgColor.getRGB(),60,1,2); + + if (isFocused()) { + float pulse = MathAnimations.pulse2(2.5f, 0.3f, 1.0f); // 2.5 oscillations/sec + int glowAlpha = (int) (pulse * 200 + 55); // alpha range 55–255 + int glowColor = ColorHelper.changeAlpha(getThemeColor(), glowAlpha).getRGB(); + DrawHelper.drawOutlineRoundedBox(graphics, x, y, w, h, CORNER_RADIUS, 1f, glowColor); + } else { + DrawHelper.drawOutlineRoundedBox(graphics, x, y, w, h, CORNER_RADIUS, 1f, DARK_GRAY.getRGB()); + } + + + String icon = "\uD83D\uDD0D"; + int iconX = x + ICON_PADDING; + int iconY = y + (h - mc.font.lineHeight) / 2 + 1; + int iconColor = isFocused()? Color.WHITE.getRGB() : 0x80FFFFFF; + graphics.drawString(mc.font, icon, iconX, iconY, iconColor, false); + + + //Rendering the original edit-box widget without its border and background, and translating it to fit inside our search box + // EditBox handles the rest of the text and highlight rendering + graphics.pose().pushMatrix(); + graphics.pose().translate(-1,3); + super.renderWidget(graphics, mouseX, mouseY, delta); + graphics.pose().popMatrix(); + + String clearText = "✕"; + int clearWidth = mc.font.width(clearText); + int clearX = x + w - CLEAR_BUTTON_SIZE; + int clearY = y + (h - mc.font.lineHeight) / 2; + boolean isClearHovered = Skin.isMouseOver(mouseX, mouseY, clearX - 2, clearY - 1, clearWidth + 4, mc.font.lineHeight + 2); + clearAnimator.update(isClearHovered && (mouseX != -1 && mouseY != -1)); + + if (!getValue().isEmpty()) { + float scale = clearAnimator.getScale(); + int clearBgColor = isClearHovered ? Color.RED.darker().getRGB() : ColorHelper.changeAlpha(Color.WHITE, 30).getRGB(); + int bgW = clearWidth + 4; + int bgH = mc.font.lineHeight; + DrawHelper.scaleAndPosition(graphics.pose(), clearX - 0.25f, clearY + 0.5f, bgW, bgH, scale); + DrawHelper.drawRoundedRectangle(graphics, clearX - 0.25f, clearY + 0.5f, bgW, bgH, 4, clearBgColor); + graphics.drawString(mc.font, clearText, clearX + 2, clearY + 1, + isClearHovered ? 0xFFFFFFFF : 0xB0FFFFFF, false); + DrawHelper.stopScaling(graphics.pose()); + } + if (isHovered()) { + graphics.requestCursor(CursorTypes.IBEAM); + } + } + @Override + public boolean mouseClicked(@NonNull MouseButtonEvent event, boolean bl) { + if (!this.isActive()) { + return false; + } + if (!getValue().isEmpty()) { + int clearX = totalX + totalWidth - CLEAR_BUTTON_SIZE - 2; + int clearY = totalY + (totalHeight - mc.font.lineHeight) / 2 - 1; + int clearW = mc.font.width("✕") + 4; + int clearH = mc.font.lineHeight + 2; + if (Skin.isMouseOver(event.x(), event.y(), clearX, clearY, clearW, clearH)) { + setValue(""); + return true; + } + } + + if (this.isValidClickButton(event.buttonInfo()) && Skin.isMouseOver(event.x(), event.y(), totalX, totalY, totalWidth, totalHeight)) { + this.playDownSound(Minecraft.getInstance().getSoundManager()); + this.onClick(event, bl); + setFocused(true); + return true; + } else { + setFocused(false); + } + + return false; + } + + public int getTotalWidth() { + return totalWidth; + } + + private Color getThemeColor() { + return ModernSkin.this.getThemeColor(); + } } } \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/Skin.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/Skin.java index 5c8e86b..e6b19a3 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/Skin.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/Skin.java @@ -5,8 +5,8 @@ import com.tanishisherewith.dynamichud.utils.contextmenu.options.OptionGroup; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.GroupableSkin; import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces.SkinRenderer; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; import java.util.ArrayList; import java.util.HashMap; @@ -15,7 +15,7 @@ import java.util.function.Supplier; public abstract class Skin { - protected static final MinecraftClient mc = MinecraftClient.getInstance(); + protected static final Minecraft mc = Minecraft.getInstance(); protected ContextMenu contextMenu; protected Map>, Supplier>>> renderers = new HashMap<>(); private boolean createNewScreen; @@ -54,7 +54,7 @@ public boolean supportsGroups() { * Flatten a list of options, expanding any groups into their constituent options. * Used by skins that don't support group rendering. */ - protected List> flattenOptions(List> options) { + public static List> flattenOptions(List> options) { List> flattened = new ArrayList<>(); for (Option option : options) { @@ -70,7 +70,7 @@ protected List> flattenOptions(List> options) { return flattened; } - protected List> getOptions(ContextMenu menu) { + public List> getOptions(ContextMenu menu) { return supportsGroups() ? menu.getOptions() : flattenOptions(menu.getOptions()); } @@ -82,7 +82,7 @@ public void setRenderers(Map>, Supplier contextMenu, int mouseX, int mouseY); + public abstract void renderContextMenu(GuiGraphics graphics, ContextMenu contextMenu, int mouseX, int mouseY); public boolean mouseClicked(ContextMenu menu, double mouseX, double mouseY, int button) { return false; @@ -102,6 +102,9 @@ public void keyPressed(ContextMenu menu, int key, int scanCode, int modifiers public void keyReleased(ContextMenu menu, int key, int scanCode, int modifiers) { } + public void charTyped(ContextMenu menu, char c, int modifiers) { + } + public void mouseScrolled(ContextMenu menu, double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { } @@ -113,7 +116,7 @@ public void setCreateNewScreen(boolean createNewScreen) { this.createNewScreen = createNewScreen; } - protected boolean isMouseOver(double mouseX, double mouseY, double x, double y, double width, double height) { + public static boolean isMouseOver(double mouseX, double mouseY, double x, double y, double width, double height) { return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/GroupableSkin.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/GroupableSkin.java index c93dd52..6a7486c 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/GroupableSkin.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/GroupableSkin.java @@ -1,12 +1,12 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces; -import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutContext; +import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutEngine; import com.tanishisherewith.dynamichud.utils.contextmenu.options.OptionGroup; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.GuiGraphics; public interface GroupableSkin { - LayoutContext.Offset getGroupIndent(); + LayoutEngine.Offset getGroupIndent(); - void renderGroup(DrawContext drawContext, OptionGroup group, int groupX, int groupY, int mouseX, int mouseY); + void renderGroup(GuiGraphics graphics, OptionGroup group, int groupX, int groupY, int mouseX, int mouseY); } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/SkinRenderer.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/SkinRenderer.java index 89fa741..671c9b1 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/SkinRenderer.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/SkinRenderer.java @@ -1,10 +1,10 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces; import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.GuiGraphics; public interface SkinRenderer> { - void render(DrawContext drawContext, T option, int x, int y, int mouseX, int mouseY); + void render(GuiGraphics graphics, T option, int x, int y, int mouseX, int mouseY); default boolean mouseClicked(T option, double mouseX, double mouseY, int button) { return option.mouseClicked(mouseX, mouseY, button); diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/handlers/ScrollHandler.java b/src/main/java/com/tanishisherewith/dynamichud/utils/handlers/ScrollHandler.java index 2bd9d98..b40997c 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/handlers/ScrollHandler.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/handlers/ScrollHandler.java @@ -1,80 +1,95 @@ package com.tanishisherewith.dynamichud.utils.handlers; -import net.minecraft.util.math.MathHelper; +import net.minecraft.util.Mth; public class ScrollHandler { - protected int scrollOffset; - protected double scrollVelocity; - protected long lastScrollTime; + protected double scrollOffset; + protected double targetScrollOffset; protected boolean isDragging; protected int maxScrollOffset; - protected double SCROLL_SPEED = 1; + protected double SCROLL_SPEED = 1.0; + protected int trackHeight = 150; protected double lastMouseY; public ScrollHandler() { - this.scrollOffset = 0; - this.scrollVelocity = 0; - this.lastScrollTime = 0; this.isDragging = false; } - public void updateScrollOffset(int maxYOffset) { - if (maxYOffset < 0) maxYOffset = 0; + public void setScrollOffset(int offset) { + this.displayValuePosition(offset); + } + + public void setScrollOffsetDirectly(double offset) { + this.displayValue = offset; + } + + public int getScrollOffset() { + return Math.toIntExact(Math.round(scrollOffset)); + } + + private double displayValue; + private static final float LERP_SPEED = 0.22f; - this.maxScrollOffset = maxYOffset; - applyMomentum(); - scrollOffset = MathHelper.clamp(scrollOffset, 0, maxScrollOffset); + public void updateScrollOffset(int maxScrollOffset) { + if(maxScrollOffset <= 0) return; + this.maxScrollOffset = maxScrollOffset; + displayValue = Mth.lerp(LERP_SPEED, displayValue, targetScrollOffset); + displayValue = Mth.clamp(displayValue, 0.0, maxScrollOffset); + this.scrollOffset = Math.round(displayValue); } public void mouseScrolled(double deltaY) { - scrollVelocity -= deltaY * 10; - lastScrollTime = System.currentTimeMillis(); + double amount = -deltaY * 18.0 * SCROLL_SPEED; + this.targetScrollOffset = Mth.clamp(targetScrollOffset + amount, 0.0, maxScrollOffset); } public void startDragging(double mouseY) { - isDragging = true; - lastMouseY = mouseY; + this.isDragging = true; + this.lastMouseY = mouseY; } public void stopDragging() { - isDragging = false; + this.isDragging = false; } public void addOffset(int offset) { - this.scrollOffset = MathHelper.clamp(scrollOffset + offset, 0, maxScrollOffset); + this.targetScrollOffset = Mth.clamp(targetScrollOffset + offset, 0.0, maxScrollOffset); } public void updateScrollPosition(double mouseY) { - if (isDragging) { - // Calculate the difference in mouse Y position - double deltaY = lastMouseY - mouseY; - - // Update the scroll offset based on the mouse movement - scrollOffset = MathHelper.clamp(scrollOffset - (int) (deltaY * SCROLL_SPEED), 0, maxScrollOffset); - - // Update the last mouse position + if (isDragging && maxScrollOffset > 0) { + double deltaY = mouseY - lastMouseY; + double scrollRatio = (double) maxScrollOffset / Math.max(1, trackHeight); + double offsetDelta = deltaY * scrollRatio; + this.targetScrollOffset = Mth.clamp(targetScrollOffset + offsetDelta, 0.0, maxScrollOffset); lastMouseY = mouseY; } } - private void applyMomentum() { - long currentTime = System.currentTimeMillis(); - double timeDelta = (currentTime - lastScrollTime) / 1000.0; - scrollOffset += (int) (scrollVelocity * timeDelta); - scrollVelocity *= 0.9; // Decay factor - scrollOffset = MathHelper.clamp(scrollOffset, 0, maxScrollOffset); - } - - public int getScrollOffset() { - return Math.max(scrollOffset, 0); + private void displayValuePosition(double val) { + this.targetScrollOffset = Mth.clamp(val, 0.0, maxScrollOffset); + this.displayValue = this.targetScrollOffset; + this.scrollOffset = this.targetScrollOffset; } public boolean isOffsetWithinBounds(int offset) { - return scrollOffset + offset >= 0 && scrollOffset + offset <= maxScrollOffset; + return targetScrollOffset + offset >= 0 && targetScrollOffset + offset <= maxScrollOffset; } public ScrollHandler setScrollSpeed(double scrollSpeed) { this.SCROLL_SPEED = scrollSpeed; return this; } + + public void setTrackHeight(int trackHeight) { + this.trackHeight = trackHeight; + } + + public int getTrackHeight() { + return trackHeight; + } + + public boolean isDragging() { + return isDragging; + } } \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/DynamicValueWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widget/DynamicValueWidget.java index 1e8630f..a8ea445 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/DynamicValueWidget.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/DynamicValueWidget.java @@ -4,7 +4,7 @@ import com.tanishisherewith.dynamichud.utils.Util; import com.tanishisherewith.dynamichud.widgets.GraphWidget; import com.tanishisherewith.dynamichud.widgets.TextWidget; -import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.CompoundTag; import java.util.function.Supplier; @@ -19,7 +19,7 @@ public abstract class DynamicValueWidget extends Widget { protected Supplier valueSupplier; public DynamicValueWidget(WidgetData data, String modID, String registryID, String registryKey) { - this(data, modID, Anchor.CENTER, registryID, registryKey); + this(data, modID, Anchor._default(), registryID, registryKey); } public DynamicValueWidget(WidgetData data, String modId, Anchor anchor, String registryID, String registryKey) { @@ -36,21 +36,21 @@ protected void initializeValueSupplier() { } @Override - public void writeToTag(NbtCompound tag) { + public void writeToTag(CompoundTag tag) { super.writeToTag(tag); tag.putString("RegistryID", registryID); tag.putString("RegistryKey", registryKey); } @Override - public void readFromTag(NbtCompound tag) { + public void readFromTag(CompoundTag tag) { super.readFromTag(tag); registryID = tag.getString("RegistryID").orElse(DynamicValueRegistry.GLOBAL_ID); registryKey = tag.getString("RegistryKey").orElse("null"); initializeValueSupplier(); - if (valueSupplier == null) throw new IllegalStateException("Value supplier remains null"); + if (valueSupplier == null) throw new IllegalStateException("Value supplier cannot be null. Invalid registry data found!"); } /** diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/UserManageable.java b/src/main/java/com/tanishisherewith/dynamichud/widget/UserManageable.java deleted file mode 100644 index b719d15..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/UserManageable.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.tanishisherewith.dynamichud.widget; - -//unused -public interface UserManageable { - // Marker interface that a widget is add-able and removable. -} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java b/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java index a385042..e25fb28 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java @@ -1,18 +1,28 @@ package com.tanishisherewith.dynamichud.widget; +import com.tanishisherewith.dynamichud.DynamicHUD; import com.tanishisherewith.dynamichud.config.GlobalConfig; import com.tanishisherewith.dynamichud.helpers.DrawHelper; import com.tanishisherewith.dynamichud.internal.UID; import com.tanishisherewith.dynamichud.utils.Input; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; +import com.tanishisherewith.dynamichud.widgets.GraphWidget; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; import org.lwjgl.glfw.GLFW; +import java.awt.*; + +/** + * This is the base Widget class that handles the rendering, scaling, dragging, anchoring and positioning of the Widget. + *

+ * Default fields are made to help with all the basic functions of a widget. + * Main fields include: {@link #uid},{@link #isVisible},{@link #isDraggable},{@link #canScale},{@link #isInEditor},{@link #widgetBox},{@link #DATA} + */ public abstract class Widget implements Input { - public static MinecraftClient mc = MinecraftClient.getInstance(); + public static Minecraft mc = Minecraft.getInstance(); public WidgetData DATA; /** * This is the UID of the widget used to identify during loading and saving. @@ -27,6 +37,8 @@ public abstract class Widget implements Input { protected boolean isDraggable = true; //Boolean to check if the widget is being dragged public boolean dragging; + private boolean wasDragged = false; + //To enable/disable snapping public boolean isShiftDown = false; /** @@ -39,7 +51,7 @@ public abstract class Widget implements Input { */ public String modId = "unknown"; - public Text tooltipText; + public Component tooltipText; // Boolean to know if the widget is currently being displayed in an instance of AbstractMoveableScreen protected boolean isInEditor = false; @@ -47,26 +59,26 @@ public abstract class Widget implements Input { // Absolute position of the widget on screen in pixels. protected int x, y; - protected boolean shouldScale = true; + protected boolean canScale = true; protected Anchor anchor; // The chosen anchor point //Dimensions of the widget - protected WidgetBox widgetBox; + protected final WidgetBox widgetBox; private int startX, startY; protected int offsetX, offsetY; // Offset from the anchor point public Widget(WidgetData DATA, String modId) { - this(DATA, modId, Anchor.CENTER); + this(DATA, modId, Anchor._default()); } public Widget(WidgetData DATA, String modId, Anchor anchor) { this.DATA = DATA; - widgetBox = new WidgetBox(0, 0, 0, 0); + this.widgetBox = new WidgetBox(0, 0, 0, 0); this.modId = modId; this.anchor = anchor; - this.tooltipText = Text.of(DATA.description()); + this.tooltipText = Component.literal(DATA.description()); init(); } @@ -94,13 +106,12 @@ public int getY() { return y; } - public float getWidth() { - return widgetBox.getWidth(); + public float getScale() { + return canScale ? widgetBox.getScale() * DynamicHUD.getGlobalScale() : 1.0f; } - public float getHeight() { - return widgetBox.getHeight(); - } + public float getWidth() { return widgetBox.getWidth(); } + public float getHeight() { return widgetBox.getHeight(); } private void calculateOffset(int initialX, int initialY, int screenWidth, int screenHeight) { int anchorX = getAnchorX(screenWidth); @@ -128,7 +139,7 @@ private int getAnchorY(int screenHeight) { // Update position based on anchor and offset void updatePosition(int screenWidth, int screenHeight) { if (offsetX == 0 || offsetY == 0) { - calculateOffset(x, y, mc.getWindow().getScaledWidth(), mc.getWindow().getScaledHeight()); + calculateOffset(x, y, mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()); } int anchorX = getAnchorX(screenWidth); @@ -141,9 +152,9 @@ void updatePosition(int screenWidth, int screenHeight) { public void setPosition(int x, int y) { this.x = x; this.y = y; - if (mc.getWindow() != null) { - calculateOffset(x, y, mc.getWindow().getScaledWidth(), mc.getWindow().getScaledHeight()); - updatePosition(mc.getWindow().getScaledWidth(), mc.getWindow().getScaledHeight()); + if(mc.getWindow() != null) { + calculateOffset(x, y, mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()); + updatePosition(mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()); } } @@ -159,17 +170,17 @@ public boolean isOverlapping(Widget other) { /** * Renders the widget on the screen. */ - public final void render(DrawContext drawContext, int mouseX, int mouseY) { + public final void render(GuiGraphics graphics, int mouseX, int mouseY) { if (!isVisible()) return; - if (shouldScale) { - DrawHelper.scaleAndPosition(drawContext.getMatrices(), getX(), getY(), GlobalConfig.get().getScale()); + if (canScale) { + DrawHelper.scaleAndPosition(graphics.pose(), getX(), getY(), getScale()); } - renderWidget(drawContext, mouseX, mouseY); + renderWidget(graphics, mouseX, mouseY); - if (shouldScale) { - DrawHelper.stopScaling(drawContext.getMatrices()); + if (canScale) { + DrawHelper.stopScaling(graphics.pose()); } clampPosition(); } @@ -177,18 +188,18 @@ public final void render(DrawContext drawContext, int mouseX, int mouseY) { /** * Renders the widget on the editor screen. */ - public final void renderInEditor(DrawContext drawContext, int mouseX, int mouseY) { + public final void renderInEditor(GuiGraphics graphics, int mouseX, int mouseY) { if (!isInEditor) return; - drawWidgetBackground(drawContext); + drawWidgetBackground(graphics,mouseX,mouseY); - if (shouldScale) { - DrawHelper.scaleAndPosition(drawContext.getMatrices(), getX(), getY(), GlobalConfig.get().getScale()); + if (canScale) { + DrawHelper.scaleAndPosition(graphics.pose(), getX(), getY(), getScale()); } - renderWidgetInEditor(drawContext, mouseX, mouseY); + renderWidgetInEditor(graphics, mouseX, mouseY); - if (shouldScale) { - DrawHelper.stopScaling(drawContext.getMatrices()); + if (canScale) { + DrawHelper.stopScaling(graphics.pose()); } clampPosition(); } @@ -199,44 +210,45 @@ public final void renderInEditor(DrawContext drawContext, int mouseX, int mouseY * The mouse position values are only passed when in a {@link com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen} screen. *

* - * @param context DrawContext Object + * @param graphics GuiGraphics Object * @param mouseX X position of mouse. * @param mouseY Y position of mouse */ - public abstract void renderWidget(DrawContext context, int mouseX, int mouseY); + public abstract void renderWidget(GuiGraphics graphics, int mouseX, int mouseY); /** * Renders the widget in the editor screen with a background. - * Override this method without super call to remove the background. * Could also be used to display placeholder values. */ - private void renderWidgetInEditor(DrawContext context, int mouseX, int mouseY) { - //drawWidgetBackground(context); + private void renderWidgetInEditor(GuiGraphics graphics, int mouseX, int mouseY) { + //drawWidgetBackground(graphics); - renderWidget(context, mouseX, mouseY); + renderWidget(graphics, mouseX, mouseY); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (widgetBox.isMouseOver(mouseX, mouseY) && button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { - toggle(); + wasDragged = false; if (isDraggable) { startX = (int) (mouseX - x); startY = (int) (mouseY - y); dragging = true; + } else { + toggle(); // Static widgets toggle immediately } return true; } return false; } - /* Input related methods. Override with **super call** to add your own input-based code like contextMenu */ - public void clampPosition() { - this.x = (int) MathHelper.clamp(this.x, 0, mc.getWindow().getScaledWidth() - getWidth()); - this.y = (int) MathHelper.clamp(this.y, 0, mc.getWindow().getScaledHeight() - getHeight()); + this.x = (int) Mth.clamp(this.x, 0, mc.getWindow().getGuiScaledWidth() - getWidth()); + this.y = (int) Mth.clamp(this.y, 0, mc.getWindow().getGuiScaledHeight() - getHeight()); } + /** Input related methods. Override with **super call** to add your own input-based code like contextMenu **/ + @Override public final boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { return false; @@ -244,7 +256,10 @@ public final boolean mouseDragged(double mouseX, double mouseY, int button, doub public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY, int snapSize) { if (!isDraggable) return false; + if (dragging && button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + wasDragged = true; + int newX = (int) (mouseX - startX); int newY = (int) (mouseY - startY); @@ -252,8 +267,8 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double del // Higher the snapSize, more the grid boxes if (this.isShiftDown) { // Calculate the size of each snap box - int snapBoxWidth = mc.getWindow().getScaledWidth() / snapSize; - int snapBoxHeight = mc.getWindow().getScaledHeight() / snapSize; + int snapBoxWidth = mc.getWindow().getGuiScaledWidth() / snapSize; + int snapBoxHeight = mc.getWindow().getGuiScaledHeight() / snapSize; // Calculate the index of the snap box that the new position would be in and // snap the new position to the top-left corner of the snap box @@ -261,10 +276,10 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double del newY = (newY / snapBoxHeight) * snapBoxHeight; } - this.x = (int) MathHelper.clamp(newX, 0, mc.getWindow().getScaledWidth() - getWidth()); - this.y = (int) MathHelper.clamp(newY, 0, mc.getWindow().getScaledHeight() - getHeight()); + this.x = (int) Mth.clamp(newX, 0, mc.getWindow().getGuiScaledWidth() - getWidth()); + this.y = (int) Mth.clamp(newY, 0, mc.getWindow().getGuiScaledHeight() - getHeight()); - calculateOffset(x, y, mc.getWindow().getScaledWidth(), mc.getWindow().getScaledHeight()); // Set initial offset + calculateOffset(x, y, mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()); // Set initial offset return true; } @@ -273,8 +288,17 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double del @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (dragging && widgetBox.isMouseOver(mouseX,mouseY) && button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + if (!wasDragged) { + toggle(); + dragging = false; + wasDragged = false; + return true; + } + } dragging = false; - return true; + wasDragged = false; + return false; } /** @@ -285,6 +309,12 @@ public boolean mouseReleased(double mouseX, double mouseY, int button) { */ @Override public void mouseScrolled(double mouseX, double mouseY, double vAmount, double hAmount) { + if (canScale && widgetBox.isMouseOver(mouseX,mouseY) && GLFW.glfwGetKey(mc.getWindow().handle(),GLFW.GLFW_KEY_LEFT_CONTROL) == GLFW.GLFW_PRESS) { + widgetBox.setScale(widgetBox.getScale() + (float) vAmount * 0.05f); + + clampPosition(); + calculateOffset(x, y, mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()); + } } @Override @@ -309,38 +339,44 @@ public void onClose() { /** * Displays a faint grayish background if enabled or faint reddish background if disabled. - * Drawn with 2 pixel offset to all sides */ - protected void drawWidgetBackground(DrawContext context) { - int backgroundColor = this.isVisible() ? GlobalConfig.get().getHudActiveColor().getRGB() : GlobalConfig.get().getHudInactiveColor().getRGB(); + protected void drawWidgetBackground(GuiGraphics graphics, int mouseX, int mouseY) { + boolean isHovered = widgetBox.isMouseOver(mouseX, mouseY); + Color backgroundColor = this.isVisible() ? GlobalConfig.get().getHudActiveColor() : GlobalConfig.get().getHudInactiveColor(); WidgetBox box = this.getWidgetBox(); - DrawHelper.drawRectangle(context, + + DrawHelper.drawRectangle(graphics, box.x, box.y, box.getWidth(), box.getHeight(), - backgroundColor); + isHovered ? backgroundColor.darker().darker().getRGB() : backgroundColor.getRGB()); } /** - * Set the tooltip text of the widget + * Set the tooltip Component of the widget */ - protected void setTooltipText(Text text) { - this.tooltipText = text; + protected void setTooltipText(Component Component) { + this.tooltipText = Component; + } + + public void setWidgetScale(float widgetScale) { + widgetBox.setScale(widgetScale); } - public void readFromTag(NbtCompound tag) { + public void readFromTag(CompoundTag tag) { modId = tag.getString("modId").orElse("unknown"); uid = tag.contains("UID") ? new UID(tag.getString("UID").get()) : UID.generate(); // x = tag.getInt("x"); // y = tag.getInt("y"); - anchor = Anchor.valueOf(tag.getString("anchor").orElse("CENTER")); - offsetX = tag.getInt("offsetX").orElse(0); - offsetY = tag.getInt("offsetY").orElse(0); + anchor = Anchor.valueOf(tag.getString("anchor").orElse("TOP_LEFT")); + offsetX = tag.getIntOr("offsetX", 0); + offsetY = tag.getIntOr("offsetY",0); isVisible = tag.getBoolean("isVisible").orElse(true); isDraggable = tag.getBoolean("isDraggable").orElse(true); - shouldScale = tag.getBoolean("shouldScale").orElse(true); + canScale = tag.getBoolean("canScale").orElse(true); + widgetBox.setScale(tag.getFloat("widgetScale").orElse(1.0f)); } /** @@ -348,12 +384,13 @@ public void readFromTag(NbtCompound tag) { * * @param tag The tag to write to */ - public void writeToTag(NbtCompound tag) { + public void writeToTag(CompoundTag tag) { tag.putString("name", DATA.name()); tag.putString("modId", modId); tag.putString("UID", uid.getUniqueID()); tag.putBoolean("isDraggable", isDraggable); - tag.putBoolean("shouldScale", shouldScale); + tag.putBoolean("canScale", canScale); + tag.putFloat("widgetScale", widgetBox.getScale()); // tag.putInt("x", x); // tag.putInt("y", y); tag.putString("anchor", anchor.name()); @@ -370,12 +407,8 @@ public WidgetBox getWidgetBox() { return widgetBox; } - public void setUid(UID uid) { - this.uid = uid; - } - - public void setShouldScale(boolean shouldScale) { - this.shouldScale = shouldScale; + public void setCanScale(boolean canScale) { + this.canScale = canScale; } public String getModId() { @@ -393,20 +426,30 @@ public String toString() { ", isVisible=" + isVisible + ", isDraggable=" + isDraggable + ", shiftDown=" + isShiftDown + - ", shouldScale=" + shouldScale + + ", canScale=" + canScale + '}'; } - public enum Anchor {TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER} + public enum Anchor { + TOP_LEFT, + TOP_RIGHT, + BOTTOM_LEFT, + BOTTOM_RIGHT, + CENTER; + + public static Anchor _default(){ + return TOP_LEFT; + } + } public abstract static class WidgetBuilder { protected int x; protected int y; - protected boolean display = true; + protected boolean isVisible = true; protected boolean isDraggable = true; protected boolean shouldScale = true; protected String modID = "unknown"; - + protected Anchor anchor = Anchor._default(); /** * X Position of the widget of the scaled screen. @@ -424,8 +467,8 @@ public T setY(int y) { return self(); } - public T setDisplay(boolean display) { - this.display = display; + public T setIsVisible(boolean isVisible) { + this.isVisible = isVisible; return self(); } @@ -444,6 +487,11 @@ public T setModID(String modID) { return self(); } + public T anchor(Anchor anchor) { + this.anchor = anchor; + return self(); + } + /** * Method to be overridden in subclasses to return "this" correctly */ diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java index e12da3f..efd5591 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java @@ -1,19 +1,33 @@ package com.tanishisherewith.dynamichud.widget; +import com.tanishisherewith.dynamichud.DynamicHUD; +import net.minecraft.util.Mth; + public class WidgetBox { public float x, y; - private float width; - private float height; + private float width, rawWidth; + private float height, rawHeight; + protected float scale; - public WidgetBox(int x, int y, int width, int height) { + public WidgetBox(float x, float y, float width, float height, float scale) { this.x = x; this.y = y; - this.width = width; - this.height = height; + this.width = width * scale; + this.height = height * scale; + this.rawWidth = width; + this.rawHeight = height; + this.scale = scale; + } + + public WidgetBox(float x, float y, float width, float height) { + this(x,y,width,height,1.0f); } + /** + * Checks if the mouse is over the box, accounting for its scale factor. + */ public boolean isMouseOver(double mouseX, double mouseY) { - return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; + return mouseX >= x && mouseX <= x + this.width && mouseY >= y && mouseY <= y + this.height; } public float getWidth() { @@ -24,45 +38,57 @@ public float getHeight() { return height; } - public boolean intersects(WidgetBox other) { - // Check if this box is to the right of the other box - if (this.x > other.x + other.width) { - return false; - } + public boolean intersects(WidgetBox other, float myScale, float otherScale) { + float myWidth = this.width * myScale; + float myHeight = this.height * myScale; + float oWidth = other.width * otherScale; + float oHeight = other.height * otherScale; - // Check if this box is to the left of the other box - if (this.x + this.width < other.x) { - return false; - } + return this.x < other.x + oWidth && this.x + myWidth > other.x && + this.y < other.y + oHeight && this.y + myHeight > other.y; + } - // Check if this box is below the other box - if (this.y > other.y + other.height) { - return false; - } + public void setScale(float scale) { + this.scale = Mth.clamp(scale, 0.2f, 10.0f); + } - // Check if this box is above the other box - // If none of the above conditions are met, the boxes must intersect - return !(this.y + this.height < other.y); + public float getScale() { + return scale * DynamicHUD.getGlobalScale(); } - public void setDimensionsNoScale(float x, float y, float width, float height) { - this.x = x; - this.y = y; - this.height = height; - this.width = width; + public float getRawWidth() { + return rawWidth; + } + + public float getRawHeight() { + return rawHeight; } - public void setDimensions(float x, float y, float width, float height, boolean shouldScale, float scale) { + private void setDimensions(float x, float y, float width, float height, boolean shouldScale, float scale) { this.x = x; this.y = y; this.height = height * (shouldScale ? scale : 1.0f); this.width = width * (shouldScale ? scale : 1.0f); + this.rawWidth = width; + this.rawHeight = height; + } + + private void setSize(float width, float height, boolean shouldScale, float scale) { + if (width >= 0) { + this.width = (float) Math.ceil(width * (shouldScale ? scale : 1.0f)); + this.rawWidth = width; + } + if (height >= 0) { + this.height = (float) Math.ceil(height * (shouldScale ? scale : 1.0f)); + this.rawHeight = height; + } + } + + public void setDimensions(float x, float y, float width, float height, boolean canScale) { + this.setDimensions(x,y,width,height,canScale,getScale()); } - public void setSize(double width, double height, boolean shouldScale, float scale) { - if (width >= 0) - this.width = (int) Math.ceil(width * (shouldScale ? scale : 1.0f)); - if (height >= 0) - this.height = (int) Math.ceil(height * (shouldScale ? scale : 1.0f)); + public void setSize(float width, float height, boolean canScale) { + this.setSize(width,height,canScale,getScale()); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetManager.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetManager.java index 1f0f05e..889b1e6 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetManager.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetManager.java @@ -1,11 +1,11 @@ package com.tanishisherewith.dynamichud.widget; import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.config.GlobalConfig; import com.tanishisherewith.dynamichud.mixins.ScreenMixin; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtList; import java.io.DataOutputStream; import java.io.File; @@ -128,8 +128,8 @@ public static void onScreenResized(int newWidth, int newHeight, int previousWidt * @param file The file to save to */ public static void saveWidgets(File file, List widgets) throws IOException { - NbtCompound rootTag = new NbtCompound(); - NbtList widgetList = new NbtList(); + CompoundTag rootTag = new CompoundTag(); + ListTag widgetList = new ListTag(); printInfo("Saving widgets"); @@ -140,7 +140,7 @@ public static void saveWidgets(File file, List widgets) throws IOExcepti Set widgetSet = new HashSet<>(); for (Widget widget : widgets) { - NbtCompound widgetTag = new NbtCompound(); + CompoundTag widgetTag = new CompoundTag(); //I faced this exception once and had to spend 10 minutes trying to find it. P.S. It leaves 0 stacktrace message try { widget.writeToTag(widgetTag); @@ -176,6 +176,8 @@ public static void saveWidgets(File file, List widgets) throws IOExcepti // If save operation was successful, replace the old file with the new one Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + + GlobalConfig.HANDLER.save(); } /** @@ -190,20 +192,20 @@ public static List loadWidgets(File file) throws IOException { file = new File(file.getAbsolutePath() + ".backup"); } - NbtCompound rootTag = NbtIo.read(file.toPath()); + CompoundTag rootTag = NbtIo.read(file.toPath()); if (rootTag == null) { printWarn("RootTag is null. File is either empty or corrupted: " + file); return Collections.emptyList(); } - NbtList widgetList = rootTag.getList("widgets").orElse(null); + ListTag widgetList = rootTag.getList("widgets").orElse(null); if (widgetList == null) { printWarn("WidgetList is null. File is empty: " + file); return Collections.emptyList(); } List widgetsToAdd = new ArrayList<>(); for (int i = 0; i < widgetList.size(); i++) { - NbtCompound widgetTag = widgetList.getCompound(i).orElse(null); + CompoundTag widgetTag = widgetList.getCompound(i).orElse(null); if(widgetTag == null) continue; WidgetData widgetData = widgetDataMap.get(widgetTag.getString("name").orElse("unknown")); if (widgetData == null) { diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java index d0bc2f1..9119129 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java @@ -2,16 +2,17 @@ import com.tanishisherewith.dynamichud.DynamicHUD; import com.tanishisherewith.dynamichud.config.GlobalConfig; +import com.tanishisherewith.dynamichud.helpers.DrawHelper; import com.tanishisherewith.dynamichud.internal.System; import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; import com.tanishisherewith.dynamichud.utils.Input; -import com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen.ContextMenuScreenRegistry; -import dev.isxander.yacl3.gui.YACLScreen; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.GameMenuScreen; -import net.minecraft.client.gui.screen.Screen; +import com.tanishisherewith.dynamichud.utils.contextmenu.screen.ContextMenuScreenRegistry; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.PauseScreen; +import net.minecraft.client.gui.screens.Screen; import org.lwjgl.glfw.GLFW; +import java.awt.*; import java.util.List; import java.util.function.Predicate; @@ -21,19 +22,25 @@ public class WidgetRenderer implements Input { public Widget selectedWidget = null; List widgets; private boolean renderInGameHud = true; - private int Z_Index = -1; + //private int Z_Index = -1; + + // Snapping Guideline Coordinates + private float snapLineX = -1; + private float snapLineY = -1; + private float screenCenterX = -1; + private float screenCenterY = -1; /** * Add the list of widgets the widgetRenderer should render *

- * By default, it adds the {@link GameMenuScreen} to allow rendering of the widgets in the pause/main menu screen. + * By default, it adds the {@link PauseScreen} to allow rendering of the widgets in the pause/main menu screen. * * @param widgets List of widgets to render */ public WidgetRenderer(List widgets) { this.widgets = widgets; // Render in GameMenuScreen - this.allowedScreens = screen -> screen.getClass() == GameMenuScreen.class || + this.allowedScreens = screen -> screen.getClass() == PauseScreen.class || System.getInstances(ContextMenuScreenRegistry.class, DynamicHUD.MOD_ID).stream().anyMatch(registry -> registry.screenKlass == screen.getClass()); } @@ -88,22 +95,26 @@ private boolean renderInDebugScreen() { if (GlobalConfig.get().renderInDebugScreen()) { return true; } - return !DynamicHUD.MC.getDebugHud().shouldShowDebugHud(); + return !DynamicHUD.MC.getDebugOverlay().showDebugScreen(); } - public void renderWidgets(DrawContext context, int mouseX, int mouseY) { + public void renderWidgets(GuiGraphics graphics, int mouseX, int mouseY) { if (WidgetManager.getWidgets().isEmpty() || !renderInDebugScreen()) return; - Screen currentScreen = DynamicHUD.MC.currentScreen; + Screen currentScreen = DynamicHUD.MC.screen; - context.getMatrices().push(); - context.getMatrices().translate(0, 0, Z_Index); + // graphics.pose().pushMatrix(); + // graphics.pose().translate(0, 0,Z_Index); //Render in editing screen if (currentScreen instanceof AbstractMoveableScreen) { for (Widget widget : widgets) { widget.isInEditor = true; - widget.renderInEditor(context, mouseX, mouseY); + widget.renderInEditor(graphics, mouseX, mouseY); + } + + if(GlobalConfig.get().doSmartSnapping()) { + drawSnapGuides(graphics); } return; } @@ -111,15 +122,15 @@ public void renderWidgets(DrawContext context, int mouseX, int mouseY) { if ((currentScreen == null && renderInGameHud) || allowedScreens.test(currentScreen)) { for (Widget widget : widgets) { widget.isInEditor = false; - widget.render(context, 0, 0); + widget.render(graphics, 0, 0); } } - context.getMatrices().pop(); + //graphics.pose().popMatrix(); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - Screen currentScreen = DynamicHUD.MC.currentScreen; + Screen currentScreen = DynamicHUD.MC.screen; if (currentScreen == null) { return false; } @@ -139,7 +150,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { @Override public void mouseScrolled(double mouseX, double mouseY, double vAmount, double hAmount) { - Screen currentScreen = DynamicHUD.MC.currentScreen; + Screen currentScreen = DynamicHUD.MC.screen; if (currentScreen == null) { return; } @@ -155,7 +166,8 @@ public void charTyped(char c, int modifiers) { } public void onCloseScreen() { - if (DynamicHUD.MC.currentScreen instanceof AbstractMoveableScreen) { + clearSnapLines(); + if (DynamicHUD.MC.screen instanceof AbstractMoveableScreen) { for (Widget widget : widgets) { widget.onClose(); } @@ -168,13 +180,18 @@ public List getWidgets() { @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { - Screen currentScreen = DynamicHUD.MC.currentScreen; + selectedWidget = null; + clearSnapLines(); + + Screen currentScreen = DynamicHUD.MC.screen; if (currentScreen == null) { return false; } if (currentScreen instanceof AbstractMoveableScreen) { for (Widget widget : widgets) { - widget.mouseReleased(mouseX, mouseY, button); + if(widget.mouseReleased(mouseX, mouseY, button)){ + return true; + } } } return false; @@ -186,7 +203,7 @@ public final boolean mouseDragged(double mouseX, double mouseY, int button, doub } public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY, int snapSize) { - Screen currentScreen = DynamicHUD.MC.currentScreen; + Screen currentScreen = DynamicHUD.MC.screen; if (currentScreen == null) { return false; } @@ -196,17 +213,21 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double del // if they are overlapped on each other. if (widget.mouseDragged(mouseX, mouseY, button, deltaX, deltaY, snapSize)) { selectedWidget = widget; + if(GlobalConfig.get().doSmartSnapping()) { + applySnappingAndGuides(selectedWidget); + } return true; } } selectedWidget = null; + clearSnapLines(); } return false; } @Override public void keyPressed(int key, int scanCode, int modifiers) { - Screen currentScreen = DynamicHUD.MC.currentScreen; + Screen currentScreen = DynamicHUD.MC.screen; if (currentScreen instanceof AbstractMoveableScreen && (key == GLFW.GLFW_KEY_LEFT_SHIFT || key == GLFW.GLFW_KEY_RIGHT_SHIFT)) { for (Widget widget : widgets) { widget.isShiftDown = true; @@ -216,7 +237,7 @@ public void keyPressed(int key, int scanCode, int modifiers) { @Override public void keyReleased(int key, int scanCode, int modifiers) { - Screen currentScreen = DynamicHUD.MC.currentScreen; + Screen currentScreen = DynamicHUD.MC.screen; if (currentScreen instanceof AbstractMoveableScreen && (key == GLFW.GLFW_KEY_LEFT_SHIFT || key == GLFW.GLFW_KEY_RIGHT_SHIFT)) { for (Widget widget : widgets) { widget.isShiftDown = false; @@ -224,8 +245,168 @@ public void keyReleased(int key, int scanCode, int modifiers) { } } - public WidgetRenderer withZIndex(int z_Index) { - this.Z_Index = z_Index; - return this; + /** + * Renders alignment and screen axis guidelines if snapping conditions are met. + */ + private void drawSnapGuides(GuiGraphics graphics) { + int screenWidth = DynamicHUD.MC.getWindow().getGuiScaledWidth(); + int screenHeight = DynamicHUD.MC.getWindow().getGuiScaledHeight(); + + int screenCenterColor = new Color(255, 80, 80, 180).getRGB(); // Light red for screen axes + int widgetSnapColor = new Color(0, 220, 255, 180).getRGB(); // Bright cyan for widget alignments + + // Screen Vertical Center guideline + if (screenCenterX != -1) { + DrawHelper.drawVerticalLine(graphics, screenCenterX, 0, screenHeight, 1.0f, screenCenterColor); + } + + // Screen Horizontal Center guideline + if (screenCenterY != -1) { + DrawHelper.drawHorizontalLine(graphics, 0, screenWidth, screenCenterY, 1.0f, screenCenterColor); + } + + // Neighboring Widget Vertical alignment guideline + if (snapLineX != -1) { + DrawHelper.drawVerticalLine(graphics, snapLineX, 0, screenHeight, 1.0f, widgetSnapColor); + } + + // Neighboring Widget Horizontal alignment guideline + if (snapLineY != -1) { + DrawHelper.drawHorizontalLine(graphics, 0, screenWidth, snapLineY, 1.0f, widgetSnapColor); + } + } + + /** + * Resets active alignment line parameters. + */ + private void clearSnapLines() { + snapLineX = -1; + snapLineY = -1; + screenCenterX = -1; + screenCenterY = -1; } + + /** + * Evaluates alignment thresholds and snaps the dragged widget to relevant lines or centers. + */ + private void applySnappingAndGuides(Widget dragged) { + clearSnapLines(); + + if (dragged == null) return; + + int screenWidth = DynamicHUD.MC.getWindow().getGuiScaledWidth(); + int screenHeight = DynamicHUD.MC.getWindow().getGuiScaledHeight(); + float threshold = 4f; // Snap tolerance threshold in pixels + + float dw = dragged.getWidth(); + float dh = dragged.getHeight(); + + float dl = dragged.getX(); + float dr = dl + dw; + float dcx = dl + dw / 2.0f; + + float dt = dragged.getY(); + float db = dt + dh; + float dcy = dt + dh / 2.0f; + + boolean snappedX = false; + boolean snappedY = false; + + float screenMidX = screenWidth / 2.0f; + float screenMidY = screenHeight / 2.0f; + + if (Math.abs(dcx - screenMidX) < threshold) { + dragged.setPosition((int) (screenMidX - dw / 2.0f), dragged.getY()); + screenCenterX = screenMidX; + snappedX = true; + // Refresh coordinates + dl = dragged.getX(); + dr = dl + dw; + dcx = dl + dw / 2.0f; + } + + if (Math.abs(dcy - screenMidY) < threshold) { + dragged.setPosition(dragged.getX(), (int) (screenMidY - dh / 2.0f)); + screenCenterY = screenMidY; + snappedY = true; + // Refresh coordinates + dt = dragged.getY(); + db = dt + dh; + dcy = dt + dh / 2.0f; + } + + for (Widget other : widgets) { + if (other == dragged || !other.isVisible()) continue; + + float ow = other.getWidth(); + float oh = other.getHeight(); + + float ol = other.getX(); + float or = ol + ow; + float ocx = ol + ow / 2.0f; + + float ot = other.getY(); + float ob = ot + oh; + float ocy = ot + oh / 2.0f; + + // X-Axis Snap Checks + if (!snappedX) { + if (Math.abs(dl - ol) < threshold) { // Left to Left + dragged.setPosition((int) ol, dragged.getY()); + snapLineX = ol; + snappedX = true; + } else if (Math.abs(dl - or) < threshold) { // Left to Right + dragged.setPosition((int) or, dragged.getY()); + snapLineX = or; + snappedX = true; + } else if (Math.abs(dr - ol) < threshold) { // Right to Left + dragged.setPosition((int) (ol - dw), dragged.getY()); + snapLineX = ol; + snappedX = true; + } else if (Math.abs(dr - or) < threshold) { // Right to Right + dragged.setPosition((int) (or - dw), dragged.getY()); + snapLineX = or; + snappedX = true; + } else if (Math.abs(dcx - ocx) < threshold) { // Center to Center + dragged.setPosition((int) (ocx - dw / 2.0f), dragged.getY()); + snapLineX = ocx; + snappedX = true; + } + } + + // Y-Axis Snap Checks + if (!snappedY) { + if (Math.abs(dt - ot) < threshold) { // Top to Top + dragged.setPosition(dragged.getX(), (int) ot); + snapLineY = ot; + snappedY = true; + } else if (Math.abs(dt - ob) < threshold) { // Top to Bottom + dragged.setPosition(dragged.getX(), (int) ob); + snapLineY = ob; + snappedY = true; + } else if (Math.abs(db - ot) < threshold) { // Bottom to Top + dragged.setPosition(dragged.getX(), (int) (ot - dh)); + snapLineY = ot; + snappedY = true; + } else if (Math.abs(db - ob) < threshold) { // Bottom to Bottom + dragged.setPosition(dragged.getX(), (int) (ob - dh)); + snapLineY = ob; + snappedY = true; + } else if (Math.abs(dcy - ocy) < threshold) { // Center to Center + dragged.setPosition(dragged.getX(), (int) (ocy - dh / 2.0f)); + snapLineY = ocy; + snappedY = true; + } + } + + if (snappedX && snappedY) break; + } + } + + + + // public WidgetRenderer withZIndex(int z_Index) { + // this.Z_Index = z_Index; + // return this; + // } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/widgets/GraphWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widgets/GraphWidget.java index e2e0d44..94b1175 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widgets/GraphWidget.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widgets/GraphWidget.java @@ -1,9 +1,9 @@ package com.tanishisherewith.dynamichud.widgets; -import com.tanishisherewith.dynamichud.config.GlobalConfig; import com.tanishisherewith.dynamichud.helpers.ColorHelper; import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.helpers.animationhelper.animations.MathAnimations; +import com.tanishisherewith.dynamichud.renderstates.GradientShadowRenderState; +import com.tanishisherewith.dynamichud.renderstates.InterpolatedCurveRenderState; import com.tanishisherewith.dynamichud.utils.CustomRenderLayers; import com.tanishisherewith.dynamichud.utils.DynamicValueRegistry; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; @@ -14,16 +14,17 @@ import com.tanishisherewith.dynamichud.utils.contextmenu.options.ColorOption; import com.tanishisherewith.dynamichud.utils.contextmenu.options.DoubleOption; import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option; +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.MinecraftSkin; import com.tanishisherewith.dynamichud.widget.DynamicValueWidget; import com.tanishisherewith.dynamichud.widget.WidgetBox; import com.tanishisherewith.dynamichud.widget.WidgetData; import com.twelvemonkeys.lang.Validate; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; -import org.joml.Matrix4f; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Util; +import org.joml.Matrix3x2f; import java.awt.*; import java.util.ArrayList; @@ -49,20 +50,26 @@ public class GraphWidget extends DynamicValueWidget implements ContextMenuProvid private float lineThickness; private boolean showGrid; private int gridLines; - private float width; - private float height; + private float gWidth; + private float gHeight; private String label; /// Automatically update the min and max of the graph private boolean autoUpdateRange = false; + ///The min range between the min/max of graph + public float minRangeSpan = 10f; + private float stepY; private float valueStep; - private float scale; + private float valueScale; int offset = -2; - public GraphWidget(String registryID, String registryKey, String modId, Anchor anchor, float width, float height, int maxDataPoints, float minValue, float maxValue, Color graphColor, Color backgroundColor, float lineThickness, boolean showGrid, int gridLines, String label) { + private long lastSampleTime = 0; + private long sampleIntervalMs = 100; + + public GraphWidget(String registryID, String registryKey, String modId, Anchor anchor, float gWidth, float gHeight, int maxDataPoints, float minValue, float maxValue, Color graphColor, Color backgroundColor, float lineThickness, boolean showGrid, int gridLines, String label) { super(DATA, modId, anchor, registryID, registryKey); - this.width = width; - this.height = height; + this.gWidth = gWidth; + this.gHeight = gHeight; this.maxDataPoints = maxDataPoints; this.graphColor = graphColor; this.backgroundColor = backgroundColor; @@ -74,8 +81,6 @@ public GraphWidget(String registryID, String registryKey, String modId, Anchor a this.setMaxValue(maxValue); internal_init(); - - createMenu(); ContextMenuManager.getInstance().registerProvider(this); } @@ -83,22 +88,24 @@ private void internal_init() { Validate.isTrue(maxDataPoints > 2, "MaxDataPoints should be more than 2."); this.dataPoints = new float[maxDataPoints]; this.label = label.trim(); - this.widgetBox = new WidgetBox(x, y, (int) width, (int) height); - this.stepY = height / (gridLines + 1); + this.widgetBox.setDimensions(x, y, (int) gWidth, (int) gHeight, canScale); + this.stepY = gHeight / (gridLines + 1); this.valueStep = (maxValue - minValue) / (gridLines + 1); - this.scale = (float) MathHelper.clamp((stepY / 9.5), 0.0f, 1.0f); + this.valueScale = (float) Math.clamp((stepY / 9.5), 0.0f, 1.0f); + computeOffset(); - setTooltipText(Text.of("Graph displaying: " + label)); + setTooltipText(Component.literal("Graph displaying: " + label)); + createMenu(); } public GraphWidget() { - this(DynamicValueRegistry.GLOBAL_ID, "unknown", "unknown", Anchor.CENTER, 0, 0, 5, 0, 10, Color.RED, Color.GREEN, 0, false, 0, "empty"); + this(DynamicValueRegistry.GLOBAL_ID, "unknown", "unknown", Anchor._default(), 0, 0, 5, 0, 10, Color.RED, Color.GREEN, 0, false, 0, "empty"); } @Override public void init() { - // Initialize the buffer with minValue, mimicking ArrayList behavior + // init the buffer with minValue for (int i = 0; i < maxDataPoints; i++) { dataPoints[i] = minValue; } @@ -110,106 +117,217 @@ public GraphWidget autoUpdateRange() { return this; } - public void addDataPoint(Float value) { - if (value == null) return; - if (autoUpdateRange) { - if (getMaxValue() < value) { - setMaxValue(value + 10); - float diff = getMaxValue() - getPrevMaxValue(); - setMinValue(getMinValue() + diff); + /** + * Sets the timeline resolution (how many historical points are drawn on the graph width). + * Resizes the internal array live and shifts elements in order to prevent data loss. + */ + public void setMaxDataPoints(int newMax) { + if (newMax <= 2 || newMax == this.maxDataPoints) return; + + float[] newData = new float[newMax]; + int oldSize = this.maxDataPoints; + + // get historical data in order [oldest -> newest] + float[] chronologicalData = new float[oldSize]; + for (int i = 0; i < oldSize; i++) { + chronologicalData[i] = dataPoints[(head + i) % oldSize]; + } + + if (newMax >= oldSize) { + // Expand graph + float paddingValue = chronologicalData[0]; + int padCount = newMax - oldSize; + for (int i = 0; i < padCount; i++) { + newData[i] = paddingValue; } - if (getMinValue() > value) { - setMinValue(value - 10); - float diff = getPrevMinValue() - getMinValue(); - setMaxValue(getMaxValue() - diff); + System.arraycopy(chronologicalData, 0, newData, padCount, oldSize); + } else { + // Shrink graph + int skipCount = oldSize - newMax; + System.arraycopy(chronologicalData, skipCount, newData, 0, newMax); + } + + this.dataPoints = newData; + this.maxDataPoints = newMax; + this.head = 0; // Buffer is now sequential, next insertion index is flatly at 0 + + if (autoUpdateRange) { + recalculateDynamicBounds(); + } + } + + /** + * Sets the active polling sample throttle interval + * + * @param ms Time window separation between new samples in milliseconds. + */ + public GraphWidget setSampleInterval(long ms) { + this.sampleIntervalMs = Math.max(1L, ms); + return this; + } + + public long getSampleInterval() { + return this.sampleIntervalMs; + } + + /** + * check the active ring buffer values to adjust min/max boundaries + */ + private void recalculateDynamicBounds() { + float currentMin = Float.MAX_VALUE; + float currentMax = -Float.MAX_VALUE; + + for (float val : dataPoints) { + if (val < currentMin) currentMin = val; + if (val > currentMax) currentMax = val; + } + + int intervals = gridLines + 1; + float rawSpan = currentMax - currentMin; + + // Apply 15% padding on top and bottom of the raw range + float paddedSpan = rawSpan * 1.30f; + float requiredSpan = Math.max(paddedSpan, minRangeSpan); + + // Find the most appropriate whole number step size + float stepSize = 1.0f; + float[] candidates = {1f, 2f, 5f, 10f, 20f, 25f, 50f, 100f, 200f, 500f, 1000f, 2000f, 5000f}; + for (float c : candidates) { + if (c * intervals >= requiredSpan) { + stepSize = c; + break; } + stepSize = c; // Fallback to maximum candidate if range is massive } + float center = (currentMin + currentMax) / 2.0f; + float targetMin = center - (intervals * stepSize) / 2.0f; + targetMin = Math.round(targetMin / stepSize) * stepSize; + float targetMax = targetMin + (intervals * stepSize); + + setMinValue(targetMin); + setMaxValue(targetMax); + + this.computeOffset(); + } + + public void addDataPoint(Float value) { + if (value == null) return; + int index = (head) % maxDataPoints; - dataPoints[index] = MathHelper.clamp(value, minValue, maxValue); - head = (head + 1) % maxDataPoints; // Buffer full, overwrite oldest and move head + dataPoints[index] = value; + head = (head + 1) % maxDataPoints; + + if (autoUpdateRange) { + recalculateDynamicBounds(); + } else { + dataPoints[index] = Math.clamp(value, minValue, maxValue); + } } private List getInterpolatedPoints() { List points = new ArrayList<>(); if (dataPoints.length < 2) return points; - float xStep = width / (dataPoints.length - 1); + float xStep = gWidth / (dataPoints.length - 1); + float range = Math.max(maxValue - minValue, 0.0001f); + + float[] yVals = new float[dataPoints.length]; + for (int i = 0; i < dataPoints.length; i++) { + int index = (head + i) % dataPoints.length; + yVals[i] = y + gHeight - ((dataPoints[index] - minValue) / range * gHeight); + } + + // Calculate Monotone Cubic Spline Tangents + float[] m = new float[dataPoints.length]; + for (int i = 0; i < dataPoints.length; i++) { + if (i == 0) { + m[i] = yVals[1] - yVals[0]; + } else if (i == dataPoints.length - 1) { + m[i] = yVals[dataPoints.length - 1] - yVals[dataPoints.length - 2]; + } else { + float sPrev = yVals[i] - yVals[i - 1]; + float sNext = yVals[i + 1] - yVals[i]; + + if (sPrev * sNext <= 0) { + m[i] = 0; + } else { + m[i] = (sPrev + sNext) / 2.0f; + float maxMag = 3.0f * Math.min(Math.abs(sPrev), Math.abs(sNext)); + if (Math.abs(m[i]) > maxMag) { + m[i] = Math.signum(m[i]) * maxMag; + } + } + } + } + + int stepsPerSegment = 32; + float stepSize = 1.0f / stepsPerSegment; + for (int i = 0; i < dataPoints.length - 1; i++) { - int index1 = (head + i) % dataPoints.length; - int index2 = (head + i + 1) % dataPoints.length; + float y0 = yVals[i]; + float y1 = yVals[i + 1]; + float m0 = m[i]; + float m1 = m[i + 1]; + float x0 = x + i * xStep; - float x1 = x + i * xStep; - float y1 = y + height - ((dataPoints[index1] - minValue) / (maxValue - minValue) * height); - float x2 = x + (i + 1) * xStep; - float y2 = y + height - ((dataPoints[index2] - minValue) / (maxValue - minValue) * height); + for (int step = 0; step <= stepsPerSegment; step++) { + // Avoid redundant overlapping nodes between adjoining segments + if (step == stepsPerSegment && i < dataPoints.length - 2) continue; - // Add interpolated points using hermite spline (simplified) - for (float t = 0; t <= 1; t += 0.03f) { + float t = step * stepSize; float t2 = t * t; float t3 = t2 * t; + + // Cubic Hermite Spline formulation float h00 = 2 * t3 - 3 * t2 + 1; float h10 = t3 - 2 * t2 + t; float h01 = -2 * t3 + 3 * t2; - - float px = h00 * x1 + h10 * xStep + h01 * x2; - float py = h00 * y1 + h10 * (y2 - y1) + h01 * y2; - points.add(new float[]{px, py}); + float h11 = t3 - t2; + + float px = x0 + t * xStep; + float py = h00 * y0 + h10 * m0 + h01 * y1 + h11 * m1; + + py = Math.clamp(py, y, y + gHeight); + + if (points.isEmpty()) { + points.add(new float[]{px, py}); + } else { + float[] lastP = points.getLast(); + float distanceSq = (px - lastP[0]) * (px - lastP[0]) + (py - lastP[1]) * (py - lastP[1]); + if (distanceSq > 0.005f) { + points.add(new float[]{px, py}); + } + } } } return points; } // draw a continuous interpolated curve - private void drawInterpolatedCurve(DrawContext drawContext, List points, int color, float thickness) { + private void drawInterpolatedCurve(GuiGraphics graphics, List points, int color, float thickness) { if (points.size() < 2) return; - drawContext.draw(vcp -> { - Matrix4f matrix = drawContext.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(CustomRenderLayers.TRIANGLE_STRIP); - - for (int i = 0; i < points.size(); i++) { - float[] point = points.get(i); - float x = point[0]; - float y = point[1]; - - // Create a thick line by offsetting vertices perpendicular to the curve - float dx = (i < points.size() - 1) ? points.get(i + 1)[0] - x : x - points.get(i - 1)[0]; - float dy = (i < points.size() - 1) ? points.get(i + 1)[1] - y : y - points.get(i - 1)[1]; - float length = (float) Math.sqrt(dx * dx + dy * dy); - if (length == 0) continue; - - float offsetX = (thickness * 0.5f * dy) / length; - float offsetY = (thickness * 0.5f * -dx) / length; - - consumer.vertex(matrix, x + offsetX, y + offsetY, 0).color(color); - consumer.vertex(matrix, x - offsetX, y - offsetY, 0).color(color); - } - }); + graphics.guiRenderState.submitGuiElement( + new InterpolatedCurveRenderState(points, thickness, color, new Matrix3x2f(graphics.pose()), CustomRenderLayers.QUADS_CUSTOM_BLEND, (int) gWidth, (int) gHeight, graphics.scissorStack.peek()) + ); } // draw a gradient shadow under the curve - private void drawGradientShadow(DrawContext context, List points, float bottomY, int startColor, int endColor) { + private void drawGradientShadow(GuiGraphics graphics, List points, float bottomY, int startColor, int endColor) { if (points.size() < 2) return; - context.draw(vcp -> { - Matrix4f matrix = context.getMatrices().peek().getPositionMatrix(); - VertexConsumer consumer = vcp.getBuffer(CustomRenderLayers.TRIANGLE_STRIP); - - for (float[] point : points) { - float x = point[0]; - float y = point[1]; - - consumer.vertex(matrix, x, y, 0).color(startColor); - consumer.vertex(matrix, x, bottomY, 0).color(endColor); - } - }); + graphics.guiRenderState.submitGuiElement( + new GradientShadowRenderState(points,bottomY, startColor, endColor, new Matrix3x2f(graphics.pose()), RenderPipelines.DEBUG_QUADS, (int) gWidth, (int) gHeight, graphics.scissorStack.peek()) + ); } @Override - public void renderWidget(DrawContext context, int mouseX, int mouseY) { - if (valueSupplier != null) { + public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY) { + long currentTime = Util.getMillis(); + if (valueSupplier != null && (currentTime - lastSampleTime >= sampleIntervalMs)) { addDataPoint(getValue()); + lastSampleTime = currentTime; } // Safety check. Happens on startup @@ -217,20 +335,20 @@ public void renderWidget(DrawContext context, int mouseX, int mouseY) { if(graphColorRainbow) graphColor = ColorHelper.getRainbowColor(100); - DrawHelper.enableScissor(widgetBox); + // DrawHelper.enableScissor(widgetBox); - // Draw gradient background with rounded.fsh corners + // Draw gradient background with rounded corners if (!isInEditor) { DrawHelper.drawRoundedRectangle( - context, + graphics, x + offset, y, false, true, false, false, - width, - height, + gWidth, + gHeight, 4, backgroundColor.getRGB() ); @@ -238,143 +356,176 @@ public void renderWidget(DrawContext context, int mouseX, int mouseY) { // Draw grid lines and value markings if (showGrid) { - //TODO: The scale is too small when no. of grid lines is greater than 21 (20 is the barely visible threshold) + //TODO: The valueScale is too small when no. of grid lines is greater than 21 (20 is the barely visible threshold) for (int i = 1; i <= gridLines; i++) { float yPos = y + stepY * i; - DrawHelper.drawHorizontalLine(context, x + offset, width, yPos, 0.5f, 0x4DFFFFFF); // Semi-transparent white + DrawHelper.drawHorizontalLine(graphics, x + offset, gWidth, yPos, 0.5f, 0x4DFFFFFF); // Semi-transparent white // Draw value labels on the left axis float value = maxValue - (i * valueStep); String valueText = formatValue(value); - float texWidth = mc.textRenderer.getWidth(valueText) * scale; + float texWidth = mc.font.width(valueText); - //Scale the text to its proper position and size with grid lines - DrawHelper.scaleAndPosition(context.getMatrices(), x - 2, yPos, scale); - context.drawText(mc.textRenderer, valueText, Math.round(x + offset - texWidth), (int) (yPos - (mc.textRenderer.fontHeight * scale) / 2.0f), 0xFFFFFFFF, true); - DrawHelper.stopScaling(context.getMatrices()); + //Scale the Component to its proper position and size with grid lines + DrawHelper.scaleAndPosition(graphics.pose(), x + offset - texWidth/2.0f, yPos - (mc.font.lineHeight * valueScale) / 2.0f,texWidth,mc.font.lineHeight * valueScale, valueScale); + graphics.drawString(mc.font, valueText, Math.round(x + offset - texWidth), (int) (yPos - (mc.font.lineHeight * valueScale) / 2), 0xFFFFFFFF, true); + DrawHelper.stopScaling(graphics.pose()); } - // Update the offsets for the rest of the elements drawn. x += offset; // Draw vertical grid lines (time axis) - float stepX = width / 5; // 5 vertical lines + float stepX = gWidth / 5; // 5 vertical lines for (int i = 1; i < 5; i++) { float xPos = x + stepX * i; - DrawHelper.drawVerticalLine(context, xPos, y, height, 0.5f, 0x4DFFFFFF); + DrawHelper.drawVerticalLine(graphics, xPos, y, gHeight, 0.5f, 0x4DFFFFFF); } } - // Draw interpolated graph curve List points = getInterpolatedPoints(); - drawInterpolatedCurve(context, points, graphColor.getRGB(), lineThickness); + // Draw shadow effect under the graph drawGradientShadow( - context, points, y + height, - ColorHelper.changeAlpha(graphColor, 50).getRGB(), + graphics, points, y + gHeight, + ColorHelper.changeAlpha(graphColor, 60).getRGB(), 0x00000000 ); + drawInterpolatedCurve(graphics, points, graphColor.getRGB(), lineThickness); + + DrawHelper.scaleAndPosition(graphics.pose(), x + 5, y + 5, 0.5f); DrawHelper.drawChromaText( - context, label, + graphics, label, x + 5, y + 5, 1.0f, 0.8f, 1.0f, 0.05f, true ); + DrawHelper.stopScaling(graphics.pose()); + + if (!points.isEmpty()) { + float[] livePoint = points.getLast(); + + DrawHelper.drawFilledCircle(graphics, livePoint[0], livePoint[1], 0.9f, 0x26FFFFFF); + } // Draw axes - DrawHelper.drawHorizontalLine(context, x, width, y + height - 1, 1.0f, 0xFFFFFFFF); // X-axis - DrawHelper.drawVerticalLine(context, x, y, height, 1.0f, 0xFFFFFFFF); // Y-axis + DrawHelper.drawHorizontalLine(graphics, x, gWidth, y + gHeight - 1, 1.0f, 0xFFFFFFFF); // X-axis + DrawHelper.drawVerticalLine(graphics, x, y, gHeight, 1.0f, 0xFFFFFFFF); // Y-axis // Draw min and max value labels with formatted values /* DrawHelper.scaleAndPosition(context.getMatrices(),x - 5,y,0.5f); String formattedMaxVal = formatValue(maxValue); - context.drawText(mc.textRenderer, formattedMaxVal, x - 5 - mc.textRenderer.getWidth(formattedMaxVal), y - 4, 0xFFFFFFFF, true); + context.drawText(mc.font, formattedMaxVal, x - 5 - mc.font.gWidth(formattedMaxVal), y - 4, 0xFFFFFFFF, true); DrawHelper.stopScaling(context.getMatrices()); */ - DrawHelper.scaleAndPosition(context.getMatrices(), x - 5, y + height, 0.5f); String formattedMinVal = formatValue(minValue); - context.drawText(mc.textRenderer, formattedMinVal, x - mc.textRenderer.getWidth(formattedMinVal), (int) (y + height - 4), 0xFFFFFFFF, true); - DrawHelper.stopScaling(context.getMatrices()); + + DrawHelper.scaleAndPosition(graphics.pose(), x - mc.font.width(formattedMinVal)/2.0f, y + gHeight,mc.font.width(formattedMinVal),mc.font.lineHeight * 0.6f, 0.6f); + graphics.drawString(mc.font, formattedMinVal, x - mc.font.width(formattedMinVal), (int) (y + gHeight - 1), 0xFFFFFFFF, true); + DrawHelper.stopScaling(graphics.pose()); if(showGrid) x -= offset; - this.widgetBox.setDimensions(x, y, width + offset, height, shouldScale, GlobalConfig.get().getScale()); - DrawHelper.disableScissor(); + this.widgetBox.setDimensions(x, y, gWidth + offset, gHeight, canScale); + // DrawHelper.disableScissor(); if (menu != null) menu.set(getX(), getY(), (int) Math.ceil(getHeight())); } // format large values (like: 1000 -> 1K, 1000000 -> 1M) private String formatValue(float value) { - if (Math.abs(value) >= 1_000_000) { - return String.format("%.1fM", value / 1_000_000).trim(); - } else if (Math.abs(value) >= 1_000) { - return String.format("%.1fK", value / 1_000).trim(); + float absVal = Math.abs(value); + + if (absVal >= 1_000_000) { + long rounded = Math.round(value / 1_000_000f); + return String.format("%dM", rounded); + } else if (absVal >= 1_000) { + long rounded = Math.round(value / 1_000f); + return String.format("%dK", rounded); } else { - return String.format("%.0f", value).trim(); + return String.format("%d", (int) value); } } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - menu.toggleDisplay(widgetBox, mouseX, mouseY, button); - return super.mouseClicked(mouseX, mouseY, button); + return menu.toggleDisplay(widgetBox, mouseX, mouseY, button) || super.mouseClicked(mouseX, mouseY, button); } public void createMenu() { - ContextMenuProperties properties = ContextMenuProperties.builder().build(); + ContextMenuProperties properties = ContextMenuProperties.builder().skin(new MinecraftSkin(MinecraftSkin.PanelColor.FOREST_GREEN)).build(); menu = new ContextMenu<>(getX(), (int) (getY() + widgetBox.getHeight()), properties); - menu.addOption(new BooleanOption(Text.of("Show Grid"), - () -> this.showGrid, value -> this.showGrid = value, + menu.addOption(new BooleanOption(Component.literal("Show Grid"), + () -> this.showGrid, value -> { + this.showGrid = value; + this.computeOffset(); + }, BooleanOption.BooleanType.YES_NO) - .description(Text.of("Shows a grid and Y axis values")) + .description(Component.literal("Shows a grid and Y axis values")) ); - menu.addOption(new DoubleOption(Text.of("Number of Grid Lines"), + menu.addOption(new DoubleOption(Component.literal("Number of Grid Lines"), 1, 25, 1, () -> (double) this.gridLines, value -> { this.setGridLines(value.intValue()); - computeOffset(); + this.computeOffset(); }, menu) .renderWhen(() -> this.showGrid) ); - menu.addOption(new ColorOption(Text.of("Graph Line Color"), + menu.addOption(new DoubleOption(Component.literal("Timeline Points"), + 10, 300, 5, + () -> (double) this.maxDataPoints, value -> { + this.setMaxDataPoints(value.intValue()); + this.computeOffset(); + }, menu).description(Component.literal("Adjusts how many total data points are saved on the visual timeline")) + .withComplexity(Option.Complexity.Enhanced) + ); + menu.addOption(new DoubleOption(Component.literal("Sample Cooldown (ms)"), + 10, 2000, 10, + () -> (double) this.sampleIntervalMs, value -> { + this.setSampleInterval(value.longValue()); + }, menu).description(Component.literal("Sets how often the graph polls for a new data point (in milliseconds)")) + .withComplexity(Option.Complexity.Pro) + ); + menu.addOption(new ColorOption(Component.literal("Graph Line Color"), () -> this.graphColor, value -> this.graphColor = value, menu) - .description(Text.of("Specify the color you want for the graph's lines")) + .description(Component.literal("Specify the color you want for the graph's lines")) ); - menu.addOption(new BooleanOption(Text.of("Rainbow Graph Line Color"), + menu.addOption(new BooleanOption(Component.literal("Rainbow Graph Line Color"), () -> this.graphColorRainbow, value -> this.graphColorRainbow = value) - .description(Text.of("Color your graph line with funny rainbow")) + .description(Component.literal("Color your graph line with funny rainbow")) .withComplexity(Option.Complexity.Pro) ); - menu.addOption(new ColorOption(Text.of("Graph Background Color"), + menu.addOption(new ColorOption(Component.literal("Graph Background Color"), () -> this.backgroundColor, value -> this.backgroundColor = value, menu) - .description(Text.of("Specify the color you want for the graph's background")) + .description(Component.literal("Specify the color you want for the graph's background")) ); - menu.addOption(new DoubleOption(Text.of("Line Thickness"), - 0.5f, 5.0f, 0.1f, + menu.addOption(new DoubleOption(Component.literal("Line Thickness"), + 0.5f, 1f, 0.01f, () -> (double) this.lineThickness, value -> this.lineThickness = value.floatValue(), menu) ); } private void computeOffset(){ - // The first text is usually the largest but a negative value may occupy more width so we check the first and last text. - // Idk how this will break. - if(mc.textRenderer == null) return; + if(!showGrid) { + offset = 0; + return; + } + // The first Component is usually the largest but a negative value may occupy more gWidth so we check the first and last Component. + // Idk how this will break. String firstText = formatValue(maxValue - valueStep); String lastText = formatValue(maxValue - (gridLines * valueStep)); offset = Math.max( - (int) Math.ceil(mc.textRenderer.getWidth(firstText) * this.scale), - (int) Math.ceil(mc.textRenderer.getWidth(lastText) * this.scale) + (int) Math.ceil(mc.font.width(firstText) * this.valueScale), + (int) Math.ceil(mc.font.width(lastText) * this.valueScale) ); } @@ -430,33 +581,15 @@ public void setLabel(String label) { this.label = label; } - @Override - public float getHeight() { - return height; - } - - public void setHeight(float height) { - this.height = height; - } - - @Override - public float getWidth() { - return width; - } - - public void setWidth(float width) { - this.width = width; - } - public int getGridLines() { return gridLines; } public void setGridLines(int gridLines) { this.gridLines = gridLines; - this.stepY = height / (gridLines + 1); + this.stepY = gHeight / (gridLines + 1); this.valueStep = (maxValue - minValue) / (gridLines + 1); - this.scale = (float) MathHelper.clamp((stepY / 9.5), 0.0f, 1.0f); + this.valueScale = (float) Math.clamp((stepY / 9.5), 0.0f, 1.0f); } public boolean isShowGrid() { @@ -490,10 +623,10 @@ public void onClose() { } @Override - public void writeToTag(NbtCompound tag) { + public void writeToTag(CompoundTag tag) { super.writeToTag(tag); - tag.putFloat("width", width); - tag.putFloat("height", height); + tag.putFloat("gWidth", gWidth); + tag.putFloat("gHeight", gHeight); tag.putInt("maxDataPoints", maxDataPoints); tag.putFloat("minValue", minValue); tag.putFloat("maxValue", maxValue); @@ -508,10 +641,10 @@ public void writeToTag(NbtCompound tag) { } @Override - public void readFromTag(NbtCompound tag) { + public void readFromTag(CompoundTag tag) { super.readFromTag(tag); - this.width = tag.getFloat("width").orElse(100f); - this.height = tag.getFloat("height").orElse(50f); + this.gWidth = tag.getFloat("gWidth").orElse(100f); + this.gHeight = tag.getFloat("gHeight").orElse(50f); this.maxDataPoints = tag.getInt("maxDataPoints").orElse(100); this.minValue = tag.getFloat("minValue").orElse(0f); this.maxValue = tag.getFloat("maxValue").orElse(1f); @@ -528,7 +661,6 @@ public void readFromTag(NbtCompound tag) { this.setMaxValue(maxValue); internal_init(); - createMenu(); } @Override @@ -537,9 +669,8 @@ public ContextMenu getContextMenu() { } public static class GraphWidgetBuilder extends DynamicValueWidgetBuilder { - private Anchor anchor = Anchor.CENTER; - private float width = 100; - private float height = 50; + private float gWidth = 100; + private float gHeight = 50; private int maxDataPoints = 50; private float minValue = 0; private float maxValue = 100; @@ -549,6 +680,7 @@ public static class GraphWidgetBuilder extends DynamicValueWidgetBuilder DATA = new WidgetData<>("TextWidget", "Display Text on screen", TextWidget::new); + public static WidgetData DATA = new WidgetData<>("TextWidget", "Display Component on screen", TextWidget::new); private ContextMenu menu; public Color textColor; - protected boolean shadow; // Whether to draw a shadow behind the text - protected boolean rainbow; // Whether to apply a rainbow effect to the text + protected boolean shadow; // Whether to draw a shadow behind the Component + protected boolean rainbow; // Whether to apply a rainbow effect to the Component protected int rainbowSpeed = 2; //Speed of the rainbow effect protected float rainbowSpread = 0.01f, rainbowSaturation = 1.0f, rainbowBrightness = 1.0f; @@ -44,71 +50,112 @@ public TextWidget(String registryID, String registryKey, boolean shadow, boolean } public void createMenu() { - menu = new ContextMenu<>(getX(), getY(),ContextMenuProperties.createGenericSimplified()); + menu = new ContextMenu<>(getX(), getY(),ContextMenuProperties.builder().skin(new ModernSkin()).build()); - menu.addOption(new BooleanOption(Text.of("Shadow"), + // if(IntegrationManager.IS_TEST_MODE) menu.setLayoutEngine(new LayoutEngine(20,20,20,80)); + + menu.addOption(new BooleanOption(Component.literal("Shadow"), () -> this.shadow, value -> this.shadow = value, BooleanOption.BooleanType.ON_OFF) - .description(Text.of("Adds shadow to your text")) + .description(Component.literal("Adds shadow to your Component")) ); - menu.addOption(new BooleanOption(Text.of("Rainbow"), + menu.addOption(new BooleanOption(Component.literal("Rainbow"), () -> this.rainbow, value -> this.rainbow = value, BooleanOption.BooleanType.ON_OFF) - .description(Text.of("Adds rainbow effect to your text")) + .description(Component.literal("Adds rainbow effect to your Component")) ); - menu.addOption(new ColorOption(Text.of("Text Color"), + menu.addOption(new ColorOption(Component.literal("Component Color"), () -> this.textColor, value -> this.textColor = value, menu) - .description(Text.of("Specify the color you want to add to your text")) + .description(Component.literal("Specify the color you want to add to your Component")) .renderWhen(() -> !this.rainbow) ); - menu.addOption(new DoubleOption(Text.of("Rainbow Speed"), - 1, 5.0f, 1, + menu.addOption(new DoubleOption(Component.literal("Rainbow Speed"), + 1, 5, 1, () -> (double) this.rainbowSpeed, value -> this.rainbowSpeed = value.intValue(), menu) .renderWhen(() -> this.rainbow) ); - menu.addOption(new DoubleOption(Text.of("Rainbow Spread"), + menu.addOption(new DoubleOption(Component.literal("Rainbow Spread"), 0.001f, 0.15f, 0.001f, () -> (double) this.rainbowSpread, value -> this.rainbowSpread = value.floatValue(), menu) .renderWhen(() -> this.rainbow) .withComplexity(Option.Complexity.Enhanced) ); - menu.addOption(new DoubleOption(Text.of("Rainbow Saturation"), + menu.addOption(new DoubleOption(Component.literal("Rainbow Saturation"), 0, 1.0f, 0.1f, () -> (double) this.rainbowSaturation, value -> this.rainbowSaturation = value.floatValue(), menu) .renderWhen(() -> this.rainbow) .withComplexity(Option.Complexity.Pro) ); - menu.addOption(new DoubleOption(Text.of("Rainbow Brightness"), + menu.addOption(new DoubleOption(Component.literal("Rainbow Brightness"), 0, 1.0f, 0.01f, () -> (double) this.rainbowBrightness, value -> this.rainbowBrightness = value.floatValue(), menu) .renderWhen(() -> this.rainbow) .withComplexity(Option.Complexity.Pro) ); + if(IntegrationManager.IS_TEST_MODE) { + // Runnable Option + AtomicBoolean ran = new AtomicBoolean(false); + menu.addOption(new RunnableOption(Component.literal("Reset Position"), + ran::get, ran::set, + () -> this.setPosition(0, 0)) + .description(Component.literal("Reset widget to default position"))); + + AtomicReference style = new AtomicReference<>("Style1"); + AtomicReference align = new AtomicReference<>(GroupLayout.Alignment.CENTER); + + // List Option + List styles = Arrays.asList("Style1", "Style2", "Style3"); + menu.addOption(new CycleOption<>(Component.literal("Text Style"), style::get, style::set, styles)); + + // Enum Option + menu.addOption(new CycleOption<>(Component.literal("Alignment"), align::get, align::set, GroupLayout.Alignment.values())); + + + // Option Group + OptionGroup group = new OptionGroup(Component.literal("Display Options")); + group.addOption(new BooleanOption(Component.literal("Bold Text"), + () -> false, value -> { + }, BooleanOption.BooleanType.YES_NO) + .description(Component.literal("Enable bold text"))); + group.addOption(new DoubleOption(Component.literal("Font Size"), + 8.0, 24.0, 1.0f, + () -> 12.0, value -> { + }, menu).description(Component.literal("Adjust font size"))); + + menu.addOption(group); + + // SubMenu Option + SubMenuOption subMenu = (SubMenuOption) new SubMenuOption(Component.literal("Advanced Settings"), menu) + .description(Component.literal("Open advanced settings")); + subMenu.getSubMenu().addOption(new BooleanOption(Component.literal("Some Boolean"), + () -> false, value -> { + }, BooleanOption.BooleanType.TRUE_FALSE) + .description(Component.literal("True/False"))); + menu.addOption(subMenu); + } } @Override - public void renderWidget(DrawContext drawContext, int mouseX, int mouseY) { + public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY) { if (menu == null) return; //int color = rainbow ? ColorHelper.getColorFromHue((System.currentTimeMillis() % (5000 * rainbowSpeed) / (5000f * rainbowSpeed))) : textColor.getRGB(); int color = textColor.getRGB(); if (valueSupplier != null) { - String text = getValue(); + String Component = getValue(); if (rainbow) { - DrawHelper.drawChromaText(drawContext, text, getX() + 2, getY() + 2, rainbowSpeed / 2f, rainbowSaturation, rainbowBrightness, rainbowSpread, shadow); + DrawHelper.drawChromaText(graphics, Component, getX() + 2, getY() + 2, rainbowSpeed / 2f, rainbowSaturation, rainbowBrightness, rainbowSpread, shadow); } else { - drawContext.drawText(mc.textRenderer, text, getX() + 2, getY() + 2, color, shadow); + graphics.drawString(mc.font, Component, getX() + 2, getY() + 2, color, shadow); } - drawContext.draw(); - widgetBox.setDimensions(getX(), getY(), mc.textRenderer.getWidth(text) + 3, mc.textRenderer.fontHeight + 2, this.shouldScale, GlobalConfig.get().getScale()); + widgetBox.setDimensions(getX(), getY(), mc.font.width(Component) + 3, mc.font.lineHeight + 2, this.canScale); } - menu.set(getX(), getY(), (int) Math.ceil(getHeight())); + menu.set(getX(), getY(), (int) Math.ceil(getHeight())); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - menu.toggleDisplay(widgetBox, mouseX, mouseY, button); - return super.mouseClicked(mouseX, mouseY, button); + return menu.toggleDisplay(widgetBox, mouseX, mouseY, button) || super.mouseClicked(mouseX, mouseY, button); } @Override @@ -118,7 +165,7 @@ public void onClose() { } @Override - public void writeToTag(NbtCompound tag) { + public void writeToTag(CompoundTag tag) { super.writeToTag(tag); tag.putBoolean("Shadow", shadow); tag.putBoolean("Rainbow", rainbow); @@ -130,7 +177,7 @@ public void writeToTag(NbtCompound tag) { } @Override - public void readFromTag(NbtCompound tag) { + public void readFromTag(CompoundTag tag) { super.readFromTag(tag); shadow = tag.getBoolean("Shadow").orElse(false); rainbow = tag.getBoolean("Rainbow").orElse(false); @@ -139,8 +186,6 @@ public void readFromTag(NbtCompound tag) { rainbowSaturation = tag.getFloat("RainbowSaturation").orElse(1.0f); rainbowBrightness = tag.getFloat("RainbowBrightness").orElse(1.0f); textColor = new Color(tag.getInt("TextColor").orElse(0xFFFFFFFF), true); // default white - registryKey = tag.getString("RegistryKey").orElse("default:key"); - registryID = tag.getString("RegistryID").orElse("default:id"); //createMenu(); } @@ -187,7 +232,7 @@ public TextWidget build() { widget.setPosition(x, y); widget.setDraggable(isDraggable); - widget.setShouldScale(shouldScale); + widget.setCanScale(shouldScale); return widget; } } diff --git a/src/main/resources/dynamichud.mixins.json b/src/main/resources/dynamichud.mixins.json index f60e286..caa9633 100644 --- a/src/main/resources/dynamichud.mixins.json +++ b/src/main/resources/dynamichud.mixins.json @@ -1,6 +1,5 @@ { "required": true, - "minVersion": "0.8", "package": "com.tanishisherewith.dynamichud.mixins", "compatibilityLevel": "JAVA_21", "mixins": [ @@ -10,7 +9,11 @@ "defaultRequire": 1 }, "client": [ - "OptionsScreenMixin", - "RenderLayerMixin" - ] + "BufferBuilderMixin", + "MinecraftMixin", + "OptionsScreenMixin" + ], + "overwrites": { + "requireAnnotations": true + } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index a6ef3cf..a416a25 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,7 +26,10 @@ ] }, "mixins": [ - "dynamichud.mixins.json" + { + "config": "dynamichud.mixins.json", + "environment": "client" + } ], "depends": { "fabricloader": ">=${loader_version}",