diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1f405ce..2c89f9c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -35,7 +35,7 @@ jobs: - name: capture build artifacts if: ${{ runner.os == 'Linux' && matrix.java == '21' }} # Only upload artifacts built from the latest java on one OS - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Dynamichud path: build/libs/ diff --git a/.gitignore b/.gitignore index 3c37caf..12044e6 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,5 @@ run/ # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar + +*.profileconfig.json diff --git a/build.gradle b/build.gradle index 782b183..ac1ac6e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.7-SNAPSHOT' + id 'fabric-loom' version '1.9-SNAPSHOT' id 'maven-publish' } @@ -34,8 +34,6 @@ dependencies { modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "dev.isxander:yet-another-config-lib:${project.yacl_version}" - - modApi "com.terraformersmc:modmenu:11.0.1" } processResources { diff --git a/gradle.properties b/gradle.properties index da95953..df1ba47 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,15 +4,16 @@ org.gradle.parallel=true # Fabric Properties # check these on https://fabricmc.net/develop -minecraft_version=1.21 -yarn_mappings=1.21+build.2 -loader_version=0.15.11 +minecraft_version=1.21.4 +yarn_mappings=1.21.4+build.1 +loader_version=0.16.9 # Mod Properties -mod_version = 2.1.0 +# need versioning system +mod_version = 3.0.0 maven_group = com.tanishisherewith archives_base_name = dynamichud # Dependencies -fabric_version=0.100.3+1.21 -yacl_version=3.5.0+1.21-fabric \ No newline at end of file +fabric_version=0.111.0+1.21.4 +yacl_version=3.6.6+1.21.4-fabric \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2617362..4eaec46 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java b/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java index 2f2f4be..ef01878 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java +++ b/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java @@ -1,52 +1,22 @@ package com.tanishisherewith.dynamichud; import com.tanishisherewith.dynamichud.config.GlobalConfig; -import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; -import com.tanishisherewith.dynamichud.utils.BooleanPool; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.WidgetManager; -import com.tanishisherewith.dynamichud.widget.WidgetRenderer; -import com.tanishisherewith.dynamichud.widgets.TextWidget; +import com.tanishisherewith.dynamichud.integration.IntegrationManager; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +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.rendering.v1.HudRenderCallback; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.metadata.ModMetadata; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.option.KeyBinding; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Objects; - +@Environment(EnvType.CLIENT) public class DynamicHUD implements ClientModInitializer { - /** - * This is a map to store the list of widgets for each widget file to be saved. - *

- * Allows saving widgets across different mods with same save file name. - */ - public static final HashMap> FILE_MAP = new HashMap<>(); - public static final Logger logger = LoggerFactory.getLogger("DynamicHud"); - private static final List widgetRenderers = new ArrayList<>(); public static MinecraftClient MC = MinecraftClient.getInstance(); + public static final Logger logger = LoggerFactory.getLogger("DynamicHud"); public static String MOD_ID = "dynamichud"; - public static void addWidgetRenderer(WidgetRenderer widgetRenderer) { - widgetRenderers.add(widgetRenderer); - } - - public static List getWidgetRenderers() { - return widgetRenderers; - } - public static void printInfo(String msg) { logger.info(msg); } @@ -55,133 +25,16 @@ public static void printWarn(String msg) { logger.warn(msg); } - /** - * Opens the MovableScreen when the specified key is pressed. - * - * @param key The key to listen for - * @param screen The AbstractMoveableScreen instance to use to set the screen - */ - public static void openDynamicScreen(KeyBinding key, AbstractMoveableScreen screen) { - if (key.wasPressed()) { - MC.setScreen(screen); - } - } - @Override public void onInitializeClient() { - printInfo("Initialising DynamicHud"); - - // Add WidgetData of included widgets - WidgetManager.registerCustomWidgets( - TextWidget.DATA - ); + printInfo("Initialising DynamicHUD"); //YACL load GlobalConfig.HANDLER.load(); - printInfo("Integrating mods..."); - FabricLoader.getInstance() - .getEntrypointContainers("dynamicHud", DynamicHudIntegration.class) - .forEach(entrypoint -> { - ModMetadata metadata = entrypoint.getProvider().getMetadata(); - String modId = metadata.getId(); - - printInfo(String.format("Supported mod with id %s was found!", modId)); - - AbstractMoveableScreen screen; - KeyBinding binding; - WidgetRenderer widgetRenderer; - File widgetsFile; - try { - DynamicHudIntegration DHIntegration = entrypoint.getEntrypoint(); - - //Calls the init method - DHIntegration.init(); - - //Gets the widget file to save and load the widgets from - widgetsFile = DHIntegration.getWidgetsFile(); - - // Adds / loads widgets from file - if (widgetsFile.exists()) { - WidgetManager.loadWidgets(widgetsFile); - } else { - DHIntegration.addWidgets(); - } - - //Calls the second init method - DHIntegration.initAfter(); - - // Get the instance of AbstractMoveableScreen - screen = Objects.requireNonNull( DHIntegration.getMovableScreen()); - - // Get the keybind to open the screen instance - binding = DHIntegration.getKeyBind(); - - //Register custom widget datas by WidgetManager.registerCustomWidgets(); - DHIntegration.registerCustomWidgets(); - - //WidgetRenderer with widgets instance - widgetRenderer = DHIntegration.getWidgetRenderer(); - addWidgetRenderer(widgetRenderer); - - List widgets = FILE_MAP.get(widgetsFile.getName()); - - if (widgets == null || widgets.isEmpty()) { - FILE_MAP.put(widgetsFile.getName(), widgetRenderer.getWidgets()); - } else { - widgets.addAll(widgetRenderer.getWidgets()); - FILE_MAP.put(widgetsFile.getName(), widgets); - } - - //Register events for rendering, saving, loading, and opening the hudEditor - ClientTickEvents.START_CLIENT_TICK.register((client) -> openDynamicScreen(binding, screen)); - - /* === Saving === */ - - //When a player exits a world (SinglePlayer worlds) or a server stops - ServerLifecycleEvents.SERVER_STOPPING.register(server -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); - - // When a resource pack is reloaded. - ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, s) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); - - //When player disconnects - ServerPlayConnectionEvents.DISCONNECT.register((handler, packetSender) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); - - //When minecraft closes - ClientLifecycleEvents.CLIENT_STOPPING.register((minecraftClient) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); - - printInfo(String.format("Integration of mod %s was successful", modId)); - } catch (Throwable e) { - if (e instanceof IOException) { - logger.warn("An error has occurred while loading widgets of mod {}", modId, e); - } else { - logger.warn("Mod {} has improper implementation of DynamicHUD", modId, e); - } - } - }); - printInfo("(DynamicHUD) Integration of mods found was successful"); - - - //Global config saving (YACL) - ServerLifecycleEvents.SERVER_STOPPING.register(server -> GlobalConfig.HANDLER.save()); - ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, s) -> GlobalConfig.HANDLER.save()); - ServerPlayConnectionEvents.DISCONNECT.register((handler, packetSender) -> GlobalConfig.HANDLER.save()); - ClientLifecycleEvents.CLIENT_STOPPING.register((minecraftClient) -> { - GlobalConfig.HANDLER.save(); - }); - + IntegrationManager.integrate(); //In game screen render. HudRenderCallback.EVENT.register(new HudRender()); } - - private void saveWidgetsSafely(File widgetsFile, List widgets) { - try { - WidgetManager.saveWidgets(widgetsFile, widgets); - } catch (IOException e) { - logger.error("Failed to save widgets. Widgets passed: {}", widgets); - throw new RuntimeException(e); - } - } - } diff --git a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudTest.java b/src/main/java/com/tanishisherewith/dynamichud/DynamicHudTest.java deleted file mode 100644 index 0883606..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.tanishisherewith.dynamichud; - -import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; -import com.tanishisherewith.dynamichud.utils.DynamicValueRegistry; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.WidgetManager; -import com.tanishisherewith.dynamichud.widget.WidgetRenderer; -import com.tanishisherewith.dynamichud.widgets.TextWidget; -import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.text.Text; - -import java.util.List; - -public class DynamicHudTest implements DynamicHudIntegration { - TextWidget FPSWidget; - TextWidget HelloWidget; - TextWidget DynamicHUDWidget; - DynamicValueRegistry registry; - WidgetRenderer renderer; - - @Override - public void init() { - //Global registry - DynamicValueRegistry.registerGlobal("FPS", () -> "FPS: " + DynamicHUD.MC.getCurrentFps()); - - //Local registry - registry = new DynamicValueRegistry(DynamicHUD.MOD_ID); - registry.registerLocal("Hello", () -> "Hello " + DynamicHUD.MC.getSession().getUsername() + "!"); - registry.registerLocal("DynamicHUD", () -> "DynamicHUD"); - - - FPSWidget = new TextWidget.Builder() - .setX(250) - .setY(100) - .setDraggable(true) - .rainbow(false) - .setDRKey("FPS") - .setModID(DynamicHUD.MOD_ID) - .shouldScale(false) - .build(); - - HelloWidget = new TextWidget.Builder() - .setX(200) - .setY(100) - .setDraggable(true) - .rainbow(false) - .setDRKey("Hello") - .setDVR(registry) - .setModID(DynamicHUD.MOD_ID) - .shouldScale(true) - .build(); - - DynamicHUDWidget = new TextWidget.Builder() - .setX(5) - .setY(5) - .setDraggable(false) - .rainbow(true) - .setDRKey("DynamicHUD") - .setDVR(registry) - .setModID(DynamicHUD.MOD_ID) - .shouldScale(true) - .build(); - - } - - @Override - public void addWidgets() { - WidgetManager.addWidget(FPSWidget); - WidgetManager.addWidget(HelloWidget); - WidgetManager.addWidget(DynamicHUDWidget); - } - - @Override - public void registerCustomWidgets() { - //WidgetManager.addWidgetData(MyWidget.DATA); - } - - public void initAfter() { - List widgets = WidgetManager.getWidgetsForMod(DynamicHUD.MOD_ID); - - renderer = new WidgetRenderer(widgets); - renderer.shouldRenderInGameHud(true); - - //This will make widgets render in the titlescreen as well. - renderer.addScreen(TitleScreen.class); - } - - @Override - public AbstractMoveableScreen getMovableScreen() { - return new AbstractMoveableScreen(Text.literal("Editor Screen"), renderer) { - }; - } - - @Override - public WidgetRenderer getWidgetRenderer() { - return renderer; - } - -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/HudRender.java b/src/main/java/com/tanishisherewith/dynamichud/HudRender.java index a7832cf..63ae883 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/HudRender.java +++ b/src/main/java/com/tanishisherewith/dynamichud/HudRender.java @@ -1,5 +1,6 @@ package com.tanishisherewith.dynamichud; +import com.tanishisherewith.dynamichud.integration.IntegrationManager; import com.tanishisherewith.dynamichud.widget.WidgetRenderer; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; import net.minecraft.client.gui.DrawContext; @@ -9,11 +10,11 @@ * Using the fabric event {@link HudRenderCallback} 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 HudRenderCallback{ +public class HudRender implements HudRenderCallback { @Override public void onHudRender(DrawContext drawContext, RenderTickCounter tickCounter) { - for (WidgetRenderer widgetRenderer : DynamicHUD.getWidgetRenderers()) { + for (WidgetRenderer widgetRenderer : IntegrationManager.getWidgetRenderers()) { widgetRenderer.renderWidgets(drawContext, -120, -120); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java b/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java new file mode 100644 index 0000000..e6c010c --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java @@ -0,0 +1,111 @@ +package com.tanishisherewith.dynamichud; + +import com.tanishisherewith.dynamichud.integration.DynamicHudConfigurator; +import com.tanishisherewith.dynamichud.integration.DynamicHudIntegration; +import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; +import com.tanishisherewith.dynamichud.utils.DynamicValueRegistry; +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 java.awt.*; + +public class IntegrationTest implements DynamicHudIntegration { + TextWidget FPSWidget; + TextWidget HelloWidget; + TextWidget DynamicHUDWidget; + GraphWidget graphWidget; + DynamicValueRegistry registry; + + @Override + 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()); + + //Local registry + registry = new DynamicValueRegistry(DynamicHUD.MOD_ID); + registry.registerLocal("Hello", () -> "Hello " + DynamicHUD.MC.getSession().getUsername() + "!"); + registry.registerLocal("DynamicHUD", () -> "DynamicHUD"); + registry.registerLocal("FPS", () -> DynamicHUD.MC.getCurrentFps()); + + FPSWidget = new TextWidget.Builder() + .setX(250) + .setY(150) + .setDraggable(true) + .rainbow(false) + .registryKey("dynamichud:FPS") + .setModID(DynamicHUD.MOD_ID) + .shouldScale(false) + .build(); + + HelloWidget = new TextWidget.Builder() + .setX(200) + .setY(100) + .setDraggable(true) + .rainbow(false) + .registryKey("Hello") + .registryID(registry.getId()) + .setModID(DynamicHUD.MOD_ID) + .shouldScale(true) + .shadow(true) + .build(); + + DynamicHUDWidget = new TextWidget.Builder() + .setX(0) + .setY(0) + .setDraggable(false) + .rainbow(true) + .registryKey("DynamicHUD") + .registryID(registry.getId()) + .setModID(DynamicHUD.MOD_ID) + .shouldScale(true) + .build(); + + graphWidget = new GraphWidget.GraphWidgetBuilder() + .setX(250) + .setY(100) + .label("FPS Chart") + .graphColor(Color.CYAN) + .anchor(Widget.Anchor.CENTER) + .height(100) + .width(150) + .gridLines(10) + .backgroundColor(Color.DARK_GRAY) + .lineThickness(1f) + .maxDataPoints(100) + .maxValue(120) + .minValue(30) + .setModID(DynamicHUD.MOD_ID) + .setDraggable(true) + .setDisplay(true) + .showGrid(true) + .registryKey("FPS") + .registryID(registry.getId()) + .build() + .autoUpdateRange(); + } + + @Override + public DynamicHudConfigurator configure(DynamicHudConfigurator configurator) { + configurator.addWidget(FPSWidget) + .addWidget(HelloWidget) + .addWidget(DynamicHUDWidget) + .addWidget(graphWidget) + .configureRenderer(renderer -> { + //Already true by default + //renderer.shouldRenderInGameHud(true); + renderer.addScreen(TitleScreen.class); + }) + .withMoveableScreen(config -> new AbstractMoveableScreen(Text.literal("Editor Screen"), config.getRenderer()) {}); + + return configurator; + } + + @Override + public void registerCustomWidgets() { + //WidgetManager.addWidgetData(MyWidget.DATA); + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java b/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java deleted file mode 100644 index 30fffb4..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.tanishisherewith.dynamichud; - -import com.tanishisherewith.dynamichud.config.GlobalConfig; -import com.terraformersmc.modmenu.api.ConfigScreenFactory; -import com.terraformersmc.modmenu.api.ModMenuApi; -import net.minecraft.client.gui.screen.Screen; - - -public class ModMenuIntegration implements ModMenuApi { - public static Screen YACL_CONFIG_SCREEN = GlobalConfig.get().createYACLGUI(); - - @Override - public ConfigScreenFactory getModConfigScreenFactory() { - return parent -> YACL_CONFIG_SCREEN; - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java b/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java index d8b5690..e832380 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java +++ b/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java @@ -1,16 +1,18 @@ package com.tanishisherewith.dynamichud.config; import dev.isxander.yacl3.api.*; -import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; -import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; +import dev.isxander.yacl3.api.controller.*; import dev.isxander.yacl3.config.v2.api.ConfigClassHandler; 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 java.awt.*; + public final class GlobalConfig { public static final ConfigClassHandler HANDLER = ConfigClassHandler.createBuilder(GlobalConfig.class) .id(Identifier.of("dynamichud", "dynamichud_config")) @@ -19,9 +21,10 @@ public final class GlobalConfig { .setJson5(true) .build()) .build(); + private static final GlobalConfig INSTANCE = new GlobalConfig(); /** - * Common scale for all widgets. Set by the user using YACL. + * Common scale for all widgets. */ @SerialEntry private float scale = 1.0f; @@ -32,11 +35,30 @@ public final class GlobalConfig { @SerialEntry private boolean showColorPickerPreview = true; + @SerialEntry + private boolean renderInDebugScreen = false; + + @SerialEntry + private final boolean forceSameContextMenuSkin = true; + + //These package names are getting seriously long + @SerialEntry + private com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity complexity = com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity.Simple; + + @SerialEntry + private int snapSize = 100; + + @SerialEntry + private Color hudActiveColor = new Color(0, 0, 0, 128); + + @SerialEntry + private Color hudInactiveColor = new Color(255, 0, 0, 128); + public static GlobalConfig get() { return INSTANCE; } - public final Screen createYACLGUI() { + public Screen createYACLGUI() { return YetAnotherConfigLib.createBuilder() .title(Text.literal("DynamicHUD config screen.")) .category(ConfigCategory.createBuilder() @@ -51,6 +73,12 @@ public final Screen createYACLGUI() { .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) + .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."))) @@ -63,12 +91,41 @@ public final Screen createYACLGUI() { .binding(true, () -> this.displayDescriptions, newVal -> this.displayDescriptions = 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"))) + .binding(100, () -> this.snapSize, newVal -> this.snapSize = newVal) + .controller(integerOption -> IntegerFieldControllerBuilder.create(integerOption).range(10, 500)) + .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"))) + .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"))) + .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"))) + .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())) + ) .build()) .build()) + .save(HANDLER::save) .build() - .generateScreen(null); + .generateScreen(MinecraftClient.getInstance().currentScreen); } - public float getScale(){ + + public float getScale() { return scale; } @@ -79,4 +136,24 @@ public boolean showColorPickerPreview() { public boolean shouldDisplayDescriptions() { return displayDescriptions; } + + public boolean renderInDebugScreen() { + return renderInDebugScreen; + } + + public int getSnapSize() { + return snapSize; + } + + public Color getHudInactiveColor() { + return hudInactiveColor; + } + + public Color getHudActiveColor() { + return hudActiveColor; + } + + 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 1faa386..a564ad9 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java @@ -1,8 +1,14 @@ package com.tanishisherewith.dynamichud.helpers; +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.opengl.GL11; +import org.lwjgl.opengl.GL30; import java.awt.*; +import java.nio.ByteBuffer; /** * This class provides helper methods for working with colors. @@ -111,6 +117,38 @@ public static float[] getRainbowColor() { return rainbow; } + /** + * @param color Target color. + * @return Alpha of the color. + */ + public static float getAlpha(int color) { + return (float) (color >> 24 & 255) / 255.0F; + } + + /** + * @param color Target color. + * @return Red value of the color. + */ + public static float getRed(int color) { + return (float) (color >> 16 & 255) / 255.0F; + } + + /** + * @param color Target color. + * @return Green value of the color. + */ + public static float getGreen(int color) { + return (float) (color >> 8 & 255) / 255.0F; + } + + /** + * @param color Target color. + * @return Blue value of the color. + */ + public static float getBlue(int color) { + return (float) (color & 255) / 255.0F; + } + /** * Rainbow color with custom speed. * @@ -136,6 +174,49 @@ public static Color changeAlpha(Color color, int alpha) { return new Color(0); } + public static int[] getMousePixelColor(double mouseX, double mouseY) { + MinecraftClient client = MinecraftClient.getInstance(); + Framebuffer framebuffer = client.getFramebuffer(); + Window window = client.getWindow(); + + // Get the window and framebuffer dimensions + int windowWidth = window.getWidth(); + int windowHeight = window.getHeight(); + int framebufferWidth = framebuffer.textureWidth; + int framebufferHeight = framebuffer.textureHeight; + + // Calculate scaling factors + double scaleX = (double) framebufferWidth / windowWidth; + double scaleY = (double) framebufferHeight / windowHeight; + + // Convert mouse coordinates to framebuffer coordinates + int x = (int) (mouseX * scaleX); + int y = (int) ((windowHeight - mouseY) * scaleY); + + // Ensure the coordinates are within the framebuffer bounds + if (x < 0 || x >= framebufferWidth || y < 0 || y >= framebufferHeight) { + System.err.println("Mouse coordinates are out of bounds"); + return null; + } + + // Allocate a buffer to store the pixel data + ByteBuffer buffer = ByteBuffer.allocateDirect(4); // 4 bytes for RGBA + + // Bind the framebuffer for reading + GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebuffer.fbo); + + // Read the pixel at the mouse position + GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); + + // Extract the color components from the buffer + int red = buffer.get(0) & 0xFF; + int green = buffer.get(1) & 0xFF; + int blue = buffer.get(2) & 0xFF; + int alpha = buffer.get(3) & 0xFF; + + return new int[]{red, green, blue, alpha}; + } + public static int fromRGBA(int r, int g, int b, int a) { return (r << 16) + (g << 8) + (b) + (a << 24); } diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java index c310b07..bbf0b4a 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java @@ -1,19 +1,30 @@ package com.tanishisherewith.dynamichud.helpers; import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.ProjectionType; import com.mojang.blaze3d.systems.RenderSystem; import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.widget.WidgetBox; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gl.ShaderProgramKeys; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.render.*; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.NotNull; import org.joml.Matrix4f; +import org.lwjgl.opengl.GL40; import org.lwjgl.opengl.GL40C; import java.awt.*; +import java.util.Objects; + +import static com.tanishisherewith.dynamichud.helpers.TextureHelper.mc; /** - * Credits: HeliosClient + * Credits: HeliosClient */ public class DrawHelper { @@ -46,8 +57,8 @@ public static void drawGradient(Matrix4f matrix4f, float x, float y, float width RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); - + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); + switch (direction) { case LEFT_RIGHT: bufferBuilder.vertex(matrix4f, x, y + height, 0.0F).color(startRed, startGreen, startBlue, startAlpha); @@ -81,8 +92,14 @@ public static void drawGradient(Matrix4f matrix4f, float x, float y, float width } public static void enableScissor(int x, int y, int width, int height) { - double scaleFactor = DynamicHUD.MC.getWindow().getScaleFactor(); + 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) { int scissorX = (int) (x * scaleFactor); int scissorY = (int) (DynamicHUD.MC.getWindow().getHeight() - ((y + height) * scaleFactor)); int scissorWidth = (int) (width * scaleFactor); @@ -117,7 +134,7 @@ public static void drawRectangle(Matrix4f matrix4f, float x, float y, float widt RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); tessellator.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); @@ -204,7 +221,7 @@ public static void drawRainbowGradientRectangle(Matrix4f matrix4f, float x, floa RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); for (int i = 0; i <= width; i++) { float hue = (float) i / width; @@ -239,14 +256,43 @@ public static void drawRainbowGradientRectangle(Matrix4f matrix4f, float x, floa RenderSystem.disableBlend(); } + /** + * Draw chroma text (text 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 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. + */ + public static void drawChromaText(@NotNull DrawContext drawContext, String text, int x, int y, float speed, float saturation, float brightness, float spread, boolean shadow) { + long time = System.currentTimeMillis(); + int length = text.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 + 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); + } + } + - public static void drawRainbowGradient(Matrix4f matrix, float x, float y, float width, float height) { + public static void drawRainbowGradient(float x, float y, float width, float height) { Matrix4f matrix4f = RenderSystem.getModelViewMatrix(); RenderSystem.enableBlend(); RenderSystem.colorMask(false, false, false, true); RenderSystem.clearColor(0.0F, 0.0F, 0.0F, 0.0F); - RenderSystem.clear(GL40C.GL_COLOR_BUFFER_BIT, false); + RenderSystem.clear(GL40C.GL_COLOR_BUFFER_BIT); RenderSystem.colorMask(true, true, true, true); drawRectangle(matrix4f, x, y, width, height, Color.BLACK.getRGB()); @@ -294,7 +340,7 @@ public static void drawOutlineCircle(Matrix4f matrix4f, float xCenter, float yCe Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); for (int i = 0; i <= 360; i++) { double x = xCenter + Math.sin(Math.toRadians(i)) * radius; @@ -327,7 +373,7 @@ public static void drawFilledCircle(Matrix4f matrix4f, float xCenter, float yCen Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); @@ -387,7 +433,7 @@ public static void drawFilledArc(Matrix4f matrix4f, float x, float y, float radi Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); RenderSystem.enableBlend(); for (float angle = startAngle; angle <= endAngle; angle += 1.0F) { @@ -430,7 +476,7 @@ public static void drawFilledGradientQuadrant(Matrix4f matrix4f, float xCenter, Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); RenderSystem.enableBlend(); bufferBuilder.vertex(matrix4f, xCenter, yCenter, 0).color(startRed, startGreen, startBlue, startAlpha); @@ -473,7 +519,7 @@ public static void drawArc(Matrix4f matrix4f, float xCenter, float yCenter, floa Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); RenderSystem.enableBlend(); for (int i = startAngle; i <= endAngle; i++) { @@ -508,9 +554,9 @@ public static void drawFilledQuadrant(Matrix4f matrix4f, float xCenter, float yC float alpha = (float) (color >> 24 & 255) / 255.0F; Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); + BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); RenderSystem.enableBlend(); bufferBuilder.vertex(matrix4f, xCenter, yCenter, 0).color(red, green, blue, alpha); @@ -743,7 +789,7 @@ public static void drawRoundedGradientRectangle(Matrix4f matrix, Color color1, C RenderSystem.enableBlend(); RenderSystem.colorMask(false, false, false, true); RenderSystem.clearColor(0.0F, 0.0F, 0.0F, 0.0F); - RenderSystem.clear(GL40C.GL_COLOR_BUFFER_BIT, false); + RenderSystem.clear(GL40C.GL_COLOR_BUFFER_BIT); RenderSystem.colorMask(true, true, true, true); drawRoundedRectangle(matrix, x, y, TL, TR, BL, BR, width, height, (int) radius, color1.getRGB()); @@ -792,6 +838,18 @@ public static void drawOutlinedBox(DrawContext drawContext, int x1, int y1, int 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 customScaledProjection(float scale) { + RenderSystem.setProjectionMatrix(new Matrix4f().setOrtho(0, mc.getWindow().getFramebufferWidth() / scale, mc.getWindow().getFramebufferHeight() / scale, 0, 1000, 21000), ProjectionType.ORTHOGRAPHIC); + } + /** * This method assumes that the x, y coords are the origin of a widget. * @@ -836,6 +894,32 @@ public static void stopScaling(MatrixStack matrices) { matrices.pop(); // 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); + int var10000 = startY + endY; + Objects.requireNonNull(textRenderer); + 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 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(); + } else { + l = MathHelper.clamp(centerX, startX + i / 2, endX - i / 2); + context.drawCenteredTextWithShadow(textRenderer, text, l, j, color); + } + + } + 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/TextureHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java index 8e8bcdd..48b5779 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java @@ -14,7 +14,7 @@ public class TextureHelper { static MinecraftClient mc = MinecraftClient.getInstance(); public static NativeImage loadTexture(Identifier textureId) { - if(mc.getResourceManager().getResource(textureId).isPresent()) { + if (mc.getResourceManager().getResource(textureId).isPresent()) { try (InputStream inputStream = mc.getResourceManager().getResource(textureId).get().getInputStream()) { return NativeImage.read(inputStream); } catch (IOException e) { @@ -24,7 +24,7 @@ public static NativeImage loadTexture(Identifier textureId) { return null; } - public static NativeImage resizeTexture(NativeImage image, int newWidth, int newHeight) { + public static NativeImage resizeTexture(NativeImage image, int newWidth, int newHeight) { NativeImage result = new NativeImage(newWidth, newHeight, false); int oldWidth = image.getWidth(); @@ -35,62 +35,64 @@ public static NativeImage resizeTexture(NativeImage image, int newWidth, int ne int srcX = x * oldWidth / newWidth; int srcY = y * oldHeight / newHeight; - result.setColor(x, y, image.getColor(srcX, srcY)); + result.setColorArgb(x, y, image.getColorArgb(srcX, srcY)); } } return result; } + public static NativeImage resizeTextureUsingBilinearInterpolation(NativeImage image, int newWidth, int newHeight) { NativeImage result = new NativeImage(newWidth, newHeight, false); - float x_ratio = ((float)(image.getWidth()-1))/newWidth; - float y_ratio = ((float)(image.getHeight()-1))/newHeight; + float x_ratio = ((float) (image.getWidth() - 1)) / newWidth; + float y_ratio = ((float) (image.getHeight() - 1)) / newHeight; float x_diff, y_diff, blue, red, green; - int offset, a, b, c, d, index; + int a, b, c, d; - for (int i=0;i>8)&0xff)*(1-x_diff)*(1-y_diff) + ((b>>8)&0xff)*(x_diff)*(1-y_diff) + - ((c>>8)&0xff)*(y_diff)*(1-x_diff) + ((d>>8)&0xff)*(x_diff*y_diff); + green = ((a >> 8) & 0xff) * (1 - x_diff) * (1 - y_diff) + ((b >> 8) & 0xff) * (x_diff) * (1 - y_diff) + + ((c >> 8) & 0xff) * (y_diff) * (1 - x_diff) + ((d >> 8) & 0xff) * (x_diff * y_diff); // Red element - 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); + 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.setColor(j, i, - ((((int)red)<<16)&0xff0000) | - ((((int)green)<<8)&0xff00) | - ((int)blue)&0xff); + result.setColorArgb(j, i, + ((((int) red) << 16) & 0xff0000) | + ((((int) green) << 8) & 0xff00) | + ((int) blue) & 0xff); } } return result; } - public static NativeImage invertTexture(NativeImage image) { + + public static NativeImage invertTexture(NativeImage image) { int width = image.getWidth(); int height = image.getHeight(); NativeImage result = new NativeImage(width, height, false); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - int argb = image.getColor(x, y); + int argb = image.getColorArgb(x, y); int alpha = (argb >> 24) & 0xFF; int red = 255 - ((argb >> 16) & 0xFF); @@ -99,7 +101,7 @@ public static NativeImage invertTexture(NativeImage image) { int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; - result.setColor(x, y, newArgb); + result.setColorArgb(x, y, newArgb); } } @@ -117,11 +119,11 @@ public static NativeImage rotateTexture(NativeImage image, int degrees) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - int newX = (int)((x - centerX) * Math.cos(angle) - (y - centerY) * Math.sin(angle) + centerX); - int newY = (int)((x - centerX) * Math.sin(angle) + (y - centerY) * Math.cos(angle) + centerY); + int newX = (int) ((x - centerX) * Math.cos(angle) - (y - centerY) * Math.sin(angle) + centerX); + int newY = (int) ((x - centerX) * Math.sin(angle) + (y - centerY) * Math.cos(angle) + centerY); if (newX >= 0 && newX < width && newY >= 0 && newY < height) { - result.setColor(newY, newX, image.getColor(x, y)); + result.setColorArgb(newY, newX, image.getColorArgb(x, y)); } } } @@ -136,7 +138,7 @@ private static NativeImage flipTextureHorizontally(NativeImage image) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - result.setColor(width - x - 1, y, image.getColor(x, y)); + result.setColorArgb(width - x - 1, y, image.getColorArgb(x, y)); } } @@ -150,7 +152,7 @@ private static NativeImage flipTextureVertically(NativeImage image) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - result.setColor(x, height - y - 1, image.getColor(x, y)); + result.setColorArgb(x, height - y - 1, image.getColorArgb(x, y)); } } @@ -172,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.getColor(x, y); + int argb = image.getColorArgb(x, y); int alpha = (argb >> 24) & 0xFF; int red = (argb >> 16) & 0xFF; @@ -182,18 +184,19 @@ public static NativeImage applyGrayScaleFilter(NativeImage image) { int gray = (red + green + blue) / 3; int newArgb = (alpha << 24) | (gray << 16) | (gray << 8) | gray; - result.setColor(x, y, newArgb); + result.setColorArgb(x, y, newArgb); } } return result; } + public static NativeImage cropTexture(NativeImage image, int x, int y, int width, int height) { NativeImage result = new NativeImage(width, height, false); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { - result.setColor(j, i, image.getColor(x + j, y + i)); + result.setColorArgb(j, i, image.getColorArgb(x + j, y + i)); } } @@ -207,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.getColor(x, y); + int argb = image.getColorArgb(x, y); int alpha = (argb >> 24) & 0xFF; int red = ((argb >> 16) & 0xFF) * ((color >> 16) & 0xFF) / 255; @@ -216,7 +219,7 @@ public static NativeImage tintTexture(NativeImage image, int color) { int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; - result.setColor(x, y, newArgb); + result.setColorArgb(x, y, newArgb); } } @@ -230,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.getColor(x, y); - int argb2 = overlay.getColor(x, y); + int argb1 = image.getColorArgb(x, y); + int argb2 = overlay.getColorArgb(x, y); int alpha = Math.max((argb1 >> 24) & 0xFF, (argb2 >> 24) & 0xFF); int red = Math.min(255, ((argb1 >> 16) & 0xFF) + ((argb2 >> 16) & 0xFF)); @@ -240,7 +243,7 @@ public static NativeImage overlayTexture(NativeImage image, NativeImage overlay) int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; - result.setColor(x, y, newArgb); + result.setColorArgb(x, y, newArgb); } } @@ -255,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.getColor(x, y); + int argb = image.getColorArgb(x, y); redTotal += (argb >> 16) & 0xFF; greenTotal += (argb >> 8) & 0xFF; @@ -263,12 +266,10 @@ public static int getAverageColor(NativeImage image) { } } - int redAverage = (int)(redTotal / pixelCount); - int greenAverage = (int)(greenTotal / pixelCount); - int blueAverage = (int)(blueTotal / pixelCount); + int redAverage = (int) (redTotal / pixelCount); + int greenAverage = (int) (greenTotal / pixelCount); + int blueAverage = (int) (blueTotal / pixelCount); return (redAverage << 16) | (greenAverage << 8) | blueAverage; } - - } diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Animation.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Animation.java new file mode 100644 index 0000000..2ce05cc --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Animation.java @@ -0,0 +1,65 @@ +package com.tanishisherewith.dynamichud.helpers.animationhelper; + +import java.util.ArrayList; +import java.util.List; + +public abstract class Animation { + public long startTime; + public long duration; + protected boolean running = false; + protected boolean finished = false; + protected EasingType easing = EasingType.LINEAR; + protected final List completionCallbacks = new ArrayList<>(); + + public void start() { + startTime = System.currentTimeMillis(); + running = true; + finished = false; + } + + public void stop() { + running = false; + finished = true; + } + + public void update() { + if (!running || finished) return; + + long elapsed = System.currentTimeMillis() - startTime; + float progress = Math.min(elapsed / (float) duration, 1.0f); + float easedProgress = Easing.apply(easing, progress); + + applyAnimation(easedProgress); + + if (progress >= 1.0f) { + finish(); + } + } + + protected abstract void applyAnimation(float progress); + + public Animation duration(long durationMs) { + this.duration = durationMs; + return this; + } + + public Animation easing(EasingType easing) { + this.easing = easing; + return this; + } + + public Animation onComplete(Runnable callback) { + completionCallbacks.add(callback); + return this; + } + + public void finish() { + finished = true; + running = false; + completionCallbacks.forEach(Runnable::run); + } + + public boolean isFinished() { + return finished; + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java new file mode 100644 index 0000000..20a80b0 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java @@ -0,0 +1,8 @@ +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/Easing.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Easing.java new file mode 100644 index 0000000..75998ea --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Easing.java @@ -0,0 +1,87 @@ +package com.tanishisherewith.dynamichud.helpers.animationhelper; + +public class Easing { + public static float apply(EasingType easingType, float progress) { + return switch (easingType) { + case LINEAR -> progress; + case EASE_IN_SINE -> (float) (1 - Math.cos((progress * Math.PI) / 2)); + case EASE_OUT_SINE -> (float) Math.sin((progress * Math.PI) / 2); + case EASE_IN_OUT_SINE -> (float) (-(Math.cos(Math.PI * progress) - 1) / 2); + case EASE_IN_QUAD -> progress * progress; + case EASE_OUT_QUAD -> 1 - (1 - progress) * (1 - progress); + case EASE_IN_OUT_QUAD -> + progress < 0.5 ? 2 * progress * progress : (float) (1 - Math.pow(-2 * progress + 2, 2) / 2); + case EASE_IN_CUBIC -> progress * progress * progress; + case EASE_OUT_CUBIC -> (float) (1 - Math.pow(1 - progress, 3)); + case EASE_IN_OUT_CUBIC -> + progress < 0.5 ? 4 * progress * progress * progress : (float) (1 - Math.pow(-2 * progress + 2, 3) / 2); + case EASE_IN_QUART -> progress * progress * progress * progress; + case EASE_OUT_QUART -> (float) (1 - Math.pow(1 - progress, 4)); + case EASE_IN_OUT_QUART -> + progress < 0.5 ? 8 * progress * progress * progress * progress : (float) (1 - Math.pow(-2 * progress + 2, 4) / 2); + case EASE_IN_QUINT -> progress * progress * progress * progress * progress; + case EASE_OUT_QUINT -> (float) (1 - Math.pow(1 - progress, 5)); + case EASE_IN_OUT_QUINT -> + progress < 0.5 ? 16 * progress * progress * progress * progress * progress : (float) (1 - Math.pow(-2 * progress + 2, 5) / 2); + case EASE_IN_EXPO -> (float) (progress == 0 ? 0 : Math.pow(2, 10 * progress - 10)); + case EASE_OUT_EXPO -> (float) (progress == 1 ? 1 : 1 - Math.pow(2, -10 * progress)); + case EASE_IN_OUT_EXPO -> { + if (progress == 0 || progress == 1) yield progress; + yield (float) (progress < 0.5 + ? Math.pow(2, 20 * progress - 10) / 2 + : (2 - Math.pow(2, -20 * progress + 10)) / 2); + } + case EASE_IN_CIRC -> (float) (1 - Math.sqrt(1 - Math.pow(progress, 2))); + case EASE_OUT_CIRC -> (float) Math.sqrt(1 - Math.pow(progress - 1, 2)); + case EASE_IN_OUT_CIRC -> progress < 0.5 + ? (float) ((1 - Math.sqrt(1 - Math.pow(2 * progress, 2))) / 2) + : (float) ((Math.sqrt(1 - Math.pow(-2 * progress + 2, 2)) + 1) / 2); + case EASE_IN_BACK -> (float) (2.70158 * progress * progress * progress - 1.70158 * progress * progress); + case EASE_OUT_BACK -> { + float c1 = 1.70158f; + float c3 = c1 + 1; + yield (float) (1 + c3 * Math.pow(progress - 1, 3) + c1 * Math.pow(progress - 1, 2)); + } + case EASE_IN_OUT_BACK -> { + float c1 = 1.70158f; + float c2 = c1 * 1.525f; + yield (float) (progress < 0.5 + ? (Math.pow(2 * progress, 2) * ((c2 + 1) * 2 * progress - c2)) / 2 + : (Math.pow(2 * progress - 2, 2) * ((c2 + 1) * (progress * 2 - 2) + c2) + 2) / 2); + } + case EASE_IN_ELASTIC -> { + float c4 = (float) (2 * Math.PI / 3); + yield progress == 0 ? 0 : progress == 1 ? 1 : (float) (-Math.pow(2, 10 * progress - 10) * Math.sin((progress * 10 - 10.75) * c4)); + } + case EASE_OUT_ELASTIC -> { + float c4 = (float) (2 * Math.PI / 3); + yield progress == 0 ? 0 : progress == 1 ? 1 : (float) (Math.pow(2, -10 * progress) * Math.sin((progress * 10 - 0.75) * c4) + 1); + } + case EASE_IN_OUT_ELASTIC -> { + float c5 = (float) (2 * Math.PI / 4.5); + yield progress == 0 ? 0 : progress == 1 ? 1 : progress < 0.5 + ? (float) (-(Math.pow(2, 20 * progress - 10) * Math.sin((20 * progress - 11.125) * c5)) / 2) + : (float) (Math.pow(2, -20 * progress + 10) * Math.sin((20 * progress - 11.125) * c5) / 2 + 1); + } + case EASE_IN_BOUNCE -> 1 - bounceOut(1 - progress); + case EASE_OUT_BOUNCE -> bounceOut(progress); + case EASE_IN_OUT_BOUNCE -> progress < 0.5 + ? (1 - bounceOut(1 - 2 * progress)) / 2 + : (1 + bounceOut(2 * progress - 1)) / 2; + }; + } + + private static float bounceOut(float progress) { + float n1 = 7.5625f; + float d1 = 2.75f; + if (progress < 1 / d1) { + return n1 * progress * progress; + } else if (progress < 2 / d1) { + return n1 * (progress -= 1.5f / d1) * progress + 0.75f; + } else if (progress < 2.5 / d1) { + return n1 * (progress -= 2.25f / d1) * progress + 0.9375f; + } else { + return n1 * (progress -= 2.625f / d1) * progress + 0.984375f; + } + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/EasingType.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/EasingType.java new file mode 100644 index 0000000..8316475 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/EasingType.java @@ -0,0 +1,35 @@ +package com.tanishisherewith.dynamichud.helpers.animationhelper; + +public enum EasingType { + LINEAR, + EASE_IN_SINE, + EASE_OUT_SINE, + EASE_IN_OUT_SINE, + EASE_IN_QUAD, + EASE_OUT_QUAD, + EASE_IN_OUT_QUAD, + EASE_IN_CUBIC, + EASE_OUT_CUBIC, + EASE_IN_OUT_CUBIC, + EASE_IN_QUART, + EASE_OUT_QUART, + EASE_IN_OUT_QUART, + EASE_IN_QUINT, + EASE_OUT_QUINT, + EASE_IN_OUT_QUINT, + EASE_IN_EXPO, + EASE_OUT_EXPO, + EASE_IN_OUT_EXPO, + EASE_IN_CIRC, + EASE_OUT_CIRC, + EASE_IN_OUT_CIRC, + EASE_IN_BACK, + EASE_OUT_BACK, + EASE_IN_OUT_BACK, + EASE_IN_ELASTIC, + EASE_OUT_ELASTIC, + EASE_IN_OUT_ELASTIC, + EASE_IN_BOUNCE, + EASE_OUT_BOUNCE, + EASE_IN_OUT_BOUNCE +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/CompositeAnimation.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/CompositeAnimation.java new file mode 100644 index 0000000..27f091c --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/CompositeAnimation.java @@ -0,0 +1,100 @@ +package com.tanishisherewith.dynamichud.helpers.animationhelper.animations; + +import com.tanishisherewith.dynamichud.helpers.animationhelper.Animation; + +import java.util.ArrayList; +import java.util.List; + +public class CompositeAnimation extends Animation { + private final List children = new ArrayList<>(); + private final boolean parallel; + private int currentChildIndex = 0; + private long[] childStartTimes; + + public CompositeAnimation(boolean parallel) { + this.parallel = parallel; + } + + public CompositeAnimation add(Animation animation) { + children.add(animation); + return this; + } + + @Override + public void start() { + super.start(); + if (parallel) { + children.forEach(Animation::start); + } else { + // Calculate total duration as sum of children's durations + this.duration = children.stream().mapToLong(a -> a.duration).sum(); + this.childStartTimes = new long[children.size()]; + long accumulated = 0; + for (int i = 0; i < children.size(); i++) { + childStartTimes[i] = accumulated; + accumulated += children.get(i).duration; + } + startChild(0); + } + } + + private void startChild(int index) { + if (index < children.size()) { + Animation child = children.get(index); + child.start(); + // Adjust child's start time to match group timeline + child.startTime = this.startTime + childStartTimes[index]; + } + } + + @Override + protected void applyAnimation(float progress) { + if (parallel) { + children.forEach(Animation::update); + } else { + long elapsed = System.currentTimeMillis() - startTime; + + // Find active child + for (int i = 0; i < children.size(); i++) { + long childDuration = children.get(i).duration; + if (elapsed < childStartTimes[i] + childDuration) { + if (currentChildIndex != i) { + currentChildIndex = i; + startChild(i); + } + children.get(i).update(); + break; + } + } + } + } + + @Override + public void stop() { + super.stop(); + children.forEach(Animation::stop); + } + + @Override + public boolean isFinished() { + if (parallel) { + return children.stream().allMatch(Animation::isFinished); + } else { + long elapsed = System.currentTimeMillis() - startTime; + return elapsed >= duration; + } + } + + @Override + public void finish() { + // Ensure all children finish properly + if (!parallel) { + children.forEach(child -> { + if (!child.isFinished()) { + child.finish(); + } + }); + } + super.finish(); + } +} \ 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 new file mode 100644 index 0000000..6850fff --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/MathAnimations.java @@ -0,0 +1,113 @@ +package com.tanishisherewith.dynamichud.helpers.animationhelper.animations; + +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; + +public class MathAnimations { + /// SHAKE: Random offset animation with smooth decay + 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); + } + + /// 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 + ); + } + + /// FLICKER: Random flashing effect + public static float flicker(float min, float max, float chance) { + Random rand = Random.create(); + return rand.nextFloat() < chance ? + min + (max - min) * rand.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( + (float) (Math.cos(angle) * radius), + (float) (Math.sin(angle) * radius) + ); + } + + /// SAWTOOTH WAVE: Linear rise with sudden drop + public static float sawtooth(float period, float min, float max) { + float phase = (System.currentTimeMillis() % period) / period; + return min + (max - min) * phase; + } + + /// 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 ? + (phase / halfPeriod) : + 2 - (phase / halfPeriod); + return min + (max - min) * value; + } + + /// BOUNCE: Simulates physical bouncing + public static float bounce(float dropHeight, float gravity, float dampening) { + float t = System.currentTimeMillis() / 1000f; + return (float) (dropHeight * Math.abs(Math.sin(t * Math.sqrt(gravity))) * + Math.exp(-dampening * t)); + } + + /// PULSE: Smooth heartbeat-like effect + public static float pulse1(float base, float amplitude, float frequency) { + return (float) (base + amplitude * + (0.5 + 0.5 * Math.sin(System.currentTimeMillis() * 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( + (float) ((baseRadius + expansionRate * t) * Math.cos(t * speed)), + (float) ((baseRadius + expansionRate * t) * Math.sin(t * speed)) + ); + } + + /// Continuous pulsating effect using sine wave + public static float pulse2(float speed, float min, float max) { + return (float) ((Math.sin(System.currentTimeMillis() * speed) + 1) / 2 * (max - min) + min); + } + + /// Linear interpolation between values over time + 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); + } + + /// 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; + } + + /// Continuous rotation using modulo + public static float continuousRotation(float speed) { + return (System.currentTimeMillis() % (360_000 / speed)) * (speed / 1000); + } + + /// Elastic wobble effect + public static float elasticWobble(float speed, float magnitude) { + return (float) (Math.sin(System.currentTimeMillis() * speed) * + Math.exp(-0.001 * System.currentTimeMillis()) * magnitude); + } +} \ 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 new file mode 100644 index 0000000..9d19d3b --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/ValueAnimation.java @@ -0,0 +1,50 @@ +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; + +public class ValueAnimation extends Animation { + private final AnimationProperty property; + 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; + this.startValue = start; + this.endValue = end; + this.easing = easingType; + } + + public ValueAnimation(AnimationProperty property, float start, float end) { + this(property, start, end, EasingType.LINEAR); + } + + @Override + protected void applyAnimation(float progress) { + this.value = startValue + (endValue - startValue) * Easing.apply(easing, progress); + property.set(value); + } + + public ValueAnimation easing(EasingType easing) { + this.easing = easing; + return this; + } + + public ValueAnimation startValue(float startValue) { + this.startValue = startValue; + return this; + } + + public ValueAnimation endValue(float endValue) { + this.endValue = endValue; + return this; + } + + public float getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java new file mode 100644 index 0000000..33ae2b7 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java @@ -0,0 +1,30 @@ +package com.tanishisherewith.dynamichud.integration; + +import com.tanishisherewith.dynamichud.widget.WidgetManager; +import com.tanishisherewith.dynamichud.widgets.GraphWidget; +import com.tanishisherewith.dynamichud.widgets.ItemWidget; +import com.tanishisherewith.dynamichud.widgets.TextWidget; + +/** + * The default implementation for included widgets. + */ +public class DefaultIntegrationImpl implements DynamicHudIntegration { + @Override + public DynamicHudConfigurator configure(DynamicHudConfigurator configurator) { + configurator.markAsUtility = true; + return configurator; + } + + @Override + public void init() { + } + + @Override + public void registerCustomWidgets() { + WidgetManager.registerCustomWidgets( + TextWidget.DATA, + ItemWidget.DATA, + GraphWidget.DATA + ); + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java new file mode 100644 index 0000000..7340598 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java @@ -0,0 +1,141 @@ +package com.tanishisherewith.dynamichud.integration; + +import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; +import com.tanishisherewith.dynamichud.widget.Widget; +import com.tanishisherewith.dynamichud.widget.WidgetManager; +import com.tanishisherewith.dynamichud.widget.WidgetRenderer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import org.jetbrains.annotations.ApiStatus; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import static com.tanishisherewith.dynamichud.integration.IntegrationManager.FILE_MAP; + +public class DynamicHudConfigurator { + private final List widgets = new ArrayList<>(); + private WidgetRenderer renderer; + private Consumer> saveHandler = widgetsList -> {}; + private AbstractMoveableScreen screen = null; + public boolean markAsUtility = false; // A.k.a we don't want this mod to display a hud. + + public DynamicHudConfigurator addWidget(Widget widget) { + this.widgets.add(widget); + return this; + } + + /** + * Configure the existing renderer object with this method + */ + public DynamicHudConfigurator configureRenderer(Consumer wrConsumer) { + if (renderer == null) { + this.renderer = new WidgetRenderer(widgets); + } + wrConsumer.accept(renderer); + return this; + } + + public DynamicHudConfigurator configureRenderer(Consumer wrConsumer, List widgets) { + this.renderer = new WidgetRenderer(widgets); + wrConsumer.accept(renderer); + return this; + } + + /** + * Override the present widget renderer with your own instance + */ + public DynamicHudConfigurator overrideRenderer(WidgetRenderer renderer) { + this.renderer = renderer; + return this; + } + + /** + * Called before saving these widgets + */ + public DynamicHudConfigurator onSave(Consumer> saveHandler) { + this.saveHandler = saveHandler; + return this; + } + + /** + * Returns the movable screen for the hud screen. + *

+ *

+ * !! Should never be null !! + *

+ *

+ */ + public DynamicHudConfigurator withMoveableScreen(Function screenProvider) { + this.screen = screenProvider.apply(this); + return this; + } + + /** + * Batch operation + */ + public DynamicHudConfigurator modifyWidgets(Consumer operation) { + widgets.forEach(operation); + return this; + } + + public List getWidgets() { + return Collections.unmodifiableList(widgets); + } + + public WidgetRenderer getRenderer() { + return renderer; + } + + public final Consumer> getSaveHandler() { + return saveHandler; + } + + public final AbstractMoveableScreen getMovableScreen() { + return screen; + } + + /** + * Internal method to save these widgets using fabric API events. Should not be called anywhere else except when loading the DHIntegration on startup. + */ + @ApiStatus.Internal + public void setupSaveEvents(File widgetsFile) { + /* === Saving === */ + // Each mod is hooked to the fabric's event system to save its widget. + + //When a player exits a world (SinglePlayer worlds) or a server stops + ServerLifecycleEvents.SERVER_STOPPING.register(server -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); + + // When a resource pack is reloaded. + ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, s) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName()))); + + //When player disconnects + 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()))); + } + + @ApiStatus.Internal + public final void registerWidgets() { + widgets.forEach(WidgetManager::addWidget); + } + + private void saveWidgetsSafely(File widgetsFile, List widgets) { + try { + this.saveHandler.accept(widgets); + WidgetManager.saveWidgets(widgetsFile, widgets); + } catch (Throwable e) { + DynamicHUD.logger.error("Failed to save widgets. Widgets passed: {}", widgets); + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java similarity index 64% rename from src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java rename to src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java index 484f981..bcc32de 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java +++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java @@ -1,5 +1,6 @@ -package com.tanishisherewith.dynamichud; +package com.tanishisherewith.dynamichud.integration; +import com.tanishisherewith.dynamichud.IntegrationTest; import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; import com.tanishisherewith.dynamichud.widget.WidgetData; import com.tanishisherewith.dynamichud.widget.WidgetManager; @@ -9,34 +10,15 @@ import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import org.lwjgl.glfw.GLFW; -import org.lwjgl.system.NonnullDefault; import java.io.File; /** * This interface provides methods for integrating DynamicHud into a mod. + * @see IntegrationTest + * @see DefaultIntegrationImpl */ public interface DynamicHudIntegration { - /** - * The category for the key binding. - */ - String KEYBIND_CATEGORY = "DynamicHud"; - - /** - * The translation key for the editor screen. - */ - String TRANSLATION_KEY = "DynamicHud Editor Screen"; - - /** - * The input type for the key binding. - */ - InputUtil.Type INPUT_TYPE = InputUtil.Type.KEYSYM; - - /** - * The key code for the key binding. - */ - int KEY = GLFW.GLFW_KEY_RIGHT_SHIFT; - /** * The key binding for opening the editor screen. */ @@ -62,6 +44,14 @@ public interface DynamicHudIntegration { */ File WIDGETS_FILE = new File(FILE_DIRECTORY, FILENAME); + + /** + * Entry point for configuring DynamicHUD integration. + * + * @param configurator Configuration context + */ + DynamicHudConfigurator configure(DynamicHudConfigurator configurator); + /** * Initializes the DynamicHud integration. *

@@ -71,9 +61,10 @@ public interface DynamicHudIntegration { void init(); /** - * To be used to add widgets using {@link WidgetManager}. + * This method is called after widgets from the widget file have been loaded successfully and added to the renderer. */ - void addWidgets(); + default void postWidgetLoading(WidgetRenderer renderer) { + } /** * To register custom widgets. This method can be overridden by implementations. @@ -85,22 +76,13 @@ public interface DynamicHudIntegration { * WidgetManager.registerCustomWidget(TextWidget.DATA); * } * - * + *

* Custom widgets can be registered in any method in the interface * but to avoid any errors and mishaps it is recommended you add them here */ default void registerCustomWidgets() { } - /** - * Performs any necessary initialization after the widgets have been added. This method can be overridden by implementations. - *

- * Suggested to be used to initialize a {@link WidgetRenderer} object with the added widgets. - *

- */ - default void initAfter() { - } - /** * Returns the file where widgets are to be saved and loaded from. * @@ -118,26 +100,4 @@ default File getWidgetsFile() { default KeyBinding getKeyBind() { return EDITOR_SCREEN_KEY_BINDING; } - - /** - * Returns the movable screen for the DynamicHud. - *

- *

- * !! Should never be null !! - *

- *

- * - * @return The movable screen. - */ - AbstractMoveableScreen getMovableScreen(); - - /** - * To return a {@link WidgetRenderer} object. - * By default, it returns a widget renderer consisting of all widgets in the {@link WidgetManager} - * - * @return The widget renderer. - */ - default WidgetRenderer getWidgetRenderer() { - return new WidgetRenderer(WidgetManager.getWidgets()); - } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java b/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java new file mode 100644 index 0000000..485a616 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java @@ -0,0 +1,221 @@ +package com.tanishisherewith.dynamichud.integration; + +import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.internal.ModError; +import com.tanishisherewith.dynamichud.internal.WarningScreen; +import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; +import com.tanishisherewith.dynamichud.utils.BooleanPool; +import com.tanishisherewith.dynamichud.widget.Widget; +import com.tanishisherewith.dynamichud.widget.WidgetManager; +import com.tanishisherewith.dynamichud.widget.WidgetRenderer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.loader.api.FabricLoader; +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 java.io.File; +import java.io.IOException; +import java.util.*; + +import static com.tanishisherewith.dynamichud.DynamicHUD.printInfo; + +public final class IntegrationManager { + + /** + * This is a map to store the list of widgets for each widget file to be saved. + *

+ * Allows saving widgets across different mods with same save file name. + */ + public static final Map> FILE_MAP = new HashMap<>(); + private static final List widgetRenderers = new ArrayList<>(); + + private static boolean enableTestIntegration = false; + + + public static void addWidgetRenderer(WidgetRenderer widgetRenderer) { + widgetRenderers.add(widgetRenderer); + } + + public static List getWidgetRenderers() { + return widgetRenderers; + } + + /** + * Opens the MovableScreen when the specified key is pressed. + * + * @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()) { + DynamicHUD.MC.setScreen(screen); + } + } + + 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]); + break; + } + } + } + + public static void integrate() { + checkToEnableTestIntegration(); + + printInfo("Integrating mods..."); + + var integrations = new ArrayList<>(getRegisteredIntegrations()); + + if (enableTestIntegration) { + EntrypointContainer testIntegration = getTestIntegration(); + if (testIntegration != null) { + integrations.add(testIntegration); + printInfo("Test integration enabled and loaded successfully."); + } + } + + List bad_implementations = new ArrayList<>(); + + integrations.forEach(container -> { + //Register custom widget data's by WidgetManager.registerCustomWidgets() first for every entrypoint + container.getEntrypoint().registerCustomWidgets(); + }); + + for (var entrypoint : integrations) { + ModMetadata metadata = entrypoint.getProvider().getMetadata(); + String modId = metadata.getId(); + + AbstractMoveableScreen screen; + KeyBinding binding; + WidgetRenderer widgetRenderer; + File widgetsFile; + try { + DynamicHudIntegration DHIntegration = entrypoint.getEntrypoint(); + + //Calls the init method + DHIntegration.init(); + + DynamicHudConfigurator configurator = DHIntegration.configure(new DynamicHudConfigurator()); + + if (configurator.markAsUtility) { + printInfo(String.format("Supported utility mod with id %s was found!", modId)); + continue; + } + + printInfo(String.format("Supported mod with id %s was found!", modId)); + + //Gets the widget file to save and load the widgets from + widgetsFile = DHIntegration.getWidgetsFile(); + + // Adds / loads widgets from file + if (WidgetManager.doesWidgetFileExist(widgetsFile)) { + List widgets = WidgetManager.loadWidgets(widgetsFile); + configurator.configureRenderer(renderer -> renderer.clearAndAdd(widgets)); + DHIntegration.postWidgetLoading(configurator.getRenderer()); + } else { + configurator.registerWidgets(); + } + + + // Get the instance of AbstractMoveableScreen + screen = Objects.requireNonNull(configurator.getMovableScreen(), "AbstractMovableScreen instance should not be null!"); + + // Get the keybind to open the screen instance + binding = DHIntegration.getKeyBind(); + + //WidgetRenderer with widgets instance + widgetRenderer = configurator.getRenderer(); + + addWidgetRenderer(widgetRenderer); + + updateFileMap(widgetsFile.getName(), widgetRenderer.getWidgets()); + + //Register events for rendering, saving, loading, and opening the hudEditor + ClientTickEvents.START_CLIENT_TICK.register((client) -> openScreen(binding, screen)); + + configurator.setupSaveEvents(widgetsFile); + + printInfo(String.format("Integration of mod %s was successful", modId)); + } catch (Throwable e) { + if (e instanceof IOException) { + DynamicHUD.logger.warn("An IO error has occurred while loading widgets of mod {}", modId, e); + } else { + DynamicHUD.logger.error("Mod {} has improper implementation of DynamicHUD", modId, e); + } + bad_implementations.add(new ModError(modId, e.getMessage().trim())); + } + } + printInfo("(DynamicHUD) Integration of supported mods was successful"); + + + // Sheesh + if (!bad_implementations.isEmpty()) { + BooleanPool.put("WarningScreenFlag", false); + + ClientTickEvents.START_CLIENT_TICK.register((client) -> { + if (BooleanPool.get("WarningScreenFlag")) return; + + if (DynamicHUD.MC.currentScreen instanceof TitleScreen) { + DynamicHUD.MC.setScreen(new WarningScreen(bad_implementations)); + BooleanPool.put("WarningScreenFlag", true); + } + }); + } + + } + + private static void updateFileMap(String fileName, List widgets) { + FILE_MAP.compute(fileName, (k, v) -> { + // Concat existing and the new widget list. + if (v == null) return new ArrayList<>(widgets); + v.addAll(widgets); + return v; + }); + } + + private static List> getRegisteredIntegrations() { + return new ArrayList<>(FabricLoader.getInstance() + .getEntrypointContainers("dynamicHud", DynamicHudIntegration.class)); + } + + /** + * This makes it so that 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` + */ + private static EntrypointContainer getTestIntegration() { + DynamicHudIntegration testIntegration; + try { + Class testClass = Class.forName("com.tanishisherewith.dynamichud.IntegrationTest"); + testIntegration = (DynamicHudIntegration) testClass.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException e) { + DynamicHUD.logger.info("DynamicHudTest class not found. Skipping test integration."); + return null; + } catch (Exception e) { + DynamicHUD.logger.error("Error instantiating DynamicHudTest", e); + return null; + } + + return new EntrypointContainer<>() { + @Override + public DynamicHudIntegration getEntrypoint() { + return testIntegration; + } + + @Override + public ModContainer getProvider() { + return FabricLoader.getInstance().getModContainer(DynamicHUD.MOD_ID).orElseThrow(); + } + }; + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/internal/ModError.java b/src/main/java/com/tanishisherewith/dynamichud/internal/ModError.java new file mode 100644 index 0000000..57ab93d --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/internal/ModError.java @@ -0,0 +1,4 @@ +package com.tanishisherewith.dynamichud.internal; + +public record ModError(String modName, String errorMessage) { +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/internal/System.java b/src/main/java/com/tanishisherewith/dynamichud/internal/System.java new file mode 100644 index 0000000..e3ab2ab --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/internal/System.java @@ -0,0 +1,29 @@ +package com.tanishisherewith.dynamichud.internal; + +import java.util.*; + +public abstract class System { + // A map to store all instances of DynamicValueRegistry by modId + private static final Map, Map>> instanceRegistry = new HashMap<>(); + + public static void registerInstance(Object instance, String modId) { + Class cls = instance.getClass(); + Map> modMap = instanceRegistry.computeIfAbsent(cls, k -> new HashMap<>()); + Set list = modMap.computeIfAbsent(modId, k -> new HashSet<>()); + list.add(instance); + } + + public static List getInstances(Class cls, String modId) { + Map> modMap = instanceRegistry.get(cls); + if (modMap == null) return Collections.emptyList(); + Set list = modMap.get(modId); + if (list == null) return Collections.emptyList(); + List typedList = new ArrayList<>(); + for (Object obj : list) { + if (cls.isInstance(obj)) { + typedList.add(cls.cast(obj)); + } + } + return typedList; + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/UID.java b/src/main/java/com/tanishisherewith/dynamichud/internal/UID.java similarity index 93% rename from src/main/java/com/tanishisherewith/dynamichud/utils/UID.java rename to src/main/java/com/tanishisherewith/dynamichud/internal/UID.java index 6a1735f..e31e0d5 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/UID.java +++ b/src/main/java/com/tanishisherewith/dynamichud/internal/UID.java @@ -1,4 +1,4 @@ -package com.tanishisherewith.dynamichud.utils; +package com.tanishisherewith.dynamichud.internal; import java.util.Random; diff --git a/src/main/java/com/tanishisherewith/dynamichud/internal/WarningScreen.java b/src/main/java/com/tanishisherewith/dynamichud/internal/WarningScreen.java new file mode 100644 index 0000000..2b7ef4b --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/internal/WarningScreen.java @@ -0,0 +1,68 @@ +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 net.minecraft.util.Util; + +import java.awt.*; +import java.io.File; +import java.util.List; + +public class WarningScreen extends Screen { + private final List modErrors; + + public WarningScreen(List modErrors) { + super(Text.of("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")) + .build(); + + ButtonWidget logs_folder = ButtonWidget.builder(Text.of("Open logs"), button -> { + File logsFolder = new File(MinecraftClient.getInstance().runDirectory, "logs"); + Util.getOperatingSystem().open(logsFolder); + }) + .dimensions(this.width / 2 - 100, this.height - 70, 200, 20) + .narrationSupplier((e) -> Text.literal("Open logs")) + .build(); + + // Add "I Understand" button + this.addDrawableChild(confirmButton); + this.addDrawableChild(logs_folder); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, 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()); + + 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); + + 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); + } + 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); + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java new file mode 100644 index 0000000..5b0367a --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java @@ -0,0 +1,38 @@ +package com.tanishisherewith.dynamichud.mixins; + +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 org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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.function.Supplier; + +@Mixin(OptionsScreen.class) +public abstract class OptionsScreenMixin extends Screen { + @Shadow + protected abstract ButtonWidget createButton(Text message, Supplier screenSupplier); + + protected OptionsScreenMixin(Text 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()); + + directionalLayoutWidget2.getMainPositioner().marginLeft(-26).marginY(-28); + + ButtonWidget widget = createButton(Text.of("DH"), () -> GlobalConfig.get().createYACLGUI()); + widget.setDimensions(20, 20); + + directionalLayoutWidget2.add(widget); + } +} \ 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 8f8680e..21446b4 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java +++ b/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java @@ -1,6 +1,6 @@ package com.tanishisherewith.dynamichud.mixins; -import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.integration.IntegrationManager; import com.tanishisherewith.dynamichud.widget.WidgetManager; import com.tanishisherewith.dynamichud.widget.WidgetRenderer; import net.minecraft.client.MinecraftClient; @@ -22,7 +22,7 @@ public abstract class ScreenMixin { @Inject(at = @At("RETURN"), method = "render") private void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { - for (WidgetRenderer widgetRenderer : DynamicHUD.getWidgetRenderers()) { + for (WidgetRenderer widgetRenderer : IntegrationManager.getWidgetRenderers()) { widgetRenderer.renderWidgets(context, mouseX, mouseY); } } @@ -35,7 +35,7 @@ private void onScreenResize(MinecraftClient client, int width, int height, Callb @Inject(at = @At("HEAD"), method = "close") private void onClose(CallbackInfo ci) { - for (WidgetRenderer widgetRenderer : DynamicHUD.getWidgetRenderers()) { + for (WidgetRenderer widgetRenderer : IntegrationManager.getWidgetRenderers()) { widgetRenderer.onCloseScreen(); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java b/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java index 7b62fdc..55ad285 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java +++ b/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java @@ -1,15 +1,17 @@ package com.tanishisherewith.dynamichud.screens; import com.tanishisherewith.dynamichud.config.GlobalConfig; +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 org.lwjgl.glfw.GLFW; public abstract class AbstractMoveableScreen extends Screen { public final WidgetRenderer widgetRenderer; - public int snapSize = 100; + /** * Constructs a AbstractMoveableScreen object. */ @@ -18,6 +20,11 @@ public AbstractMoveableScreen(Text title, WidgetRenderer renderer) { this.widgetRenderer = renderer; } + @Override + protected void init() { + super.init(); + } + @Override public void onDisplayed() { super.onDisplayed(); @@ -26,39 +33,56 @@ public void onDisplayed() { @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - widgetRenderer.mouseDragged(mouseX, mouseY, button, snapSize); - return false; + 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); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if(widgetRenderer.mouseClicked(mouseX, mouseY, button)){ - handleClickOnWidget(widgetRenderer.selectedWidget,mouseX,mouseY,button); + if (widgetRenderer.mouseClicked(mouseX, mouseY, button)) { + handleClickOnWidget(widgetRenderer.selectedWidget, mouseX, mouseY, button); } - return false; + ContextMenuManager.getInstance().mouseClicked(mouseX, mouseY, button); + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + widgetRenderer.charTyped(chr, modifiers); + ContextMenuManager.getInstance().charTyped(chr, modifiers); + return super.charTyped(chr, modifiers); } @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { widgetRenderer.mouseReleased(mouseX, mouseY, button); - return false; + ContextMenuManager.getInstance().mouseReleased(mouseX, mouseY, button); + return super.mouseReleased(mouseX, mouseY, button); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - widgetRenderer.keyPressed(keyCode); + 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); + } + return super.keyPressed(keyCode, scanCode, modifiers); } @Override public boolean keyReleased(int keyCode, int scanCode, int modifiers) { - widgetRenderer.keyReleased(keyCode); + widgetRenderer.keyReleased(keyCode, scanCode, modifiers); + ContextMenuManager.getInstance().keyReleased(keyCode, scanCode, modifiers); return super.keyReleased(keyCode, scanCode, modifiers); } @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); } @@ -72,27 +96,36 @@ public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmou */ @Override public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { - assert this.client != null; if (this.client.world == null) { renderInGameBackground(drawContext); } - drawContext.drawText(client.textRenderer,title,client.getWindow().getScaledWidth()/2 - client.textRenderer.getWidth(title.getString())/2,textRenderer.fontHeight/2,-1,true); + drawContext.drawText(client.textRenderer, title, client.getWindow().getScaledWidth() / 2 - client.textRenderer.getWidth(title.getString()) / 2, textRenderer.fontHeight / 2, -1, true); // Draw each widget widgetRenderer.renderWidgets(drawContext, mouseX, mouseY); - if(widgetRenderer.selectedWidget != null && GlobalConfig.get().shouldDisplayDescriptions() && widgetRenderer.selectedWidget.DATA.description() != null){ - drawContext.drawTooltip(client.textRenderer,Text.of(widgetRenderer.selectedWidget.DATA.description()),mouseX,mouseY); + ContextMenuManager.getInstance().renderAll(drawContext, 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); + break; + } + } } } - public void handleClickOnWidget(Widget widget, double mouseX, double mouseY, int button){ + public void handleClickOnWidget(Widget widget, double mouseX, double mouseY, int button) { } @Override public void close() { widgetRenderer.isInEditor = false; widgetRenderer.onCloseScreen(); + ContextMenuManager.getInstance().onClose(); super.close(); } @@ -100,9 +133,5 @@ public void close() { public boolean shouldPause() { return false; } - - public void setSnapSize(int size) { - this.snapSize = size; - } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/BooleanPool.java b/src/main/java/com/tanishisherewith/dynamichud/utils/BooleanPool.java index 9c518b5..aaf3abd 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/BooleanPool.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/BooleanPool.java @@ -9,6 +9,7 @@ public class BooleanPool { public static void put(String key, boolean value) { pool.put(key, value); } + public static void remove(String key) { pool.remove(key); } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/DynamicValueRegistry.java b/src/main/java/com/tanishisherewith/dynamichud/utils/DynamicValueRegistry.java index f3c8390..355a616 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/DynamicValueRegistry.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/DynamicValueRegistry.java @@ -1,103 +1,179 @@ package com.tanishisherewith.dynamichud.utils; -import java.util.ArrayList; +import com.tanishisherewith.dynamichud.internal.System; + import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.function.Supplier; /** - * This class is responsible for managing dynamic values for widgets. - * It maintains a global and local registry of suppliers for these dynamic values. + * A type-safe registry for managing dynamic values for widgets. + * Supports both global and local registries with unique identifiers. *

* To use a local registry, simple create an object of the class. *

  *     {@code
- *     DynamicValueRegistry dvr = new DynamicValueRegistry("mod_id");
+ *     DynamicValueRegistry dvr = new DynamicValueRegistry("mod_id", "my_registry_id");
  *     dvr.registerLocal("ABC",//YourSupplier);
  *     Supplier result = dvr.get("ABC");
  *     }
  * 
*

*/ -public class DynamicValueRegistry extends System { - /** - * A map that holds the global registry of suppliers. - * - * @see #localRegistry - */ - private static final Map> globalRegistry = new HashMap<>(); +public class DynamicValueRegistry { + private static final Map REGISTRY_BY_ID = new HashMap<>(); + private static final Map> GLOBAL_REGISTRY = new HashMap<>(); + public static final String GLOBAL_ID = "global"; + + private final String id; // Unique identifier for this registry instance + private final Map> localRegistry = new HashMap<>(); /** - * A map that holds the local registry of suppliers. + * Constructor for a local registry with a unique ID. * - * @see #globalRegistry + * @param modId The mod ID or unique identifier for grouping registries. + * @param registryId A unique ID for this registry instance. */ - private final Map> localRegistry = new HashMap<>(); + public DynamicValueRegistry(String modId, String registryId) { + this.id = registryId.trim(); + System.registerInstance(this, modId); + REGISTRY_BY_ID.put(this.id, this); + } /** - * Constructor for the DynamicValueRegistry class. + * Constructor for a local registry using with registryId as modID. * - * @param modId The ID of the mod for which this registry is being created. Doesn't need to be modId, it can simply be used as a standard unique identifier string. + * @param modId The mod ID or unique identifier for grouping registries. */ public DynamicValueRegistry(String modId) { - super(modId); - instances.computeIfAbsent(modId, k -> new ArrayList<>()).add(this); + this.id = modId; + System.registerInstance(this, modId); + REGISTRY_BY_ID.put(modId, this); } /** * Registers a supplier in the global registry. * - * @param key The key under which the supplier is to be registered. - * @param supplier The supplier to be registered. + * @param key The key for the supplier. + * @param supplier The supplier providing values of type T. */ - public static void registerGlobal(String key, Supplier supplier) { - globalRegistry.put(key, supplier); + public static void registerGlobal(String key, Supplier supplier) { + GLOBAL_REGISTRY.put(key, supplier); } /** * Retrieves a supplier from the global registry. * - * @param key The key of the supplier to be retrieved. - * @return The supplier registered under the given key, or null if no such supplier exists. + * @param key The key of the supplier. + * @return The supplier, or null if not found. */ public static Supplier getGlobal(String key) { - return globalRegistry.get(key); + return GLOBAL_REGISTRY.get(key); } /** * Registers a supplier in the local registry. * - * @param key The key under which the supplier is to be registered. - * @param supplier The supplier to be registered. + * @param key The key for the supplier. + * @param supplier The supplier providing values of type T. */ public void registerLocal(String key, Supplier supplier) { localRegistry.put(key, supplier); } /** - * Retrieves a supplier from the local registry, falling back to the global registry if necessary. + * Retrieves a supplier from the local or global registry. * - * @param key The key of the supplier to be retrieved. - * @return The supplier registered under the given key, or null if no such supplier exists. + * @param key The key of the supplier. + * @return The supplier, or null if not found. */ public Supplier get(String key) { - // First, try to get the supplier from the local registry - Supplier supplier = localRegistry.get(key); + return localRegistry.getOrDefault(key, null); + } + + /** + * Gets the registry instance by its unique ID. + * + * @param registryId The unique ID of the registry. + * @return The registry instance, or null if not found. + */ + public static DynamicValueRegistry getById(String registryId) { + return REGISTRY_BY_ID.get(registryId); + } + + /** + * Gets the registry instance by its unique ID but throws an error if the instance is not present + * + * @param registryId The unique ID of the registry. + * @return The registry instance, or null if not found. + * @throws IllegalStateException If a registry for the id was not found + */ + public static DynamicValueRegistry getByIdSafe(String registryId) { + if (!REGISTRY_BY_ID.containsKey(registryId)) { + throw new IllegalStateException("DynamicValueRegistry for id: " + registryId + " not found"); + } + return REGISTRY_BY_ID.get(registryId); + } + + /** + * @param registryID the registry id + * @return whether the given id matches the global registry id or not + */ + public static boolean isGlobal(String registryID) { + return registryID.equals(GLOBAL_ID); + } - // If the supplier is not in the local registry, try the global registry - if (supplier == null) { - supplier = globalRegistry.get(key); + /** + * Directly get the supplier for a given key and registry id + * + * @param registryID The registry ID + * @param key the registry key + * @return supplier as returned by the registry with the given key + */ + public static Supplier getValue(String registryID, String key) { + if (registryID.isEmpty() || key.isEmpty()) throw new IllegalArgumentException(); + + if (registryID.equals(GLOBAL_ID)) { + return getGlobal(key); } + return getByIdSafe(registryID).get(key); + } - return supplier; + /** + * Retrieves all registry instances for a mod ID. + * + * @param modId The mod ID. + * @return A list of registries for the mod. + */ + public static List getInstances(String modId) { + return System.getInstances(DynamicValueRegistry.class, modId); + } + + /** + * Removes a supplier from the global registry. + * + * @param key The key of the supplier. + */ + public static void removeGlobal(String key) { + GLOBAL_REGISTRY.remove(key); + } + + /** + * Removes a supplier from the local registry. + * + * @param key The key of the supplier. + */ + public void removeLocal(String key) { + localRegistry.remove(key); } /** - * Sets the local registry to the given map. + * Gets the unique ID of this registry. * - * @param map The map to be set as the local registry. + * @return The registry ID. */ - public void setLocalRegistry(Map> map) { - localRegistry.putAll(map); + public String getId() { + return id; } -} +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/Input.java b/src/main/java/com/tanishisherewith/dynamichud/utils/Input.java new file mode 100644 index 0000000..2e621d7 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/Input.java @@ -0,0 +1,22 @@ +package com.tanishisherewith.dynamichud.utils; + + +public interface Input { + boolean mouseClicked(double mouseX, double mouseY, int button); + + boolean mouseReleased(double mouseX, double mouseY, int button); + + boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY); + + void keyPressed(int key, int scanCode, int modifiers); + + void keyReleased(int key, int scanCode, int modifiers); + + void mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount); + + default 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; + } + + void charTyped(char c, int modifiers); +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/System.java b/src/main/java/com/tanishisherewith/dynamichud/utils/System.java deleted file mode 100644 index 3cbc6a0..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/System.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.tanishisherewith.dynamichud.utils; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public abstract class System { - // A map to store all instances of DynamicValueRegistry by modId - protected static final Map> instances = new ConcurrentHashMap<>(); - protected final String modId; - - public System(String modId) { - this.modId = modId; - } - - public static List getInstances(String modId) { - return instances.get(modId); - } - - public String getModId() { - return modId; - } -} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java b/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java index b7f2ccc..4210d61 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java @@ -26,4 +26,13 @@ public enum Quadrant { UPPER_LEFT, UPPER_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT } + public static boolean errorIfTrue(boolean expression, String message, Object... objects) { + if (!expression) DynamicHUD.logger.error(message, objects); + return expression; + } + + public static boolean warnIfTrue(boolean expression, String message, Object... objects) { + if (expression) DynamicHUD.logger.warn(message, objects); + return expression; + } } 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 688d7b8..48a2cf6 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java @@ -1,90 +1,130 @@ package com.tanishisherewith.dynamichud.utils.contextmenu; +import com.tanishisherewith.dynamichud.DynamicHUD; import com.tanishisherewith.dynamichud.helpers.DrawHelper; +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.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 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.List; +import java.util.Objects; -public class ContextMenu { - private final List> options = new ArrayList<>(); // The list of options in the context menu +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ContextMenu implements Input { + public final Color darkerBackgroundColor; + //The properties of a context menu + @NotNull + protected final T properties; + 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. - private int width = 0; - public int finalWidth = 0; - public int height = 0; - public Color backgroundColor = new Color(107, 112, 126, 124); - private final Color darkerBorderColor = backgroundColor.darker().darker().darker().darker().darker().darker(); - //Todo: Add padding around the rectangle instead of just one side. - public int padding = 5; // The amount of padding around the rectangle - public int heightOffset = 4; // Height offset from the widget - public boolean shouldDisplay = false; - public static boolean drawBorder = true; + protected int width = 0; + protected int height = 0, widgetHeight = 0; + protected boolean shouldDisplay = false; protected float scale = 0.0f; + protected Screen parentScreen = null; + protected boolean newScreenFlag = false; + + @Nullable + private final ContextMenu parentMenu; + + public ContextMenu(int x, int y, T properties) { + this(x, y, properties, new DefaultContextMenuScreenFactory()); + } + + public ContextMenu(int x, int y, T properties, ContextMenuScreenFactory screenFactory) { + this(x, y, properties, screenFactory, null); + } + + public ContextMenu(int x, int y, @NotNull T properties, ContextMenuScreenFactory screenFactory, @Nullable ContextMenu parentMenu) { + Objects.requireNonNull(screenFactory, "ContextMenuScreenFactory cannot be null!"); + Objects.requireNonNull(properties, "ContextMenu Properties cannot be null!"); - public ContextMenu(int x, int y) { this.x = x; - this.y = y + heightOffset; + this.y = y + properties.getHeightOffset(); + this.properties = properties; + this.screenFactory = screenFactory; + this.darkerBackgroundColor = properties.getBackgroundColor().darker().darker().darker().darker().darker().darker(); + this.parentMenu = parentMenu; + this.properties.getSkin().setContextMenu(this); + + Screen dummy = screenFactory.create(this, properties); + System.registerInstance(new ContextMenuScreenRegistry(dummy.getClass()), DynamicHUD.MOD_ID); } public void addOption(Option option) { + option.updateProperties(this.getProperties()); options.add(option); } - public void render(DrawContext drawContext, int x, int y, int height, int mouseX, int mouseY) { - this.x = x; - this.y = y + heightOffset + height; - if (!shouldDisplay) return; + public void render(DrawContext drawContext, int x, int y, int mouseX, int mouseY) { + if (newScreenFlag && screenFactory != null) { + DynamicHUD.MC.setScreen(screenFactory.create(this, properties)); + return; + } + this.x = x; + this.y = y + properties.getHeightOffset() + widgetHeight; update(); - DrawHelper.scaleAndPosition(drawContext.getMatrices(), x, y, scale); + if (scale <= 0.0f || newScreenFlag) return; - // Draw the background - DrawHelper.drawRoundedRectangle(drawContext.getMatrices().peek().getPositionMatrix(), this.x - 1, this.y, this.width, this.height, 2,backgroundColor.getRGB()); - if(drawBorder){ - DrawHelper.drawOutlineRoundedBox(drawContext.getMatrices().peek().getPositionMatrix(), this.x - 1,this.y,this.width,this.height,2,0.7f,darkerBorderColor.getRGB()); - } + DrawHelper.scaleAndPosition(drawContext.getMatrices(), x, y, scale); - int yOffset = this.y + 3; - this.width = 10; - for (Option option : options) { - if (!option.shouldRender()) continue; - if(isMouseOver(mouseX,mouseY, this.x +1,yOffset-1,this.finalWidth - 2,option.height)){ - DrawHelper.drawRoundedRectangle(drawContext.getMatrices().peek().getPositionMatrix(), this.x,yOffset - 1.24f,this.finalWidth - 2,option.height + 0.48f,2,backgroundColor.darker().darker().getRGB()); - } - option.render(drawContext, x + 2, yOffset,mouseX,mouseY); - this.width = Math.max(this.width, option.width); - yOffset += option.height + 1; - } - this.width = this.width + padding; - this.finalWidth = this.width; - this.height = (yOffset - this.y); + properties.getSkin().setContextMenu(this); + properties.getSkin().renderContextMenu(drawContext, this, mouseX, mouseY); DrawHelper.stopScaling(drawContext.getMatrices()); } - public boolean isMouseOver(int mouseX, int mouseY, int x, int y, int width, int height){ - return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; - } public void update() { - // Update the scale - float scaleSpeed = 0.1f; - scale += scaleSpeed; - if (scale > 1.0f) { + if (!properties.enableAnimations()) { scale = 1.0f; + return; } + + // Update the scale + if (shouldDisplay) { + scale += 0.1f; + } else { + scale -= 0.1f; + } + + scale = MathHelper.clamp(scale, 0, 1.0f); } public void close() { shouldDisplay = false; - scale = 0.0f; + newScreenFlag = false; + for (Option option : options) { + option.onClose(); + } + if (properties.getSkin().shouldCreateNewScreen() && scale <= 0 && parentScreen != null) { + DynamicHUD.MC.setScreen(parentScreen); + } } public void open() { shouldDisplay = true; update(); + parentScreen = DynamicHUD.MC.currentScreen; + if (properties.getSkin().shouldCreateNewScreen()) { + newScreenFlag = true; + } } public void toggleDisplay() { @@ -95,41 +135,88 @@ public void toggleDisplay() { } } - public void mouseClicked(double mouseX, double mouseY, int button) { - if (!shouldDisplay) return; + public void toggleDisplay(WidgetBox widgetBox, double mouseX, double mouseY, int button) { + if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT && widgetBox.isMouseOver(mouseX, mouseY)) { + toggleDisplay(); + } + } + + public void resetAllOptions() { for (Option option : options) { - option.mouseClicked(mouseX, mouseY, button); + option.reset(); + } + } + + @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); } + return properties.getSkin().mouseClicked(this, mouseX, mouseY, button); } - public void mouseReleased(double mouseX, double mouseY, int button) { + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (!shouldDisplay) return false; + for (Option option : options) { + option.getRenderer().mouseReleased(option, mouseX, mouseY, button); + } + return properties.getSkin().mouseReleased(this, mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (!shouldDisplay) return false; + for (Option option : options) { + option.getRenderer().mouseDragged(option, mouseX, mouseY, button, deltaX, deltaY); + } + return properties.getSkin().mouseDragged(this, mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public void keyPressed(int key, int scanCode, int modifiers) { if (!shouldDisplay) return; - for (Option option : options) { - option.mouseReleased(mouseX, mouseY, button); + for (Option option : options) { + option.getRenderer().keyPressed(option, key, scanCode, modifiers); } + + properties.getSkin().keyPressed(this, key, scanCode, modifiers); } - public void mouseDragged(double mouseX, double mouseY, int button) { + @Override + public void keyReleased(int key, int scanCode, int modifiers) { if (!shouldDisplay) return; - for (Option option : options) { - option.mouseDragged(mouseX, mouseY, button); + for (Option option : options) { + option.getRenderer().keyReleased(option, key, scanCode, modifiers); } + properties.getSkin().keyReleased(this, key, scanCode, modifiers); + } - public void keyPressed(int key) { + @Override + public void mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { if (!shouldDisplay) return; - for (Option option : options) { - option.keyPressed(key); + for (Option option : options) { + option.getRenderer().mouseScrolled(option, mouseX, mouseY, horizontalAmount, verticalAmount); } + properties.getSkin().mouseScrolled(this, mouseX, mouseY, horizontalAmount, verticalAmount); } - public void keyReleased(int key) { + @Override + public void charTyped(char c, int modifiers) { if (!shouldDisplay) return; for (Option option : options) { - option.keyReleased(key); + option.charTyped(c, modifiers); } } + public void set(int x, int y, int widgetHeight) { + this.x = x; + this.y = y; + this.widgetHeight = widgetHeight; + } + public int getX() { return x; } @@ -137,15 +224,53 @@ public int getX() { public int getY() { return y; } + public List> getOptions() { - return options; + return Collections.unmodifiableList(options); } public int getHeight() { return height; } + public void setHeight(int height) { + this.height = height; + } + public int getWidth() { - return finalWidth; + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public @NotNull T getProperties() { + return properties; + } + + public void setWidgetHeight(int widgetHeight) { + this.widgetHeight = widgetHeight; + } + + @Nullable + public ContextMenu getParentMenu() { + return parentMenu; + } + + public ContextMenu createSubMenu(int x, int y, K properties) { + return new ContextMenu<>(x, y, properties, screenFactory, this); + } + + public float getScale() { + return scale; + } + + public boolean isVisible() { + return shouldDisplay; + } + + public void setVisible(boolean shouldDisplay) { + this.shouldDisplay = 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 new file mode 100644 index 0000000..70edde9 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuManager.java @@ -0,0 +1,111 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu; + +import com.tanishisherewith.dynamichud.utils.Input; +import net.minecraft.client.gui.DrawContext; + +import java.util.ArrayList; +import java.util.List; + +public class ContextMenuManager implements Input { + private static final ContextMenuManager INSTANCE = new ContextMenuManager(); + private final List providers = new ArrayList<>(); + + private ContextMenuManager() { + } + + public static ContextMenuManager getInstance() { + return INSTANCE; + } + + public void registerProvider(ContextMenuProvider provider) { + providers.add(provider); + } + + public void renderAll(DrawContext drawContext, int mouseX, int mouseY) { + for (ContextMenuProvider provider : providers) { + ContextMenu contextMenu = provider.getContextMenu(); + if (contextMenu != null) { + contextMenu.render(drawContext, contextMenu.getX(), contextMenu.getY(), mouseX, mouseY); + } + } + } + + public void onClose() { + for (ContextMenuProvider provider : providers) { + provider.getContextMenu().close(); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + for (ContextMenuProvider provider : providers) { + ContextMenu contextMenu = provider.getContextMenu(); + if (contextMenu != null) { + contextMenu.mouseClicked(mouseX, mouseY, button); + } + } + return false; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + for (ContextMenuProvider provider : providers) { + ContextMenu contextMenu = provider.getContextMenu(); + if (contextMenu != null) { + contextMenu.mouseReleased(mouseX, mouseY, button); + } + } + return false; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + for (ContextMenuProvider provider : providers) { + ContextMenu contextMenu = provider.getContextMenu(); + if (contextMenu != null) { + contextMenu.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + } + return false; + } + + @Override + public void keyPressed(int key, int scanCode, int modifiers) { + for (ContextMenuProvider provider : providers) { + ContextMenu contextMenu = provider.getContextMenu(); + if (contextMenu != null) { + contextMenu.keyPressed(key, scanCode, modifiers); + } + } + } + + @Override + public void keyReleased(int key, int scanCode, int modifiers) { + for (ContextMenuProvider provider : providers) { + ContextMenu contextMenu = provider.getContextMenu(); + if (contextMenu != null) { + contextMenu.keyReleased(key, scanCode, modifiers); + } + } + } + + @Override + public void mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + for (ContextMenuProvider provider : providers) { + ContextMenu contextMenu = provider.getContextMenu(); + if (contextMenu != null) { + contextMenu.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + } + } + + @Override + public void charTyped(char c, int modifiers) { + for (ContextMenuProvider provider : providers) { + ContextMenu contextMenu = provider.getContextMenu(); + if (contextMenu != null) { + contextMenu.charTyped(c, modifiers); + } + } + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProperties.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProperties.java new file mode 100644 index 0000000..1b75f56 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProperties.java @@ -0,0 +1,211 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu; + +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.ClassicSkin; +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.Skin; + +import java.awt.*; + +/** + * Note: Not all of these properties are used in all skins or all places. + */ +public class ContextMenuProperties { + protected Color backgroundColor = new Color(107, 112, 126, 124); + protected Color borderColor = Color.BLACK; + protected float borderWidth = 1f; + protected int padding = 5; // The amount of padding around the rectangle + protected int heightOffset = 4; // Height offset from the widget + protected boolean drawBorder = true; + protected boolean shadow = true; + protected boolean roundedCorners = true; + protected int cornerRadius = 3; + protected boolean hoverEffect = true; + protected Color hoverColor = new Color(42, 42, 42, 150); + protected boolean enableAnimations = true; + protected Skin skin = new ClassicSkin(); + + protected ContextMenuProperties() { + } + + public static Builder builder() { + return new Builder<>(new ContextMenuProperties()); + } + + public static ContextMenuProperties createGenericSimplified() { + return new ContextMenuProperties(); + } + + // Getters for all properties + public Color getBackgroundColor() { + return backgroundColor; + } + + public Color getBorderColor() { + return borderColor; + } + + public float getBorderWidth() { + return borderWidth; + } + + public int getPadding() { + return padding; + } + + public int getHeightOffset() { + return heightOffset; + } + + public void setHeightOffset(int heightOffset) { + this.heightOffset = heightOffset; + } + + public boolean shouldDrawBorder() { + return drawBorder; + } + + public boolean shadow() { + return shadow; + } + + public boolean roundedCorners() { + return roundedCorners; + } + + public int getCornerRadius() { + return cornerRadius; + } + + public boolean hoverEffect() { + return hoverEffect; + } + + public Color getHoverColor() { + return hoverColor; + } + + public boolean enableAnimations() { + return enableAnimations; + } + + public Skin getSkin() { + return skin; + } + + /** + * @return Cloned object of every property except skin + */ + public ContextMenuProperties clone() { + return cloneToBuilder().build(); + } + + public Builder cloneToBuilder() { + return ContextMenuProperties.builder() + .backgroundColor(backgroundColor) + .borderColor(borderColor) + .borderWidth(borderWidth) + .padding(padding) + .heightOffset(heightOffset) + .drawBorder(drawBorder) + .shadow(shadow) + .roundedCorners(roundedCorners) + .cornerRadius(cornerRadius) + .hoverEffect(hoverEffect) + .hoverColor(hoverColor) + .enableAnimations(enableAnimations); + } + + /** + * @return Cloned object with same skin as the current ContextMenuProperties + */ + public ContextMenuProperties cloneWithSkin() { + return this.cloneToBuilder() + .skin(skin) + .build(); + } + + /** + * @return Cloned object with a clone of the skin as the current ContextMenuProperties + */ + public ContextMenuProperties cloneSkin() { + return this.cloneToBuilder() + .skin(skin.clone()) + .build(); + } + + public static class Builder { + protected final T properties; + + protected Builder(T properties) { + this.properties = properties; + } + + public Builder backgroundColor(Color backgroundColor) { + properties.backgroundColor = backgroundColor; + return this; + } + + public Builder borderColor(Color borderColor) { + properties.borderColor = borderColor; + return this; + } + + public Builder skin(Skin skin) { + properties.skin = skin; + return this; + } + + public Builder borderWidth(float borderWidth) { + properties.borderWidth = borderWidth; + return this; + } + + public Builder padding(int padding) { + properties.padding = padding; + return this; + } + + public Builder heightOffset(int heightOffset) { + properties.heightOffset = heightOffset; + return this; + } + + public Builder drawBorder(boolean drawBorder) { + properties.drawBorder = drawBorder; + return this; + } + + public Builder shadow(boolean shadow) { + properties.shadow = shadow; + return this; + } + + public Builder roundedCorners(boolean roundedCorners) { + properties.roundedCorners = roundedCorners; + return this; + } + + public Builder cornerRadius(int cornerRadius) { + properties.cornerRadius = cornerRadius; + return this; + } + + public Builder hoverEffect(boolean hoverEffect) { + properties.hoverEffect = hoverEffect; + return this; + } + + public Builder hoverColor(Color hoverColor) { + properties.hoverColor = hoverColor; + return this; + } + + public Builder enableAnimations(boolean enableAnimations) { + properties.enableAnimations = enableAnimations; + return this; + } + + public T build() { + return properties; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProvider.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProvider.java new file mode 100644 index 0000000..995f2c4 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenuProvider.java @@ -0,0 +1,6 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu; + +public interface ContextMenuProvider { + ContextMenu getContextMenu(); +} + diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/Option.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/Option.java deleted file mode 100644 index dfea08c..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/Option.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu; - -import com.tanishisherewith.dynamichud.widget.Widget; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; - -import java.util.function.Consumer; -import java.util.function.Supplier; - -public abstract class Option { - public int x, y; - public int width = 0; - public int height = 0; - public T value = null; - public Supplier shouldRender = () -> true; - protected float scale = 0.0f; - protected Supplier getter; - protected Consumer setter; - protected T defaultValue = null; - protected MinecraftClient mc = MinecraftClient.getInstance(); - - public Option(Supplier getter, Consumer setter) { - this.getter = getter; - this.setter = setter; - value = get(); - defaultValue = get(); - } - - public Option(Supplier getter, Consumer setter, Supplier shouldRender) { - this.getter = getter; - this.setter = setter; - this.shouldRender = shouldRender; - value = get(); - defaultValue = get(); - } - - protected T get() { - return getter.get(); - } - - protected void set(T value) { - this.value = value; - setter.accept(value); - } - - public void render(DrawContext drawContext, int x, int y) { - this.x = x; - this.y = y; - } - public void render(DrawContext drawContext, int x, int y,int mouseX, int mouseY) { - this.render(drawContext, x, y); - } - - public boolean mouseClicked(double mouseX, double mouseY, int button) { - return isMouseOver(mouseX, mouseY); - } - - public boolean mouseReleased(double mouseX, double mouseY, int button) { - return isMouseOver(mouseX, mouseY); - } - - public boolean mouseDragged(double mouseX, double mouseY, int button) { - return isMouseOver(mouseX, mouseY); - } - - public void keyPressed(int key) { - - } - - public void keyReleased(int key) { - - } - - public boolean isMouseOver(double mouseX, double mouseY) { - return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; - } - - public Option setShouldRender(Supplier shouldRender) { - this.shouldRender = shouldRender; - return this; - } - - public boolean shouldRender() { - return shouldRender.get(); - } -} 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 new file mode 100644 index 0000000..1ccfce4 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreen.java @@ -0,0 +1,87 @@ +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/contextmenuscreen/ContextMenuScreenFactory.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenFactory.java new file mode 100644 index 0000000..6dea3ec --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenFactory.java @@ -0,0 +1,14 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen; + +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; +import net.minecraft.client.gui.screen.Screen; + +/** + * We will use this interface to provide the context menu with the screen required by its skins. + * Some skins like {@link com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.MinecraftSkin} require a separate screen to render its contents. + * This can also be used for developers to provide a new custom screen by them or for a custom skin. + */ +public interface ContextMenuScreenFactory { + Screen create(ContextMenu contextMenu, ContextMenuProperties properties); +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenRegistry.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenRegistry.java new file mode 100644 index 0000000..3bd3646 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/ContextMenuScreenRegistry.java @@ -0,0 +1,11 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen; + +import net.minecraft.client.gui.screen.Screen; + +public class ContextMenuScreenRegistry { + public Class screenKlass; + + public ContextMenuScreenRegistry(Class screenKlass) { + this.screenKlass = screenKlass; + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/DefaultContextMenuScreenFactory.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/DefaultContextMenuScreenFactory.java new file mode 100644 index 0000000..b156347 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/contextmenuscreen/DefaultContextMenuScreenFactory.java @@ -0,0 +1,15 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen; + +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; +import net.minecraft.client.gui.screen.Screen; + +/** + * Default implementation of the {@link ContextMenuScreenFactory} providing a {@link ContextMenuScreen} + */ +public class DefaultContextMenuScreenFactory implements ContextMenuScreenFactory { + @Override + public Screen create(ContextMenu contextMenu, ContextMenuProperties properties) { + return new ContextMenuScreen(contextMenu, properties); + } +} 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 new file mode 100644 index 0000000..fb3d782 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/layout/LayoutContext.java @@ -0,0 +1,84 @@ +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/options/BooleanOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/BooleanOption.java index d5ec0e4..d5f3b55 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,45 +1,63 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options; import com.tanishisherewith.dynamichud.utils.BooleanPool; -import com.tanishisherewith.dynamichud.utils.contextmenu.Option; -import net.minecraft.client.gui.DrawContext; +import net.minecraft.screen.ScreenTexts; import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; -import java.awt.*; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; public class BooleanOption extends Option { - public String name = "Empty"; + private final BooleanType booleanType; - public BooleanOption(String name, Supplier getter, Consumer setter) { - super(getter, setter); - this.name = name; + public BooleanOption(Text name, Supplier getter, Consumer setter, BooleanType booleanType) { + super(name, getter, setter); + this.booleanType = booleanType; + this.renderer.init(this); } - public BooleanOption(String name, boolean defaultValue) { - this(name, () -> BooleanPool.get(name), value -> BooleanPool.put(name, value)); - BooleanPool.put(name, defaultValue); + public BooleanOption(Text name, Supplier getter, Consumer setter) { + this(name, getter, setter, BooleanType.TRUE_FALSE); } - @Override - public void render(DrawContext drawContext, int x, int y) { - super.render(drawContext, x, y); - - value = get(); - int color = value ? Color.GREEN.getRGB() : Color.RED.getRGB(); - drawContext.drawText(mc.textRenderer, Text.of(name), x, y, color, false); - this.height = mc.textRenderer.fontHeight; - this.width = mc.textRenderer.getWidth(name) + 1; + public BooleanOption(Text name, boolean defaultValue) { + this(name, defaultValue, BooleanType.TRUE_FALSE); + } + + public BooleanOption(Text name, boolean defaultValue, BooleanType type) { + this(name, () -> BooleanPool.get(name.getString()), value -> BooleanPool.put(name.getString(), value), type); + BooleanPool.put(name.getString(), defaultValue); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - super.mouseClicked(mouseX, mouseY, button); - if (isMouseOver(mouseX, mouseY)) { - value = !value; + if (isMouseOver(mouseX, mouseY) && button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + this.value = !this.value; set(value); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + + public BooleanType getBooleanType() { + return booleanType; + } + + 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); + + private final Function function; + + BooleanType(Function function) { + this.function = function; + } + + public Text getText(boolean val) { + return function.apply(val); } - return true; } } 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 6ad4d49..9471f7c 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 @@ -1,10 +1,7 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options; -import com.tanishisherewith.dynamichud.helpers.DrawHelper; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.utils.contextmenu.Option; -import com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption.ColorGradientPicker; -import net.minecraft.client.gui.DrawContext; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption.ColorGradient; import net.minecraft.text.Text; import java.awt.*; @@ -12,40 +9,15 @@ import java.util.function.Supplier; public class ColorOption extends Option { - public String name = "Empty"; public boolean isVisible = false; - public ContextMenu parentMenu = null; - private ColorGradientPicker colorPicker = null; + private ContextMenu parentMenu = null; + private ColorGradient colorGradient = null; - public ColorOption(String name, ContextMenu parentMenu, Supplier getter, Consumer setter) { - super(getter, setter); - this.name = name; + public ColorOption(Text name, Supplier getter, Consumer setter, ContextMenu parentMenu) { + super(name, getter, setter); this.parentMenu = parentMenu; - colorPicker = new ColorGradientPicker(x + this.parentMenu.finalWidth, y - 10, get(), this::set, 50, 100); - } - - @Override - public void render(DrawContext drawContext, int x, int y) { - super.render(drawContext, x, y); - - int color = isVisible ? Color.GREEN.getRGB() : Color.RED.getRGB(); - this.height = mc.textRenderer.fontHeight; - this.width = mc.textRenderer.getWidth(name) + 8; - drawContext.drawText(mc.textRenderer, Text.of(name), x, y, color, false); - - int shadowOpacity = Math.min(value.getAlpha(),90); - DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext.getMatrices().peek().getPositionMatrix(), - x + width - 4, - y - 1, - 8, - 8, - 2, - value.getRGB(), - shadowOpacity, - 1, - 1); - - colorPicker.render(drawContext, this.x + parentMenu.finalWidth + 7, y - 10); + this.colorGradient = new ColorGradient(x + this.parentMenu.getWidth(), y - 10, get(), this::set, 50, 100); + this.renderer.init(this); } @Override @@ -53,25 +25,39 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { if (isMouseOver(mouseX, mouseY)) { isVisible = !isVisible; if (isVisible) { - colorPicker.setPos(this.x + parentMenu.finalWidth + 7, y - 10); - colorPicker.display(); + colorGradient.setPos(this.x + parentMenu.getWidth() + 7, y - 10); + colorGradient.display(); } else { - colorPicker.close(); + colorGradient.close(); } } - colorPicker.mouseClicked(mouseX, mouseY, button); + colorGradient.mouseClicked(mouseX, mouseY, button); return super.mouseClicked(mouseX, mouseY, button); } @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { - colorPicker.mouseReleased(mouseX, mouseY, button); + colorGradient.mouseReleased(mouseX, mouseY, button); return super.mouseReleased(mouseX, mouseY, button); } @Override - public boolean mouseDragged(double mouseX, double mouseY, int button) { - colorPicker.mouseDragged(mouseX, mouseY, button); - return super.mouseDragged(mouseX, mouseY, button); + 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); + } + + public ColorGradient getColorGradient() { + return colorGradient; + } + + @Override + public void onClose() { + isVisible = false; + colorGradient.close(); + } + + public ContextMenu getParentMenu() { + return parentMenu; } } 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 90123ee..5f0e2ac 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,25 +2,24 @@ import com.tanishisherewith.dynamichud.helpers.DrawHelper; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.utils.contextmenu.Option; -import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; import org.apache.commons.lang3.Validate; +import org.lwjgl.glfw.GLFW; import java.awt.*; import java.util.function.Consumer; import java.util.function.Supplier; public class DoubleOption extends Option { - public String name = "Empty"; - float step = 0.1f; + public double minValue; + public double maxValue; + public float step; + ContextMenu parentMenu; private boolean isDragging = false; - private double minValue = 0.0, maxValue = 0.0; - ContextMenu parentMenu; - public DoubleOption(String name, double minValue, double maxValue, float step, Supplier getter, Consumer setter, ContextMenu parentMenu) { - super(getter, setter); - this.name = name; + public DoubleOption(Text name, double minValue, double maxValue, float step, Supplier getter, Consumer setter, ContextMenu parentMenu) { + super(name, getter, setter); this.value = get(); this.minValue = minValue; this.maxValue = maxValue; @@ -29,48 +28,10 @@ public DoubleOption(String name, double minValue, double maxValue, float step, S 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); } - @Override - public void render(DrawContext drawContext, int x, int y) { - super.render(drawContext, x, y); - value = get(); - - this.width = 35; - this.height = 16; - - // Draw the label - TextRenderer textRenderer = mc.textRenderer; - DrawHelper.scaleAndPosition(drawContext.getMatrices(), x, y, 0.7f); - String labelText = name + ": " + String.format("%.1f", value); - int labelWidth = textRenderer.getWidth(labelText); - this.width = Math.max(this.width, labelWidth); - drawContext.drawTextWithShadow(textRenderer, labelText, x, y + 1, 0xFFFFFFFF); - DrawHelper.stopScaling(drawContext.getMatrices()); - - float handleWidth = 3; - float handleHeight = 8; - double handleX = x + (value - minValue) / (maxValue - minValue) * (width - handleWidth); - double handleY = y + textRenderer.fontHeight + 1 + ((2 - handleHeight) / 2); - - // Draw the slider - drawSlider(drawContext, x, y + textRenderer.fontHeight + 1, width, handleX); - - // Draw the handle - - DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext.getMatrices().peek().getPositionMatrix(), - (float) handleX, - (float) handleY, - handleWidth, - handleHeight, - 1, - 0xFFFFFFFF, - 90, - 0.6f, - 0.6f); - } - - private void drawSlider(DrawContext drawContext, int sliderX, int sliderY, int sliderWidth, double handleX) { + public void drawSlider(DrawContext drawContext, int sliderX, int sliderY, int sliderWidth, double handleX) { DrawHelper.drawRectangle(drawContext.getMatrices().peek().getPositionMatrix(), sliderX, sliderY, sliderWidth, 2, 0xFFFFFFFF); if (handleX - sliderX > 0) { DrawHelper.drawRectangle(drawContext.getMatrices().peek().getPositionMatrix(), (float) sliderX, (float) sliderY, (float) ((value - minValue) / (maxValue - minValue) * (width - 3)), 2, Color.ORANGE.getRGB()); @@ -79,8 +40,7 @@ private void drawSlider(DrawContext drawContext, int sliderX, int sliderY, int s @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - super.mouseClicked(mouseX, mouseY, button); - if (isMouseOver(mouseX, mouseY)) { + if (super.mouseClicked(mouseX, mouseY, button) && button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { step(mouseX); isDragging = true; } @@ -94,17 +54,31 @@ public boolean mouseReleased(double mouseX, double mouseY, int button) { } private void step(double mouseX) { + this.step(mouseX, x); + } + + public void step(double mouseX, double x) { double newValue = minValue + (float) (mouseX - x) / width * (maxValue - minValue); // Round the new value to the nearest step newValue = Math.round(newValue / step) * step; + newValue = Math.clamp(newValue, minValue, maxValue); set(newValue); } + @Override - public boolean mouseDragged(double mouseX, double mouseY, int button) { + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { if (isMouseOver(mouseX, mouseY) && isDragging) { step(mouseX); } - return super.mouseDragged(mouseX, mouseY, button); + return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + public void setDragging(boolean dragging) { + isDragging = dragging; + } + + public boolean isDragging() { + return isDragging; } } 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 index 1351d17..1eab358 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/EnumOption.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/EnumOption.java @@ -1,21 +1,16 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options; -import com.tanishisherewith.dynamichud.utils.contextmenu.Option; -import net.minecraft.client.gui.DrawContext; import net.minecraft.text.Text; -import java.awt.*; import java.util.function.Consumer; import java.util.function.Supplier; public class EnumOption> extends Option { private final E[] values; - public String name = "Empty"; private int currentIndex = 0; - public EnumOption(String name, Supplier getter, Consumer setter, E[] values) { - super(getter, setter); - this.name = name; + 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++) { @@ -24,24 +19,12 @@ public EnumOption(String name, Supplier getter, Consumer setter, E[] value break; } } - } - - @Override - public void render(DrawContext drawContext, int x, int y) { - super.render(drawContext, x, y); - - value = get(); - this.height = mc.textRenderer.fontHeight + 1; - this.width = mc.textRenderer.getWidth(name + ": " + value.name()) + 1; - - drawContext.drawText(mc.textRenderer, Text.of(name + ": "), x, y, Color.WHITE.getRGB(), false); - drawContext.drawText(mc.textRenderer, Text.of(value.name()), x + mc.textRenderer.getWidth(name + ": ") + 1, y, Color.CYAN.getRGB(), false); + this.renderer.init(this); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - super.mouseClicked(mouseX, mouseY, button); - if (isMouseOver(mouseX, mouseY)) { + if (super.mouseClicked(mouseX, mouseY, button)) { if (button == 0) { currentIndex = (currentIndex + 1) % values.length; if (currentIndex > values.length - 1) { @@ -59,4 +42,8 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } 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 index a7aef94..d407242 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ListOption.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ListOption.java @@ -1,22 +1,17 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options; -import com.tanishisherewith.dynamichud.utils.contextmenu.Option; -import net.minecraft.client.gui.DrawContext; import net.minecraft.text.Text; -import java.awt.*; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; public class ListOption extends Option { private final List values; - public String name = "Empty"; private int currentIndex = 0; - public ListOption(String name, Supplier getter, Consumer setter, List values) { - super(getter, setter); - this.name = name; + 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++) { @@ -25,25 +20,12 @@ public ListOption(String name, Supplier getter, Consumer setter, List v break; } } - } - - @Override - public void render(DrawContext drawContext, int x, int y) { - super.render(drawContext, x, y); - - value = get(); - this.height = mc.textRenderer.fontHeight + 1; - this.width = mc.textRenderer.getWidth(name + ": " + value.toString()) + 1; - - drawContext.drawText(mc.textRenderer, Text.of(name + ": "), x, y, Color.WHITE.getRGB(), false); - drawContext.drawText(mc.textRenderer, Text.of(value.toString()), x + mc.textRenderer.getWidth(name + ": ") + 1, y, Color.CYAN.getRGB(), false); - + this.renderer.init(this); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - super.mouseClicked(mouseX, mouseY, button); - if (isMouseOver(mouseX, mouseY)) { + if (super.mouseClicked(mouseX, mouseY, button)) { if (button == 0) { currentIndex = (currentIndex + 1) % values.size(); if (currentIndex > values.size() - 1) { @@ -61,4 +43,8 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } 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 new file mode 100644 index 0000000..be4bf94 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/Option.java @@ -0,0 +1,206 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.options; + +import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.config.GlobalConfig; +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 java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class Option implements Input { + public Text name, description = Text.empty(); + public T value = null; + protected int x, y; + protected int width = 0; + protected int height = 0; + protected Supplier shouldRender; + protected Supplier getter; + protected Consumer setter; + protected T defaultValue = null; + protected MinecraftClient mc = MinecraftClient.getInstance(); + protected ContextMenuProperties properties; + protected SkinRenderer> renderer; + protected Complexity complexity = Complexity.Simple; + + public Option(Text name, Supplier getter, Consumer setter) { + this(name, getter, setter, () -> true); + } + + public Option(Text 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); + } + + public Option(Text name, Supplier getter, Consumer setter, Supplier shouldRender) { + this(name, getter, setter, shouldRender, ContextMenuProperties.createGenericSimplified()); + } + + public T get() { + return getter.get(); + } + + public void set(T value) { + this.value = value; + setter.accept(value); + } + + public void updateProperties(ContextMenuProperties properties) { + this.properties = properties; + this.renderer = properties.getSkin().getRenderer((Class>) this.getClass()); + if (renderer == null) { + DynamicHUD.logger.error("Renderer not found for class: {} in the following skin: {}", this.getClass().getName(), properties.getSkin()); + throw new RuntimeException(); + } + } + + public void render(DrawContext drawContext, 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); + } + + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return isMouseOver(mouseX, mouseY); + } + + public boolean mouseReleased(double mouseX, double mouseY, int button) { + return isMouseOver(mouseX, mouseY); + } + + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + return isMouseOver(mouseX, mouseY); + } + + public void mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + } + + @Override + public void keyPressed(int key, int scanCode, int modifiers) { + } + + @Override + public void charTyped(char c, int modifiers) { + } + + @Override + public void keyReleased(int key, int scanCode, int modifiers) { + } + + /** + * Called when the context menu closes + */ + public void onClose() { + } + + public boolean isMouseOver(double mouseX, double mouseY) { + return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; + } + + public Option renderWhen(Supplier shouldRender) { + this.shouldRender = shouldRender; + return this; + } + + public boolean shouldRender() { + return shouldRender.get() && GlobalConfig.get().complexity().ordinal() >= complexity.ordinal(); + } + + public void reset() { + this.value = get(); + } + + public ContextMenuProperties getProperties() { + return properties; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public void setPosition(int x, int y) { + this.x = x; + this.y = y; + } + + public Option description(Text description) { + this.description = description; + return this; + } + + public Option withComplexity(Complexity complexity) { + this.complexity = complexity; + return this; + } + + public SkinRenderer> getRenderer() { + return renderer; + } + + public Text getName() { + return name; + } + + public Text getDescription() { + return description; + } + + @Override + public String toString() { + return this.getClass().getName() + "{" + + ", name=" + name + + ", x=" + x + + ", y=" + y + + '}'; + } + + /** + * How complex do you think this option is for a daily normal user. + * For example, some options may be advanced and not needed for casual players but are helpful in customisation + */ + public enum Complexity { + Simple, + Enhanced, + Pro + } +} 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 new file mode 100644 index 0000000..99097fd --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/OptionGroup.java @@ -0,0 +1,130 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.options; + +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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +// A group is just another type of Option that contains other options +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) { + super(name, () -> null, (v) -> {}, () -> true); + this.expanded = false; + } + + public void addOption(Option option) { + groupOptions.add(option); + } + + public List> getGroupOptions() { + return Collections.unmodifiableList(groupOptions); + } + + @Override + public void updateProperties(ContextMenuProperties properties) { + super.updateProperties(properties); + if (groupOptions == null) return; + + for (Option option : groupOptions) { + option.updateProperties(properties); + } + } + + @Override + public void render(DrawContext drawContext, int x, int y, int mouseX, int mouseY) { + super.render(drawContext,x,y,mouseX,mouseY); + } + + public boolean isExpanded() { + return expanded; + } + + public void setExpanded(boolean expanded) { + this.expanded = expanded; + } + + public int getHeightOfOptions() { + int height = 0; + for (Option option : getGroupOptions()) { + height += option.getHeight() + 1; + } + return height; + } + + @Override + public int getHeight() { + return super.getHeight(); + } + + public static class OptionGroupRenderer implements SkinRenderer> { + @Override + public void render(DrawContext drawContext, 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); + } + return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); + } + + @Override + public boolean mouseReleased(Option option2, double mouseX, double mouseY, int button) { + OptionGroup option = (OptionGroup) option2; + + for (Option subOption : option.getGroupOptions()) { + subOption.getRenderer().mouseReleased(subOption, mouseX, mouseY, button); + } + return SkinRenderer.super.mouseReleased(option, mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(Option option2, double mouseX, double mouseY, int button, double deltaX, double deltaY) { + OptionGroup option = (OptionGroup) option2; + + for (Option subOption : option.getGroupOptions()) { + subOption.getRenderer().mouseDragged(subOption, mouseX, mouseY, button, deltaX, deltaY); + } + return SkinRenderer.super.mouseDragged(option, mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public void keyPressed(Option option2, int key, int scanCode, int modifiers) { + OptionGroup option = (OptionGroup) option2; + + for (Option subOption : option.getGroupOptions()) { + subOption.getRenderer().keyPressed(subOption, key, scanCode, modifiers); + } + SkinRenderer.super.keyPressed(option, key, scanCode, modifiers); + } + + @Override + public void keyReleased(Option option2, int key, int scanCode, int modifiers) { + OptionGroup option = (OptionGroup) option2; + + for (Option subOption : option.getGroupOptions()) { + subOption.getRenderer().keyReleased(subOption, key, scanCode, modifiers); + } + SkinRenderer.super.keyReleased(option, key, scanCode, modifiers); + } + + @Override + public void mouseScrolled(Option option2, double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + OptionGroup option = (OptionGroup) option2; + + for (Option subOption : option.getGroupOptions()) { + subOption.getRenderer().mouseScrolled(subOption, mouseX, mouseY, horizontalAmount, verticalAmount); + } + SkinRenderer.super.mouseScrolled(option, mouseX, mouseY, horizontalAmount, verticalAmount); + } + } +} \ No newline at end of file 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 c797189..8bc80a1 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 @@ -1,17 +1,14 @@ package com.tanishisherewith.dynamichud.utils.contextmenu.options; +import com.tanishisherewith.dynamichud.DynamicHUD; import com.tanishisherewith.dynamichud.utils.BooleanPool; -import com.tanishisherewith.dynamichud.utils.contextmenu.Option; -import net.minecraft.client.gui.DrawContext; import net.minecraft.text.Text; -import java.awt.*; import java.util.function.Consumer; import java.util.function.Supplier; public class RunnableOption extends Option { private final Runnable task; - public String name = "Empty"; /** * Runnable option which runs a task when clicked on it. @@ -21,40 +18,29 @@ 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(String name, Supplier getter, Consumer setter, Runnable task) { - super(getter, setter); - this.name = "Run: " + name; // prepend the "run" symbol to the name + public RunnableOption(Text name, Supplier getter, Consumer setter, Runnable task) { + super(name, getter, setter); + this.name = name; this.task = task; + this.renderer.init(this); } - public RunnableOption(String name, boolean defaultValue,Runnable task) { - this(name, () -> BooleanPool.get(name), value -> BooleanPool.put(name, value),task); - BooleanPool.put(name, defaultValue); - } - - Color DARK_RED = new Color(116, 0, 0); - Color DARK_GREEN = new Color(24, 132, 0, 226); - - @Override - public void render(DrawContext drawContext, int x, int y) { - super.render(drawContext, x, y); - - value = get(); - this.height = mc.textRenderer.fontHeight; - this.width = mc.textRenderer.getWidth("Run: " + name); - int color = value ? DARK_GREEN.getRGB() : DARK_RED.getRGB(); - drawContext.drawText(mc.textRenderer, Text.of(name), x, y, color, false); + public RunnableOption(Text name, boolean defaultValue, Runnable task) { + this(name, () -> BooleanPool.get(name.getString()), value -> BooleanPool.put(name.getString(), value), task); + BooleanPool.put(name.getString(), defaultValue); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - super.mouseClicked(mouseX, mouseY, button); - if (isMouseOver(mouseX, mouseY)) { - value = !value; - set(value); - if (value) { + 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); + return true; } return true; } 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 07a7b0b..36f7f9b 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 @@ -2,11 +2,9 @@ import com.tanishisherewith.dynamichud.utils.BooleanPool; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.utils.contextmenu.Option; -import net.minecraft.client.gui.DrawContext; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; import net.minecraft.text.Text; -import java.awt.*; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Supplier; @@ -19,42 +17,34 @@ * The {@link #setter} returns a boolean value depending on if the subMenu is visible or not */ public class SubMenuOption extends Option { - private final ContextMenu subMenu; - private final ContextMenu parentMenu; - public String name = "Empty"; + private final ContextMenu subMenu; - - public SubMenuOption(String name, ContextMenu parentMenu, Supplier getter, Consumer setter) { - super(getter, setter); - Objects.requireNonNull(parentMenu, "Parent Menu cannot be null"); - this.name = name; - this.parentMenu = parentMenu; - this.subMenu = new ContextMenu(parentMenu.x + parentMenu.finalWidth, this.y); - this.subMenu.heightOffset = 0; - this.subMenu.shouldDisplay = get(); - } - public SubMenuOption(String name, ContextMenu parentMenu) { - this(name, parentMenu, () -> BooleanPool.get(name), value -> BooleanPool.put(name, value)); + public SubMenuOption(Text 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); } - @Override - public void render(DrawContext drawContext, int x, int y,int mouseX,int mouseY) { - this.x = x; - this.y = y; + public SubMenuOption(Text name, ContextMenu parentMenu, T properties) { + this(name, parentMenu, () -> BooleanPool.get(name.getString()), value -> BooleanPool.put(name.getString(), value), properties); + } - int color = value ? Color.GREEN.getRGB() : Color.RED.getRGB(); - drawContext.drawText(mc.textRenderer, Text.of(name), x, y + 1, color, false); - this.height = mc.textRenderer.fontHeight + 2; - this.width = mc.textRenderer.getWidth(name) + 1; + public SubMenuOption(Text name, ContextMenu parentMenu, Supplier getter, Consumer setter) { + this(name, parentMenu, getter, setter, parentMenu.getProperties().cloneWithSkin()); + } - subMenu.render(drawContext, this.x + parentMenu.finalWidth, this.y, 0,mouseX, mouseY); + public SubMenuOption(Text 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.shouldDisplay); + set(subMenu.isVisible()); return true; } subMenu.mouseClicked(mouseX, mouseY, button); @@ -68,16 +58,20 @@ public boolean mouseReleased(double mouseX, double mouseY, int button) { } @Override - public boolean mouseDragged(double mouseX, double mouseY, int button) { - subMenu.mouseDragged(mouseX, mouseY, button); - return super.mouseDragged(mouseX, mouseY, button); + 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); } public SubMenuOption getOption() { return this; } - public ContextMenu getSubMenu() { + public ContextMenu getSubMenu() { return subMenu; } + + public ContextMenu getParentMenu() { + return subMenu.getParentMenu(); + } } 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 c7a8794..69d8ece 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 @@ -22,7 +22,7 @@ public AlphaSlider(int x, int y, int width, int height, Color color) { this.color = color; this.x = x; this.y = y; - alpha = color.getAlpha() / 255f; + this.alpha = color.getAlpha() / 255f; } public void render(DrawContext drawContext, int x, int y) { @@ -63,6 +63,14 @@ public void setX(int x) { this.x = x; } + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + public boolean isMouseOver(double mouseX, double mouseY) { return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; } 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 new file mode 100644 index 0000000..a035bdc --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradient.java @@ -0,0 +1,156 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; + +import com.tanishisherewith.dynamichud.config.GlobalConfig; +import com.tanishisherewith.dynamichud.helpers.ColorHelper; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; + +import java.awt.*; +import java.util.function.Consumer; + +public class ColorGradient { + final MinecraftClient client = MinecraftClient.getInstance(); + private final Consumer onColorSelected; // The callback to call when a color is selected + private final HueSlider gradientSlider; + private final SaturationHueBox gradientBox; + private final ColorPickerButton colorPickerButton; + private final AlphaSlider alphaSlider; + private final int boxSize; + private int x, y; + private boolean display = false; + + public ColorGradient(int x, int y, Color initialColor, Consumer onColorSelected, int boxSize, int colors) { + this.x = x; + this.y = y; + this.onColorSelected = onColorSelected; + this.gradientSlider = new HueSlider(x, y, colors, 10); + this.gradientBox = new SaturationHueBox(x, y + 20, boxSize); + this.alphaSlider = new AlphaSlider(x, y, 10, boxSize, initialColor); + + float[] hsv = Color.RGBtoHSB(initialColor.getRed(), initialColor.getGreen(), initialColor.getBlue(), null); + + this.boxSize = boxSize; + this.gradientBox.setHue(hsv[0]); + this.gradientBox.setSaturation(hsv[1]); + this.gradientBox.setValue(hsv[2]); + this.gradientSlider.setHue(hsv[0]); + + this.colorPickerButton = new ColorPickerButton(x + boxSize + 8, y + 20, 30, 18); + } + + public void setPos(int x, int y) { + this.x = x; + this.y = y; + } + + public void display() { + display = true; + } + + public void close() { + display = false; + } + + public void render(DrawContext drawContext, 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()) { + int[] colors = ColorHelper.getMousePixelColor(mouseX, mouseY); + 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(); + } + } + } + + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!display) { + return false; + } + /*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())); + alphaSlider.onClick(mouseX, mouseY, button); + onColorSelected.accept(alphaSlider.getColor()); + + return true; + } + + public void mouseReleased(double mouseX, double mouseY, int button) { + gradientSlider.onRelease(mouseX, mouseY, button); + gradientBox.onRelease(mouseX, mouseY, button); + alphaSlider.onRelease(mouseX, mouseY, button); + } + + public void mouseDragged(double mouseX, double mouseY, int button) { + if (!display) { + return; + } + gradientSlider.onDrag(mouseX, mouseY, button); + gradientBox.setHue(gradientSlider.getHue()); + gradientBox.onDrag(mouseX, mouseY, button); + alphaSlider.setColor(new Color(gradientBox.getColor())); + alphaSlider.onDrag(mouseX, mouseY, button); + onColorSelected.accept(alphaSlider.getColor()); + } + + public int getBoxSize() { + return boxSize; + } + + public boolean shouldDisplay() { + return display; + } + + public ColorPickerButton getColorPickerButton() { + return colorPickerButton; + } + + public AlphaSlider getAlphaSlider() { + return alphaSlider; + } + + public HueSlider getGradientSlider() { + return gradientSlider; + } + + public SaturationHueBox getGradientBox() { + return gradientBox; + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradientPicker.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradientPicker.java deleted file mode 100644 index 65a15c9..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradientPicker.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; - -import com.tanishisherewith.dynamichud.config.GlobalConfig; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.gui.DrawContext; -import org.lwjgl.opengl.GL11; - -import java.awt.*; -import java.nio.ByteBuffer; -import java.util.function.Consumer; - -public class ColorGradientPicker { - final MinecraftClient client = MinecraftClient.getInstance(); - private final Consumer onColorSelected; // The callback to call when a color is selected - private final GradientSlider gradientSlider; - private final GradientBox gradientBox; - private final ColorPickerButton colorPickerButton; - private final AlphaSlider alphaSlider; - private final int boxSize; - private int x, y; - private boolean display = false; - private final Color initialColor; - - public ColorGradientPicker(int x, int y, Color initialColor, Consumer onColorSelected, int boxSize, int colors) { - this.x = x; - this.y = y; - this.initialColor = initialColor; - this.onColorSelected = onColorSelected; - this.gradientSlider = new GradientSlider(x, y, colors, 10); - this.gradientBox = new GradientBox(x, y + 20, boxSize); - this.alphaSlider = new AlphaSlider(x, y, 10, boxSize, initialColor); - - float[] hsv = new float[3]; - Color.RGBtoHSB(initialColor.getRed(), initialColor.getGreen(), initialColor.getBlue(), hsv); - - this.boxSize = boxSize; - this.gradientSlider.setHue(hsv[0]); - this.gradientBox.setHue(hsv[0]); - this.gradientBox.setSaturation(hsv[1]); - this.gradientBox.setValue(hsv[2]); - - this.colorPickerButton = new ColorPickerButton(x + boxSize + 8, y + 20, 30, 18); - } - - public void setPos(int x, int y) { - this.x = x; - this.y = y; - } - - public void display() { - display = true; - } - - public void close() { - display = false; - } - - public void render(DrawContext drawContext, int x1, int y1) { - 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()) { - // Draw the preview box near cursor - - //Translate cursor screen position to minecraft's scaled window - double mouseX = client.mouse.getX() * client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth(); - double mouseY = client.mouse.getY() * client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight(); - - Framebuffer framebuffer = client.getFramebuffer(); - int x = (int) (mouseX * framebuffer.textureWidth / client.getWindow().getScaledWidth()); - int y = (int) ((client.getWindow().getScaledHeight() - mouseY) * framebuffer.textureHeight / client.getWindow().getScaledHeight()); - - //Read the pixel color at x,y pos to buffer - ByteBuffer buffer = ByteBuffer.allocate(4); - GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); - int red = buffer.get(0) & 0xFF; - int green = buffer.get(1) & 0xFF; - int blue = buffer.get(2) & 0xFF; - - drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0, 0, 500); - drawContext.fill((int) mouseX + 10, (int) mouseY, (int) mouseX + 26, (int) mouseY + 16, -1); - drawContext.fill((int) mouseX + 11, (int) mouseY + 1, (int) mouseX + 25, (int) mouseY + 15, (red << 16) | (green << 8) | blue | 0xFF000000); - drawContext.getMatrices().pop(); - } - } - - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!display) { - return false; - } - 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()) { - Framebuffer framebuffer = client.getFramebuffer(); - int x = (int) (mouseX * framebuffer.textureWidth / client.getWindow().getScaledWidth()); - int y = (int) ((client.getWindow().getScaledHeight() - mouseY) * framebuffer.textureHeight / client.getWindow().getScaledHeight()); - - ByteBuffer buffer = ByteBuffer.allocate(4); - GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); - int red = buffer.get(0) & 0xFF; - int green = buffer.get(1) & 0xFF; - int blue = buffer.get(2) & 0xFF; - - float[] hsv = Color.RGBtoHSB(red, green, blue, null); - gradientSlider.setHue(hsv[0]); - gradientBox.setHue(hsv[0]); - gradientBox.setSaturation(hsv[1]); - gradientBox.setValue(hsv[2]); - - colorPickerButton.setPicking(false); - } - alphaSlider.setColor(new Color(gradientBox.getColor())); - alphaSlider.onClick(mouseX, mouseY, button); - onColorSelected.accept(alphaSlider.getColor()); - return true; - } - - public void mouseReleased(double mouseX, double mouseY, int button) { - gradientSlider.onRelease(mouseX, mouseY, button); - gradientBox.onRelease(mouseX, mouseY, button); - alphaSlider.onRelease(mouseX, mouseY, button); - } - - public void mouseDragged(double mouseX, double mouseY, int button) { - if (!display) { - return; - } - gradientSlider.onDrag(mouseX, mouseY, button); - gradientBox.setHue(gradientSlider.getHue()); - gradientBox.onDrag(mouseX, mouseY, button); - alphaSlider.setColor(new Color(gradientBox.getColor())); - alphaSlider.onDrag(mouseX, mouseY, button); - onColorSelected.accept(alphaSlider.getColor()); - } - -} 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 fa2b8f1..4d9b711 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 @@ -3,6 +3,8 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; +import java.awt.*; + public class ColorPickerButton { private final int width; private final int height; @@ -23,7 +25,7 @@ public void render(DrawContext drawContext, int x, int 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, 0xFFAAAAAA); + 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(); } @@ -32,6 +34,10 @@ public int getHeight() { return height; } + public int getWidth() { + return width; + } + public boolean onClick(double mouseX, double mouseY, int button) { if (button == 0) { if (mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height) { diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientSlider.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/HueSlider.java similarity index 91% rename from src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientSlider.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/HueSlider.java index 39f1ff7..7f8298c 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientSlider.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/HueSlider.java @@ -5,7 +5,7 @@ import java.awt.*; -public class GradientSlider { +public class HueSlider { private final int width; private final int height; private int x; @@ -13,7 +13,7 @@ public class GradientSlider { private float hue = 0.0f; private boolean isDragging = false; - public GradientSlider(int x, int y, int width, int height) { + public HueSlider(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; @@ -44,6 +44,7 @@ public void render(DrawContext drawContext, int x, int y) { color = (color & 0x00FFFFFF) | (255 << 24); drawContext.fill(x + i, y, x + i + 1, y + height, color); } + drawContext.draw(); // Draw the handle @@ -70,7 +71,7 @@ public void onClick(double mouseX, double mouseY, int button) { if (mouseX >= handleX && mouseX <= handleX + handleWidth && mouseY >= handleY && mouseY <= handleY + handleHeight) { this.isDragging = true; } else if (mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height) { - hue = (float) (mouseX - x) / width; + this.hue = (float) (mouseX - x) / this.width; this.isDragging = true; } } @@ -88,9 +89,8 @@ public void onRelease(double mouseX, double mouseY, int button) { public void onDrag(double mouseX, double mouseY, int button) { if (isDragging) { - hue = (float) (mouseX - x) / width; - hue = Math.max(0, hue); - hue = Math.min(1, hue); + this.hue = (float) (mouseX - x) / this.width; + this.hue = Math.clamp(this.hue, 0.0f, 1.0f); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientBox.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/SaturationHueBox.java similarity index 79% rename from src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientBox.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/SaturationHueBox.java index 49d7699..e0eca23 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientBox.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/SaturationHueBox.java @@ -5,7 +5,7 @@ import java.awt.*; -public class GradientBox { +public class SaturationHueBox { private final int size; private int x; private int y; @@ -15,7 +15,7 @@ public class GradientBox { private boolean isDragging = false; - public GradientBox(int x, int y, int size) { + public SaturationHueBox(int x, int y, int size) { this.x = x; this.y = y; this.size = size; @@ -31,11 +31,11 @@ public void render(DrawContext drawContext, int x, int y) { DrawHelper.drawRoundedGradientRectangle(drawContext.getMatrices().peek().getPositionMatrix(), Color.BLACK, Color.BLACK, Color.getHSBColor(hue, 1.0f, 1.0f), Color.WHITE, x, y, size, size, 2); // Draw the handle - float handleSize = 3; + 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.getMatrices().peek().getPositionMatrix(), handleX, handleY, 1, -1); + DrawHelper.drawFilledCircle(drawContext.getMatrices().peek().getPositionMatrix(), handleX, handleY, handleSize, -1); drawContext.getMatrices().pop(); } @@ -60,8 +60,11 @@ public void onClick(double mouseX, double mouseY, int button) { if (mouseX >= handleX && mouseX <= handleX + handleSize && mouseY >= handleY && mouseY <= handleY + handleSize) { this.isDragging = true; } else if (mouseX >= x && mouseX <= x + size && mouseY >= y && mouseY <= y + size) { - saturation = (float) (mouseX - x) / size; - value = 1.0f - (float) (mouseY - y) / size; + this.saturation = (float) (mouseX - x) / size; + this.value = 1.0f - (float) (mouseY - y) / size; + + this.saturation = Math.clamp(saturation, 0.0f, 1.0f); + this.value = Math.clamp(value, 0.0f, 1.0f); this.isDragging = true; } } @@ -79,13 +82,11 @@ public void onRelease(double mouseX, double mouseY, int button) { public void onDrag(double mouseX, double mouseY, int button) { if (isDragging) { - saturation = (float) (mouseX - x) / size; - saturation = Math.max(0, saturation); - saturation = Math.min(1, saturation); + this.saturation = (float) (mouseX - x) / size; + this.value = 1.0f - (float) (mouseY - y) / size; - value = 1.0f - (float) (mouseY - y) / size; - value = Math.max(0, value); - value = Math.min(1, value); + this.saturation = Math.clamp(saturation, 0.0f, 1.0f); + this.value = Math.clamp(value, 0.0f, 1.0f); } } @@ -104,4 +105,8 @@ public void setValue(float value) { public int getColor() { return Color.HSBtoRGB(hue, saturation, value); } + + public int getSize() { + return size; + } } 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 new file mode 100644 index 0000000..fec9a29 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ClassicSkin.java @@ -0,0 +1,271 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem; + +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 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(SubMenuOption.class, ClassicSubMenuRenderer::new); + addRenderer(RunnableOption.class, ClassicRunnableRenderer::new); + addRenderer(ColorOption.class, ClassicColorOptionRenderer::new); + + setCreateNewScreen(false); + } + + @Override + public void renderContextMenu(DrawContext drawContext, ContextMenu contextMenu, int mouseX, int mouseY) { + this.contextMenu = contextMenu; + + MatrixStack matrices = drawContext.getMatrices(); + ContextMenuProperties properties = contextMenu.getProperties(); + + // Draw the background + drawBackground(matrices, 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(matrices, contextMenu, properties, yOffset - 1, contextMenu.getWidth(), option.getHeight() + 1, contextMenu.getProperties().getHoverColor().getRGB(), false); + } + + option.render(drawContext, contextMenu.x + 4, yOffset, mouseX, mouseY); + width = Math.max(width, option.getWidth()); + yOffset += option.getHeight() + 1; + } + contextMenu.setWidth(width + properties.getPadding()); + contextMenu.setHeight(yOffset - contextMenu.y); + + // Draw the border if needed + if (properties.shouldDrawBorder()) { + drawBorder(matrices, contextMenu, properties); + } + } + + private void drawBackground(MatrixStack matrices, ContextMenu contextMenu, ContextMenuProperties properties) { + drawBackground(matrices, contextMenu, properties, contextMenu.y, contextMenu.getWidth(), contextMenu.getHeight(), properties.getBackgroundColor().getRGB(), properties.shadow()); + } + + private void drawBackground(MatrixStack matrices, ContextMenu contextMenu, ContextMenuProperties properties, int yOffset, int width, int height, int color, boolean shadow) { + if (properties.roundedCorners()) { + // Rounded + if (shadow) { + DrawHelper.drawRoundedRectangleWithShadowBadWay(matrices.peek().getPositionMatrix(), + contextMenu.x, + yOffset, + width, + height, + properties.getCornerRadius(), + color, + 150, + 1, + 1 + ); + } else { + DrawHelper.drawRoundedRectangle(matrices.peek().getPositionMatrix(), + contextMenu.x, + yOffset, + width, + height, + properties.getCornerRadius(), + color + ); + } + } else { + // Normal + if (shadow) { + DrawHelper.drawRectangleWithShadowBadWay(matrices.peek().getPositionMatrix(), + contextMenu.x, + yOffset, + width, + height, + color, + 150, + 1, + 1 + ); + } else { + DrawHelper.drawRectangle(matrices.peek().getPositionMatrix(), + contextMenu.x, + yOffset, + width, + height, + color + ); + } + } + } + + private void drawBorder(MatrixStack matrices, ContextMenu contextMenu, ContextMenuProperties properties) { + if (properties.roundedCorners()) { + DrawHelper.drawOutlineRoundedBox(matrices.peek().getPositionMatrix(), + contextMenu.x, + contextMenu.y, + contextMenu.getWidth(), + contextMenu.getHeight(), + properties.getCornerRadius(), + properties.getBorderWidth(), + properties.getBorderColor().getRGB() + ); + } else { + DrawHelper.drawOutlineBox(matrices.peek().getPositionMatrix(), + contextMenu.x, + contextMenu.y, + contextMenu.getWidth(), + contextMenu.getHeight(), + properties.getBorderWidth(), + properties.getBorderColor().getRGB() + ); + } + } + + @Override + 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 static class ClassicColorOptionRenderer implements SkinRenderer { + @Override + public void render(DrawContext drawContext, 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); + + int shadowOpacity = Math.min(option.value.getAlpha(), 90); + DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext.getMatrices().peek().getPositionMatrix(), + x + option.getWidth() - 10 + 1, + y - 1, + 8, + 8, + 2, + option.value.getRGB(), + shadowOpacity, + 1, + 1); + + option.getColorGradient().render(drawContext, x + option.getParentMenu().getWidth() + 7, y - 10, mouseX, mouseY); + } + } + + public static class ClassicEnumRenderer> 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 static class ClassicSubMenuRenderer implements SkinRenderer { + @Override + public void render(DrawContext drawContext, 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); + + 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); + } + } + + 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 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()); + + 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); + + // Draw the slider + option.drawSlider(drawContext, x, y + textRenderer.fontHeight + 1, option.getWidth(), handleX); + + // Draw the handle + DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext.getMatrices().peek().getPositionMatrix(), + (float) handleX, + (float) handleY, + handleWidth, + handleHeight, + 1, + 0xFFFFFFFF, + 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 new file mode 100644 index 0000000..ed49960 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/MinecraftSkin.java @@ -0,0 +1,647 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.tanishisherewith.dynamichud.DynamicHUD; +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.layout.LayoutContext; +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.gl.ShaderProgramKeys; +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 org.lwjgl.glfw.GLFW; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.function.IntSupplier; + +/** + * 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 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 imageX, imageY; + private final ScrollHandler scrollHandler; + + private List optionGroups; + private OptionGroup selectedGroup; + private final ScrollHandler groupScrollHandler; + private final int groupPanelWidth = 60; // Width for the group panel + private final IntSupplier groupPanelX = () -> imageX - groupPanelWidth - 15; + + 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(SubMenuOption.class, MinecraftSubMenuRenderer::new); + addRenderer(RunnableOption.class, MinecraftRunnableRenderer::new); + addRenderer(ColorOption.class, MinecraftColorOptionRenderer::new); + + this.panelHeight = DEFAULT_PANEL_HEIGHT; + this.panelWidth = DEFAULT_PANEL_WIDTH; + this.BACKGROUND_PANEL = DEFAULT_BACKGROUND_PANEL; + this.scrollHandler = new ScrollHandler(); + this.groupScrollHandler = new ScrollHandler(); + + setCreateNewScreen(true); + } + + private void enableContextMenuScissor() { + DrawHelper.enableScissor(0, imageY + 3, mc.getWindow().getScaledWidth(), panelHeight - 8); + } + + private void createGroups() { + OptionGroup generalGroup = new OptionGroup(Text.of("General")); + for (Option option : getOptions(contextMenu)) { + if (option instanceof OptionGroup og) { + optionGroups.add(og); + og.setExpanded(false); + } else { + generalGroup.addOption(option); + } + } + optionGroups.addFirst(generalGroup); + } + + private void initOptionGroups() { + if (this.optionGroups == null) { + this.optionGroups = new ArrayList<>(); + createGroups(); + selectedGroup = optionGroups.getFirst(); // Default to the first group + selectedGroup.setExpanded(true); + scrollHandler.updateScrollPosition(0); + } + } + + @Override + public void renderContextMenu(DrawContext drawContext, ContextMenu contextMenu, int mouseX, int mouseY) { + this.contextMenu = contextMenu; + + initOptionGroups(); + + int screenWidth = mc.getWindow().getScaledWidth(); + int screenHeight = mc.getWindow().getScaledHeight(); + + int centerX = screenWidth / 2; + int centerY = screenHeight / 2; + + contextMenu.set(centerX, centerY, 0); + + // Calculate the top-left corner of the image + imageX = (screenWidth - panelWidth + 25) / 2; + imageY = (screenHeight - panelHeight) / 2; + + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + + drawContext.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_PANEL, imageX, imageY, 0, 0, panelWidth, panelHeight, 256, 254,panelColor.getColor()); + + drawSingularButton(drawContext, "X", mouseX, mouseY, imageX + 3, imageY + 3, 14, 14); + + //Up and down arrows near the group panel + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + + 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); + + this.enableContextMenuScissor(); + + contextMenu.setWidth(panelWidth - 4); + contextMenu.y = imageY; + + renderOptionGroups(drawContext, mouseX, mouseY); + renderSelectedGroupOptions(drawContext, mouseX, mouseY); + + contextMenu.setHeight(getContentHeight() + 15); + + drawScrollbar(drawContext); + + scrollHandler.updateScrollOffset(getMaxScrollOffset()); + + // Disable scissor after rendering + DrawHelper.disableScissor(); + } + + 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(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); + } + + private void renderOptionGroups(DrawContext drawContext, int mouseX, int mouseY) { + int groupX = groupPanelX.getAsInt(); + int groupY = imageY; + + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + 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); + + 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); + + DrawHelper.drawScrollableText(drawContext, mc.textRenderer, 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(); + + yOffset += 20; // Space for the header + } + yOffset += 10; // Space for the group + } + + groupScrollHandler.updateScrollOffset(yOffset - groupY - 12 + groupScrollHandler.getScrollOffset() - panelHeight); + + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + } + + private int renderSelectedGroupOptions(DrawContext drawContext, int mouseX, int mouseY) { + int yOffset = imageY + 12 - scrollHandler.getScrollOffset(); + for (Option option : selectedGroup.getGroupOptions()) { + if (!option.shouldRender()) continue; + + if (yOffset >= imageY - option.getHeight() && yOffset <= imageY + option.getHeight() + panelHeight) { + option.render(drawContext, imageX + 4, yOffset, mouseX, mouseY); + } + yOffset += option.getHeight() + 1; + } + return yOffset; + } + + private void drawScrollbar(DrawContext drawContext) { + if (getMaxScrollOffset() > 0) { + int scrollbarX = imageX + panelWidth + 10; + int scrollbarY = imageY; + double ratio = (double) panelHeight / getContentHeight(); + double handleHeight = panelHeight * ratio; + int handleY = (int) (scrollbarY + (panelHeight - handleHeight) * ((double) scrollHandler.getScrollOffset() / getMaxScrollOffset())); + + RenderSystem.enableBlend(); + 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); + RenderSystem.disableBlend(); + } + } + + private int getMaxScrollOffset() { + return getContentHeight() - panelHeight + 10; + } + + private int getContentHeight() { + return selectedGroup.getHeightOfOptions() + 10; + } + + @Override + public void mouseScrolled(ContextMenu menu, double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + if (isMouseOver(mouseX, mouseY, imageX, imageY, panelWidth, panelHeight)) { + scrollHandler.mouseScrolled(verticalAmount); + } + if (isMouseOver(mouseX, mouseY, groupPanelX.getAsInt() - 10, imageY, groupPanelWidth + 10, panelHeight)) { + groupScrollHandler.mouseScrolled(verticalAmount); + } + } + + @Override + public boolean mouseClicked(ContextMenu menu, double mouseX, double mouseY, int button) { + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + if (isMouseOver(mouseX, mouseY, imageX + 3, imageY + 3, 14, 14)) { + mc.getSoundManager().play(PositionedSoundInstance.master( + SoundEvents.UI_BUTTON_CLICK, 1.0F)); + + contextMenu.close(); + scrollHandler.stopDragging(); + return true; + } + 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( + 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( + SoundEvents.UI_BUTTON_CLICK, 1.0F)); + groupScrollHandler.addOffset(10); + } + + if (isMouseOver(mouseX, mouseY, imageX + panelWidth + 10, imageY, 10, panelHeight)) { + scrollHandler.startDragging(mouseY); + return true; + } + int groupX = groupPanelX.getAsInt(); + int groupY = imageY; + + int yOffset = groupY + 10 - groupScrollHandler.getScrollOffset(); + for (OptionGroup group : optionGroups) { + if (yOffset >= groupY && yOffset <= groupY + panelHeight) { + // Handle click to select the group + if (isMouseOver(mouseX, mouseY, groupX + 10, yOffset, groupPanelWidth - 20, 20)) { + selectedGroup.setExpanded(false); + selectedGroup = group; + selectedGroup.setExpanded(true); + } + yOffset += 20; + } + yOffset += 10; + } + } + + return super.mouseClicked(menu, mouseX, mouseY, button); + } + + @Override + public boolean mouseReleased(ContextMenu menu, double mouseX, double mouseY, int button) { + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + scrollHandler.stopDragging(); + groupScrollHandler.stopDragging(); + } + return super.mouseReleased(menu, mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(ContextMenu menu, double mouseX, double mouseY, int button, double deltaX, double 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); + } + return true; + } + return super.mouseDragged(menu, mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public LayoutContext.Offset getGroupIndent() { + return LayoutContext.Offset.zero(); + } + + public void setPanelColor(PanelColor panelColor) { + this.panelColor = panelColor; + } + + public PanelColor getPanelColor() { + return panelColor; + } + + public int getPanelHeight() { + return panelHeight; + } + + public int getPanelWidth() { + return panelWidth; + } + + public int getImageX() { + return imageX; + } + + public int getImageY() { + return imageY; + } + + /** + * Group rendering handled already + */ + @Override + public void renderGroup(DrawContext drawContext, OptionGroup group, int groupX, int groupY, int mouseX, int mouseY) { + } + + public enum PanelColor { + COFFEE_BROWN(0.6f, 0.3f, 0.1f, 1.0f), + CREAMY(1.0f, 0.9f, 0.8f, 1.0f), + DARK_PANEL(0.2f, 0.2f, 0.2f, 1.0f), + FOREST_GREEN(0.0f, 0.6f, 0.2f, 1.0f), + GOLDEN_YELLOW(1.0f, 0.8f, 0.0f, 1.0f), + LAVENDER(0.8f, 0.6f, 1.0f, 1.0f), + LIGHT_BLUE(0.6f, 0.8f, 1.0f, 1.0f), + LIME_GREEN(0.7f, 1.0f, 0.3f, 1.0f), + MIDNIGHT_PURPLE(0.3f, 0.0f, 0.5f, 1.0f), + OCEAN_BLUE(0.0f, 0.5f, 1.0f, 1.0f), + ROSE_PINK(1.0f, 0.4f, 0.6f, 1.0f), + SKY_BLUE(0.5f, 0.8f, 1.0f, 1.0f), + SOFT_GREEN(0.6f, 1.0f, 0.6f, 1.0f), + SUNSET_ORANGE(1.0f, 0.5f, 0.0f, 1.0f), + WARM_YELLOW(1.0f, 1.0f, 0.6f, 1.0f), + CUSTOM(0.0f, 0.0f, 0.0f, 0.0f); /// PlaceHolder for custom colors + + private float red; + private float green; + private float blue; + private float alpha; + + PanelColor(float red, float green, float blue, float alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + + public static PanelColor custom(float red, float green, float blue, float alpha) { + PanelColor custom = CUSTOM; + custom.red = red; + custom.green = green; + custom.blue = blue; + custom.alpha = alpha; + return custom; + } + + public void applyColor() { + RenderSystem.setShaderColor(red, green, blue, alpha); + } + public int getColor() { + return new Color(red,green,blue,alpha).getRGB(); + } + } + + 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); + + int width = 50; + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(true, option.isMouseOver(mouseX, mouseY)), option.getX(), y, width, 20); + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + Text text = 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); + } + + @Override + public boolean mouseClicked(BooleanOption option, double mouseX, double mouseY, int button) { + return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); + } + } + + 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); + + option.setPosition(x + panelWidth - 45, y); + + int width = 20; + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableBlend(); + drawContext.drawGuiTexture(RenderLayer::getGuiTextured, TEXTURES.get(!option.isVisible, option.isMouseOver(mouseX, mouseY)), option.getX(), y, width, 20); + RenderSystem.disableBlend(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + + int shadowOpacity = Math.min(option.value.getAlpha(), 45); + drawContext.draw(); + DrawHelper.drawRectangleWithShadowBadWay(drawContext.getMatrices().peek().getPositionMatrix(), + option.getX() + 4, + y + 4, + width - 8, + 20 - 8, + 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 + } + //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); + + if (option.getColorGradient().shouldDisplay()) { + int colorGradientHeight = option.getColorGradient().getBoxSize() + 10 + option.getColorGradient().getGradientSlider().getHeight(); + option.setHeight(option.getHeight() + colorGradientHeight); + } + + if (option.getColorGradient().getColorPickerButton().isPicking()) { + DrawHelper.enableScissor(imageX, imageY + 2, panelWidth, panelHeight - 4); + } + } + } + + 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 void init(DoubleOption option) { + SkinRenderer.super.init(option); + } + + @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); + + 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.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + + 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); + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + // Determine the number of decimal places in option.step + 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); + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + 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(); + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + drawContext.drawText(mc.textRenderer, text, option.getX() + maxWidth / 2 - mc.textRenderer.getWidth(text) / 2, y + 5, Color.CYAN.getRGB(), true); + } + } + + public class MinecraftListRenderer implements SkinRenderer> { + private int maxWidth = 50; + + private void calculateMaxWidth(ListOption option) { + for (E listValues : option.getValues()) { + int width = mc.textRenderer.getWidth(listValues.toString()) + 5; + if (width > maxWidth) { + maxWidth = width; + } + } + } + + @Override + public void render(DrawContext drawContext, ListOption 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); + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + 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(); + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + drawContext.drawText(mc.textRenderer, text, option.getX() + maxWidth / 2 - mc.textRenderer.getWidth(text) / 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); + option.setWidth(30); + + drawContext.drawText(mc.textRenderer, 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); + RenderSystem.enableBlend(); + 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); + RenderSystem.disableBlend(); + + option.getSubMenu().render(drawContext, 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); + option.setWidth(26); + + drawContext.drawText(mc.textRenderer, 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); + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + 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); + RenderSystem.disableBlend(); + RenderSystem.disableDepthTest(); + } + } + + @Override + public Skin clone() { + return new MinecraftSkin(panelColor); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..df1203e --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/ModernSkin.java @@ -0,0 +1,958 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem; + +import com.mojang.blaze3d.systems.RenderSystem; +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.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutContext; +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 org.lwjgl.glfw.GLFW; + +import java.awt.*; +import java.util.Arrays; +import java.util.List; + +public class ModernSkin extends Skin implements GroupableSkin { + static Color DARK_GRAY = new Color(20, 20, 20, 229); + 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 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 static int SCALE_FACTOR = 4; + private final ScrollHandler scrollHandler; + + + public ModernSkin(Color themeColor, float radius, Text defaultToolTipHeader, Text defaultToolTipText) { + this.themeColor = themeColor; + this.radius = radius; + TOOLTIP_TEXT = defaultToolTipText; + 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(SubMenuOption.class, ModernSubMenuRenderer::new); + addRenderer(RunnableOption.class, ModernRunnableRenderer::new); + addRenderer(ColorOption.class, ModernColorOptionRenderer::new); + + this.scrollHandler = new ScrollHandler(); + + setCreateNewScreen(true); + } + + 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!")); + } + + public ModernSkin(Color themeColor) { + this(themeColor, 4); + } + + public ModernSkin() { + this(Color.CYAN.darker().darker()); + } + + @Override + public LayoutContext.Offset getGroupIndent() { + return new LayoutContext.Offset(2, 2); + } + + public void enableSkinScissor() { + DrawHelper.enableScissor(contextMenuX + (int) (width * 0.2f) + 10, contextMenuY + 19, (int) (width * 0.8f - 14), height - 23, SCALE_FACTOR); + } + + @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); + + if (group.isExpanded() && group.getHeight() > 20) { + DrawHelper.drawRoundedRectangle(drawContext.getMatrices().peek().getPositionMatrix(), + groupX + 1, groupY + 14, width - groupX - 8 + contextMenuX, group.getHeight() - 16, radius, DARKER_GRAY_2.getRGB()); + } + + Text groupText = group.name.copy().append(" " + (group.isExpanded() ? "-" : "+")); + + DrawHelper.drawRoundedRectangle(drawContext.getMatrices().peek().getPositionMatrix(), + groupX + 1, groupY + 1, true, true, !group.isExpanded(), !group.isExpanded(), mc.textRenderer.getWidth(groupText) + 6, 16, radius, DARKER_GRAY_2.getRGB()); + + drawContext.drawText(mc.textRenderer, groupText, groupX + 4, groupY + 4, -1, true); + + 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; + } + + group.setHeight(yOffset - groupY); + } else { + group.setHeight(20); + } + } + + private void drawScrollbar(DrawContext drawContext) { + if (getMaxScrollOffset() > 0) { + int scrollbarX = contextMenuX + width + 5; // Position at the right of the panel + int scrollbarY = contextMenuY + 19; // Position below the header + 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.getMatrices().peek().getPositionMatrix(), scrollbarX, scrollbarY, 2, height - 23, 1, DARKER_GRAY.getRGB()); + DrawHelper.drawRoundedRectangle(drawContext.getMatrices().peek().getPositionMatrix(), 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()); + + 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); + + updateContextDimensions(); + contextMenu.set(contextMenuX, contextMenuY, 0); + + //Background + DrawHelper.drawRoundedRectangle(drawContext.getMatrices().peek().getPositionMatrix(), + contextMenuX, contextMenuY, width, height, radius, DARKER_GRAY.getRGB()); + + drawBackButton(drawContext, mouseX, mouseY); + + //OptionStartX = Tool-Tip width + padding + int optionStartX = contextMenu.x + (int) (width * 0.2f) + 10; + + //Background behind the options + DrawHelper.drawRoundedRectangle(drawContext.getMatrices().peek().getPositionMatrix(), + optionStartX, contextMenuY + 19, width * 0.8f - 14, height - 23, radius, DARK_GRAY.getRGB()); + + enableSkinScissor(); + int yOffset = contextMenu.y + 19 + 3 - scrollHandler.getScrollOffset(); + for (Option option : getOptions(contextMenu)) { + if (!option.shouldRender()) continue; + + if (option.isMouseOver(mouseX, mouseY)) { + setTooltipText(option.name, option.description); + } + + if (option instanceof OptionGroup group) { + this.renderGroup(drawContext, group, optionStartX + 2, yOffset, mouseX, mouseY); + } else { + option.render(drawContext, optionStartX + 2, yOffset, mouseX, mouseY); + } + + yOffset += option.getHeight() + 1; + } + drawContext.draw(); + RenderSystem.disableScissor(); + + contextMenu.setWidth(width); + contextMenu.setHeight(yOffset - (contextMenu.y + 19 + 3 - scrollHandler.getScrollOffset()) + 4); + + scrollHandler.updateScrollOffset(getMaxScrollOffset()); + + drawScrollbar(drawContext); + + renderToolTipText(drawContext, mouseX, mouseY); + + //Reset our scaling so minecraft runs normally\ + DrawHelper.scaledProjection(); + } + + private void updateContextDimensions() { + scaledWidth = (float) mc.getWindow().getFramebufferWidth() / SCALE_FACTOR; + scaledHeight = (float) mc.getWindow().getFramebufferHeight() / SCALE_FACTOR; + contextMenuX = (int) (scaledWidth * 0.1f); + contextMenuY = (int) (scaledHeight * 0.1f); + width = (int) (scaledWidth * 0.8f); + height = (int) (scaledHeight * 0.8f); + } + + public void drawBackButton(DrawContext drawContext, int mouseX, int mouseY) { + String backText = "< Back"; + int textWidth = mc.textRenderer.getWidth(backText); + + boolean isHoveringOver = isMouseOver(mouseX, mouseY, contextMenuX + 2, contextMenuY + 2, textWidth + 8, 14); + int color = isHoveringOver ? themeColor.darker().getRGB() : themeColor.getRGB(); + + DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext.getMatrices().peek().getPositionMatrix(), + 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(); + } + + public void renderToolTipText(DrawContext drawContext, 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.min(height - 23, toolTipHeight); + } + + float textScale = 0.8f; + + // Draw background + DrawHelper.drawRoundedRectangle( + drawContext.getMatrices().peek().getPositionMatrix(), + contextMenuX + 2, + tooltipY, + toolTipWidth, + toolTipHeight, + radius, + DARK_GRAY.getRGB() + ); + DrawHelper.drawHorizontalLine( + drawContext.getMatrices().peek().getPositionMatrix(), + contextMenuX + 2, + toolTipWidth, + tooltipY + 16, + 0.7f, + ColorHelper.changeAlpha(Color.WHITE, 175).getRGB() + ); + + if (TOOLTIP_TEXT.getString().isEmpty() || TOOLTIP_HEAD.getString().isEmpty()) { + setTooltipText(defaultToolTipHeader, defaultToolTipText); + return; + } + + //Draw the head text + drawContext.drawText( + mc.textRenderer, + TOOLTIP_HEAD, + contextMenuX + 4, + tooltipY + 4, + -1, + true + ); + + List wrappedText = mc.textRenderer.wrapLines(StringVisitable.styled(TOOLTIP_TEXT.getString(), TOOLTIP_TEXT.getStyle()), toolTipWidth); + + DrawHelper.scaleAndPosition(drawContext.getMatrices(), contextMenuX + 4, tooltipY + 19, textScale); + + // Draw text + int textY = tooltipY + 19; + for (OrderedText line : wrappedText) { + drawContext.drawText( + mc.textRenderer, + line, + contextMenuX + 2 + 2, + textY, + -1, + false + ); + textY += mc.textRenderer.fontHeight; + } + + drawContext.draw(); + DrawHelper.stopScaling(drawContext.getMatrices()); + + setTooltipText(defaultToolTipHeader, defaultToolTipText); + } + + public void setTooltipText(Text head_text, Text tooltip_text) { + TOOLTIP_TEXT = tooltip_text; + TOOLTIP_HEAD = head_text; + } + + private int getMaxScrollOffset() { + return contextMenu.getHeight() - height + 23; + } + + @Override + public void mouseScrolled(ContextMenu menu, double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + super.mouseScrolled(menu, mouseX, mouseY, horizontalAmount, verticalAmount); + scrollHandler.mouseScrolled(verticalAmount); + } + + @Override + public boolean mouseReleased(ContextMenu menu, double mouseX, double mouseY, int button) { + scrollHandler.stopDragging(); + 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; + + if (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, contextMenuX + width - 5, contextMenuY, 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(); + for (Option option : getOptions(contextMenu)) { + if (!option.shouldRender()) continue; + + 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; + } + } + + 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( + SoundEvents.UI_BUTTON_CLICK, 1.0F)); + contextMenu.close(); + } + 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 (button == GLFW.GLFW_MOUSE_BUTTON_LEFT && isMouseOver(mouseX, mouseY, contextMenuX + width - 5, contextMenuY, 7, height)) { + scrollHandler.updateScrollPosition(mouseY); + } + return super.mouseDragged(menu, mouseX, mouseY, button, deltaX, deltaY); + } + + public Color getThemeColor() { + return themeColor; + } + + 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 + 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( + matrices.peek().getPositionMatrix(), + 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(matrices.peek().getPositionMatrix(), toggleX, y + 2 + 3.3f, 2.8f, Color.WHITE.getRGB()); + + // Draw option name + drawContext.drawText( + mc.textRenderer, + option.name, + x + 2, + y + 4, + -1, + false + ); + } + + @Override + public boolean mouseClicked(BooleanOption option, double mouseX, double mouseY, int button) { + mouseX = mc.mouse.getX() / SCALE_FACTOR; + mouseY = mc.mouse.getY() / SCALE_FACTOR; + + int backgroundWidth = (int) (width * 0.8f - 14); + int toggleBgX = option.getX() + backgroundWidth - 30; + + if (isMouseOver(mouseX, mouseY, toggleBgX, option.getY(), 14, option.getHeight())) { + option.set(!option.get()); + animationStartTime = System.currentTimeMillis(); + return true; + } + return false; + } + } + + + public class ModernColorOptionRenderer implements SkinRenderer { + private static final float ANIMATION_SPEED = 0.1f; + private float scale = 0f; + private boolean display = false; + + public void update(ColorOption option) { + if (option.getColorGradient().shouldDisplay() && display) { + scale += ANIMATION_SPEED; + } + if (!display) { + scale -= ANIMATION_SPEED; + } + scale = MathHelper.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) { + update(option); + + int backgroundWidth = (int) (width * 0.8f - 14); + + // Draw option name + drawContext.drawText( + mc.textRenderer, + option.name, + x + 2, + 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.getMatrices().peek().getPositionMatrix(), + x + backgroundWidth - width - 17, + y + 1, + width + 2, + 14, + 2, + behindColor.getRGB(), + shadowOpacity, + 1, + 1); + + //The letter above the shape behind the preview + drawContext.drawText( + mc.textRenderer, + option.getColorGradient().shouldDisplay() ? "^" : "v", + x + backgroundWidth - 21, + y + 4, + -1, + false + ); + + //Preview + DrawHelper.drawRoundedRectangleWithShadowBadWay(drawContext.getMatrices().peek().getPositionMatrix(), + x + backgroundWidth - width - 15, + y + 2, + width - 8, + 12, + 1, + option.value.getRGB(), + shadowOpacity, + 1, + 1); + + 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.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()); + + if (option.getColorGradient().getColorPickerButton().isPicking()) { + enableSkinScissor(); // re-enable the scissor + } + } + + @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)) { + option.isVisible = !option.isVisible; + if (option.isVisible) { + option.getColorGradient().display(); + display = true; + } else { + display = false; + } + return true; + } + option.getColorGradient().mouseClicked(mouseX, mouseY, button); + return false; + } + + @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); + } + + @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); + } + } + + public class ModernDoubleRenderer implements SkinRenderer { + private double displayValue; + private static final float ANIMATION_SPEED = 0.1f; + + @Override + public void render(DrawContext drawContext, DoubleOption option, int x, int y, int mouseX, int mouseY) { + // Draw option name + drawContext.drawText( + mc.textRenderer, + option.name, + x + 2, + y, + -1, + false + ); + + 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()); + + // Background + DrawHelper.drawRoundedRectangle( + drawContext.getMatrices().peek().getPositionMatrix(), + sliderX, y, sliderBackgroundWidth, sliderBackgroundHeight, 1, DARKER_GRAY.getRGB() + ); + + // Active fill + int activeFillWidth = (int) ((displayValue - option.minValue) / (option.maxValue - option.minValue) * option.getWidth()); + Color fillColor = isMouseOver(mouseX, mouseY, sliderX, y, sliderBackgroundWidth, sliderBackgroundHeight + 4) ? getThemeColor().darker().darker() : getThemeColor(); + DrawHelper.drawRoundedRectangle( + drawContext.getMatrices().peek().getPositionMatrix(), + sliderX, y, activeFillWidth, sliderBackgroundHeight, 1, fillColor.getRGB() + ); + + // Draw slider handle + float sliderHandleX = sliderX + activeFillWidth - 5; + DrawHelper.drawFilledCircle(drawContext.getMatrices().peek().getPositionMatrix(), 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, + -1, + true + ); + drawContext.getMatrices().pop(); + } + + @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)) { + option.setDragging(true); + return true; + } + return false; + } + + @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); + return true; + } + return false; + } + + @Override + 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.getMatrices().peek().getPositionMatrix(), + 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.getMatrices().peek().getPositionMatrix(), + leftX + 1, y + 3, + (mc.textRenderer.getWidth("<") * 2) + 10, mc.textRenderer.fontHeight, 2, + ColorHelper.changeAlpha(Color.BLACK, 128).getRGB() + ); + DrawHelper.drawRoundedRectangle( + drawContext.getMatrices().peek().getPositionMatrix(), + 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.getMatrices().peek().getPositionMatrix(), + 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.getMatrices().peek().getPositionMatrix(), + 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); + } + + @Override + public boolean mouseClicked(EnumOption option, double mouseX, double mouseY, int button) { + if (option.getValues().length == 0) return false; + + mouseX = mc.mouse.getX() / SCALE_FACTOR; + mouseY = mc.mouse.getY() / SCALE_FACTOR; + + 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); + + 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); + } + + } + + public class ModernListRenderer implements SkinRenderer> { + + @Override + public void render(DrawContext drawContext, ListOption 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.getMatrices().peek().getPositionMatrix(), + 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.getMatrices().peek().getPositionMatrix(), + leftX + 1, y + 3, + (mc.textRenderer.getWidth("<") * 2) + 10, mc.textRenderer.fontHeight, 2, + ColorHelper.changeAlpha(Color.BLACK, 128).getRGB() + ); + DrawHelper.drawRoundedRectangle( + drawContext.getMatrices().peek().getPositionMatrix(), + 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.getMatrices().peek().getPositionMatrix(), + 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.getMatrices().peek().getPositionMatrix(), + 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); + } + + @Override + public boolean mouseClicked(ListOption option, double mouseX, double mouseY, int button) { + if (option.getValues().isEmpty()) return false; + + mouseX /= SCALE_FACTOR; + mouseY /= SCALE_FACTOR; + + int x = option.getX(); + int y = option.getY(); + Text mainLabel = option.name.copy().append(": "); + String selectedOption = option.get().toString(); + + // 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); + + // 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 + } else if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT || hoveredOverRight) { + nextIndex = (currentIndex - 1 + values.size()) % values.size(); // Cycle backward + } else { + return false; // No valid click + } + + option.set(values.get(nextIndex)); + return true; + } + + return SkinRenderer.super.mouseClicked(option, mouseX, mouseY, button); + } + } + + 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; + + option.setPosition(xPos - 1, y); + option.setWidth(mc.textRenderer.getWidth(text) + 5); + option.setHeight(16); + + drawContext.drawText(mc.textRenderer, option.name, x + 4, y + 4, -1, false); + + drawContext.drawText(mc.textRenderer, text, xPos + 2, y + 4, Color.WHITE.getRGB(), true); + + Color fillColor = isMouseOver(mouseX, mouseY, xPos + 2, y + 4, mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4) ? getThemeColor().darker().darker() : getThemeColor(); + + DrawHelper.drawRoundedRectangleWithShadowBadWay( + drawContext.getMatrices().peek().getPositionMatrix(), + xPos - 1, y + 1, + mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4, + 2, + fillColor.getRGB(), + 180, + 1, + 1 + ); + DrawHelper.drawOutlineRoundedBox( + drawContext.getMatrices().peek().getPositionMatrix(), + xPos - 1, y + 1, + mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4, + 2, + 0.7f, + Color.WHITE.getRGB() + ); + + 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); + } + + @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); + } + + @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); + } + + @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); + } + } + + public class ModernRunnableRenderer 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) { + String text = "Run â–¶"; + int contextMenuWidth = (int) (width * 0.8f - 14); + int xPos = x + 4 + contextMenuWidth - 45; + + option.setPosition(xPos - 1, y); + option.setWidth(mc.textRenderer.getWidth(text) + 5); + option.setHeight(mc.textRenderer.fontHeight + 6); + + drawContext.drawText(mc.textRenderer, option.name, x + 4, y + 4, -1, false); + + drawContext.drawText(mc.textRenderer, text, xPos + 2, y + 4, option.value ? DARK_GREEN.getRGB() : DARK_RED.getRGB(), true); + + Color fillColor = isMouseOver(mouseX, mouseY, xPos + 2, y + 4, mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4) ? getThemeColor().darker().darker() : getThemeColor(); + + DrawHelper.drawRoundedRectangleWithShadowBadWay( + drawContext.getMatrices().peek().getPositionMatrix(), + xPos - 1, y + 1, + mc.textRenderer.getWidth(text) + 5, mc.textRenderer.fontHeight + 4, + 2, + fillColor.getRGB(), + 180, + 1, + 1 + ); + } + + @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); + } + } + @Override + public Skin clone() { + return new ModernSkin(themeColor,radius,defaultToolTipHeader,defaultToolTipText); + } +} \ 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 new file mode 100644 index 0000000..5c8e86b --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/Skin.java @@ -0,0 +1,126 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem; + +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option; +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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public abstract class Skin { + protected static final MinecraftClient mc = MinecraftClient.getInstance(); + protected ContextMenu contextMenu; + protected Map>, Supplier>>> renderers = new HashMap<>(); + private boolean createNewScreen; + + public Skin(ContextMenu menu) { + this(); + this.contextMenu = menu; + } + + public Skin() { + addRenderer(OptionGroup.class, OptionGroup.OptionGroupRenderer::new); + } + + public > void addRenderer(Class optionClass, Supplier> renderer) { + renderers.put(optionClass, renderer); + } + + @SuppressWarnings("unchecked") + public > SkinRenderer getRenderer(Class optionClass) { + Supplier>> supplier = renderers.get(optionClass); + if (supplier != null) { + return (SkinRenderer) supplier.get(); + } + return null; + } + + /** + * Whether this skin supports rendering option groups. + * If false, groups should be flattened before rendering. + */ + public boolean supportsGroups() { + return this instanceof GroupableSkin; // Check if the skin supports groups + } + + /** + * 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) { + List> flattened = new ArrayList<>(); + + for (Option option : options) { + if (option instanceof OptionGroup group) { + // Create a new list with type List> + ArrayList> groupOptions = new ArrayList<>(group.getGroupOptions()); + flattened.addAll(flattenOptions(groupOptions)); + } else { + flattened.add(option); + } + } + + return flattened; + } + + protected List> getOptions(ContextMenu menu) { + return supportsGroups() ? menu.getOptions() : flattenOptions(menu.getOptions()); + } + + public void setContextMenu(ContextMenu contextMenu) { + this.contextMenu = contextMenu; + } + + public void setRenderers(Map>, Supplier>>> renderers) { + this.renderers = renderers; + } + + public abstract void renderContextMenu(DrawContext drawContext, ContextMenu contextMenu, int mouseX, int mouseY); + + public boolean mouseClicked(ContextMenu menu, double mouseX, double mouseY, int button) { + return false; + } + + public boolean mouseReleased(ContextMenu menu, double mouseX, double mouseY, int button) { + return false; + } + + public boolean mouseDragged(ContextMenu menu, double mouseX, double mouseY, int button, double deltaX, double deltaY) { + return false; + } + + public void keyPressed(ContextMenu menu, int key, int scanCode, int modifiers) { + } + + public void keyReleased(ContextMenu menu, int key, int scanCode, int modifiers) { + } + + public void mouseScrolled(ContextMenu menu, double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + } + + public boolean shouldCreateNewScreen() { + return createNewScreen; + } + + public void setCreateNewScreen(boolean createNewScreen) { + this.createNewScreen = createNewScreen; + } + + protected 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; + } + + /** + * Create a new Skin object with the same parameters as this current screen. This is must for SubMenu options. + * If an object of same skin type is not returned then SubMenu options will not share the same skin with parent menu. + * @return new instance of this skin that need to be cloned to sub-menu option. + */ + public abstract Skin clone(); +} 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 new file mode 100644 index 0000000..c93dd52 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/GroupableSkin.java @@ -0,0 +1,12 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces; + +import com.tanishisherewith.dynamichud.utils.contextmenu.layout.LayoutContext; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.OptionGroup; +import net.minecraft.client.gui.DrawContext; + +public interface GroupableSkin { + LayoutContext.Offset getGroupIndent(); + + void renderGroup(DrawContext drawContext, 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 new file mode 100644 index 0000000..89fa741 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/skinsystem/interfaces/SkinRenderer.java @@ -0,0 +1,35 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.interfaces; + +import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option; +import net.minecraft.client.gui.DrawContext; + +public interface SkinRenderer> { + void render(DrawContext drawContext, 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); + } + + default boolean mouseReleased(T option, double mouseX, double mouseY, int button) { + return option.mouseReleased(mouseX, mouseY, button); + } + + default boolean mouseDragged(T option, double mouseX, double mouseY, int button, double deltaX, double deltaY) { + return option.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + default void keyPressed(T option, int key, int scanCode, int modifiers) { + option.keyPressed(key, scanCode, modifiers); + } + + default void keyReleased(T option, int key, int scanCode, int modifiers) { + option.keyReleased(key, scanCode, modifiers); + } + + default void mouseScrolled(T option, double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + option.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + default void init(T option) { + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/handlers/ScrollHandler.java b/src/main/java/com/tanishisherewith/dynamichud/utils/handlers/ScrollHandler.java new file mode 100644 index 0000000..2bd9d98 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/handlers/ScrollHandler.java @@ -0,0 +1,80 @@ +package com.tanishisherewith.dynamichud.utils.handlers; + +import net.minecraft.util.math.MathHelper; + +public class ScrollHandler { + protected int scrollOffset; + protected double scrollVelocity; + protected long lastScrollTime; + protected boolean isDragging; + protected int maxScrollOffset; + protected double SCROLL_SPEED = 1; + 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; + + this.maxScrollOffset = maxYOffset; + applyMomentum(); + scrollOffset = MathHelper.clamp(scrollOffset, 0, maxScrollOffset); + } + + public void mouseScrolled(double deltaY) { + scrollVelocity -= deltaY * 10; + lastScrollTime = System.currentTimeMillis(); + } + + public void startDragging(double mouseY) { + isDragging = true; + lastMouseY = mouseY; + } + + public void stopDragging() { + isDragging = false; + } + + public void addOffset(int offset) { + this.scrollOffset = MathHelper.clamp(scrollOffset + offset, 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 + 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); + } + + public boolean isOffsetWithinBounds(int offset) { + return scrollOffset + offset >= 0 && scrollOffset + offset <= maxScrollOffset; + } + + public ScrollHandler setScrollSpeed(double scrollSpeed) { + this.SCROLL_SPEED = scrollSpeed; + return this; + } +} \ 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 new file mode 100644 index 0000000..506de8d --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/DynamicValueWidget.java @@ -0,0 +1,80 @@ +package com.tanishisherewith.dynamichud.widget; + +import com.tanishisherewith.dynamichud.utils.DynamicValueRegistry; +import com.tanishisherewith.dynamichud.utils.Util; +import com.tanishisherewith.dynamichud.widgets.GraphWidget; +import com.tanishisherewith.dynamichud.widgets.TextWidget; +import net.minecraft.nbt.NbtCompound; + +import java.util.function.Supplier; + +/** + * DynamicValueWidget is an abstract extension of Widget that automatically handles DynamicValueRegistry to retrieve supplier data. + * @see GraphWidget + * @see TextWidget + */ +public abstract class DynamicValueWidget extends Widget { + protected String registryKey; + protected String registryID; + protected Supplier valueSupplier; + + public DynamicValueWidget(WidgetData data, String modID, String registryID, String registryKey) { + this(data, modID, Anchor.CENTER, registryID, registryKey); + } + + public DynamicValueWidget(WidgetData data, String modId, Anchor anchor, String registryID, String registryKey) { + super(data, modId, anchor); + boolean emptyCheck = Util.warnIfTrue(registryID == null || registryID.isEmpty(), "Empty registry ID, using global registry. Widget: {}", this.toString()); + this.registryID = emptyCheck ? DynamicValueRegistry.GLOBAL_ID : registryID; + this.registryKey = registryKey; + + initializeValueSupplier(); + } + + protected void initializeValueSupplier() { + this.valueSupplier = DynamicValueRegistry.getValue(registryID, registryKey); + } + + @Override + public void writeToTag(NbtCompound tag) { + super.writeToTag(tag); + tag.putString("RegistryID", registryID); + tag.putString("RegistryKey", registryKey); + } + + @Override + public void readFromTag(NbtCompound tag) { + super.readFromTag(tag); + registryID = tag.getString("RegistryID"); + registryKey = tag.getString("RegistryKey"); + + initializeValueSupplier(); + + if (valueSupplier == null) throw new IllegalStateException("Value supplier remains null"); + } + + /** + * Subclasses should implement this to get value from the supplier. + */ + public abstract Object getValue(); + + public abstract static class DynamicValueWidgetBuilder, W extends DynamicValueWidget> extends WidgetBuilder { + protected String registryKey = ""; + protected String registryID = null; + + @SuppressWarnings("unchecked") + protected T self() { + return (T) this; + } + + public T registryKey(String registryKey) { + this.registryKey = registryKey; + return self(); + } + + public T registryID(String registryID) { + this.registryID = registryID; + return self(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/UserManageable.java b/src/main/java/com/tanishisherewith/dynamichud/widget/UserManageable.java new file mode 100644 index 0000000..b719d15 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/UserManageable.java @@ -0,0 +1,6 @@ +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 3898d1f..7838d34 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java @@ -1,20 +1,19 @@ package com.tanishisherewith.dynamichud.widget; import com.tanishisherewith.dynamichud.config.GlobalConfig; -import com.tanishisherewith.dynamichud.helpers.ColorHelper; import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.utils.UID; +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 org.lwjgl.glfw.GLFW; -import java.util.Set; - -public abstract class Widget { - - public static WidgetData DATA; +public abstract class Widget implements Input { + public static MinecraftClient mc = MinecraftClient.getInstance(); + public WidgetData DATA; /** * This is the UID of the widget used to identify during loading and saving. *

@@ -23,19 +22,13 @@ public abstract class Widget { * @see #modId */ public UID uid = UID.generate(); - public boolean isInEditor = false; // Whether the widget is enabled and should be displayed. - public boolean display = true; - public boolean isDraggable = true; - + protected boolean isVisible = true; + protected boolean isDraggable = true; //Boolean to check if the widget is being dragged public boolean dragging; - //To enable/disable snapping - public boolean shiftDown = false; - // Absolute position of the widget on screen in pixels. - public int x, y; - public boolean shouldScale = true; + public boolean isShiftDown = false; /** * An identifier for widgets to group them under one ID. *

@@ -45,25 +38,35 @@ public abstract class Widget { * @see #uid */ public String modId = "unknown"; - protected MinecraftClient mc = MinecraftClient.getInstance(); - // The x position of the widget as a percentage of the screen width, i.e. the relative x position of the widget for resizing and scaling - protected float xPercent; - // The y position of the widget as a percentage of the screen height, i.e. the relative y position of the widget for resizing and scaling - protected float yPercent; - /** - * Scale of the current widget. - * - * @see GlobalConfig#getScale() - */ - protected float scale = 1.0f; + + public Text tooltipText; + + // Boolean to know if the widget is currently being displayed in an instance of AbstractMoveableScreen + protected boolean isInEditor = false; + + // Absolute position of the widget on screen in pixels. + protected int x, y; + + protected boolean shouldScale = true; + + protected Anchor anchor; // The chosen anchor point + //Dimensions of the widget protected WidgetBox widgetBox; - int startX, startY; + + private int startX, startY; + protected int offsetX, offsetY; // Offset from the anchor point public Widget(WidgetData DATA, String modId) { - Widget.DATA = DATA; + this(DATA, modId, Anchor.CENTER); + } + + public Widget(WidgetData DATA, String modId, Anchor anchor) { + this.DATA = DATA; widgetBox = new WidgetBox(0, 0, 0, 0); this.modId = modId; + this.anchor = anchor; + this.tooltipText = Text.of(DATA.description()); init(); } @@ -71,7 +74,6 @@ public Widget(WidgetData DATA, String modId) { * This method is called at the end of the {@link Widget#Widget(WidgetData, String)} constructor. */ public void init() { - } /** @@ -100,25 +102,55 @@ public float getHeight() { return widgetBox.getHeight(); } + private void calculateOffset(int initialX, int initialY, int screenWidth, int screenHeight) { + int anchorX = getAnchorX(screenWidth); + int anchorY = getAnchorY(screenHeight); + this.offsetX = initialX - anchorX; + this.offsetY = initialY - anchorY; + } + + private int getAnchorX(int screenWidth) { + return switch (anchor) { + case TOP_RIGHT, BOTTOM_RIGHT -> screenWidth; + case CENTER -> screenWidth / 2; + default -> 0; // TOP_LEFT and BOTTOM_LEFT + }; + } + + private int getAnchorY(int screenHeight) { + return switch (anchor) { + case BOTTOM_LEFT, BOTTOM_RIGHT -> screenHeight; + case CENTER -> screenHeight / 2; + default -> 0; // TOP_LEFT and TOP_RIGHT + }; + } + + // 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()); + } + + int anchorX = getAnchorX(screenWidth); + int anchorY = getAnchorY(screenHeight); + this.x = anchorX + offsetX; + this.y = anchorY + offsetY; + clampPosition(); + } + 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()); + } } public void setDraggable(boolean draggable) { isDraggable = draggable; } - public boolean isOverlapping(Set other) { - for (Widget widgetBox : other) { - if ((this.getX() < widgetBox.getX() + widgetBox.getWidgetBox().getWidth() && this.getX() + this.getWidgetBox().getWidth() > widgetBox.getX() && - this.getY() < widgetBox.getY() + widgetBox.getWidgetBox().getHeight() && this.getY() + this.getWidgetBox().getHeight() > widgetBox.getY())) { - return true; - } - } - return false; - } - public boolean isOverlapping(Widget other) { return this.getX() < other.getX() + other.getWidgetBox().getWidth() && this.getX() + this.getWidgetBox().getWidth() > other.getX() && this.getY() < other.getY() + other.getWidgetBox().getHeight() && this.getY() + this.getWidgetBox().getHeight() > other.getY(); @@ -128,7 +160,8 @@ public boolean isOverlapping(Widget other) { * Renders the widget on the screen. */ public final void render(DrawContext drawContext, int mouseX, int mouseY) { - if (!shouldDisplay()) return; + if (!isVisible()) return; + if (shouldScale) { DrawHelper.scaleAndPosition(drawContext.getMatrices(), getX(), getY(), GlobalConfig.get().getScale()); @@ -138,16 +171,16 @@ public final void render(DrawContext drawContext, int mouseX, int mouseY) { if (shouldScale) { DrawHelper.stopScaling(drawContext.getMatrices()); } - + clampPosition(); } /** * Renders the widget on the editor screen. */ public final void renderInEditor(DrawContext drawContext, int mouseX, int mouseY) { - if(!isInEditor) return; + if (!isInEditor) return; - displayBg(drawContext); + drawWidgetBackground(drawContext); if (shouldScale) { DrawHelper.scaleAndPosition(drawContext.getMatrices(), getX(), getY(), GlobalConfig.get().getScale()); @@ -157,41 +190,37 @@ public final void renderInEditor(DrawContext drawContext, int mouseX, int mouseY if (shouldScale) { DrawHelper.stopScaling(drawContext.getMatrices()); } + clampPosition(); } - /** * Renders the widget on the screen *

* The mouse position values are only passed when in a {@link com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen} screen. *

* - * @param context + * @param context DrawContext Object * @param mouseX X position of mouse. * @param mouseY Y position of mouse */ public abstract void renderWidget(DrawContext context, 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. - * - * @param context */ private void renderWidgetInEditor(DrawContext context, int mouseX, int mouseY) { - //displayBg(context); + //drawWidgetBackground(context); renderWidget(context, mouseX, mouseY); } - /* Input related methods. Override with super call to add your own input-based code like contextMenu */ - + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (widgetBox.isMouseOver(mouseX, mouseY) && button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { toggle(); - if(isDraggable) { + if (isDraggable) { startX = (int) (mouseX - x); startY = (int) (mouseY - y); dragging = true; @@ -201,15 +230,27 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return false; } - public boolean mouseDragged(double mouseX, double mouseY, int button, int snapSize) { - if(!isDraggable) 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()); + } + + @Override + public final boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + return false; + } + + 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) { int newX = (int) (mouseX - startX); int newY = (int) (mouseY - startY); // Divides the screen into several "grid boxes" which the elements snap to. // Higher the snapSize, more the grid boxes - if (this.shiftDown) { + if (this.isShiftDown) { // Calculate the size of each snap box int snapBoxWidth = mc.getWindow().getScaledWidth() / snapSize; int snapBoxHeight = mc.getWindow().getScaledHeight() / snapSize; @@ -223,16 +264,17 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, int snapSi this.x = (int) MathHelper.clamp(newX, 0, mc.getWindow().getScaledWidth() - getWidth()); this.y = (int) MathHelper.clamp(newY, 0, mc.getWindow().getScaledHeight() - getHeight()); - this.xPercent = (float) this.x / mc.getWindow().getScaledWidth(); - this.yPercent = (float) this.y / mc.getWindow().getScaledHeight(); + calculateOffset(x, y, mc.getWindow().getScaledWidth(), mc.getWindow().getScaledHeight()); // Set initial offset return true; } return false; } - public void mouseReleased(double mouseX, double mouseY, int button) { + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { dragging = false; + return true; } /** @@ -241,26 +283,38 @@ public void mouseReleased(double mouseX, double mouseY, int button) { * @param vAmount vertical amount of scrolling * @param hAmount horizontal amount of scrolling */ + @Override public void mouseScrolled(double mouseX, double mouseY, double vAmount, double hAmount) { } + @Override + public void keyPressed(int key, int scanCode, int modifiers) { + } + + @Override + public void keyReleased(int key, int scanCode, int modifiers) { + } + + @Override + public void charTyped(char c, int modifiers) { + } public boolean toggle() { - return this.display = !this.display; + return this.isVisible = !this.isVisible; } public void onClose() { - this.shiftDown = false; + this.isShiftDown = false; } /** * Displays a faint grayish background if enabled or faint reddish background if disabled. * Drawn with 2 pixel offset to all sides - * */ - protected void displayBg(DrawContext context) { - int backgroundColor = this.shouldDisplay() ? ColorHelper.getColor(0, 0, 0, 128) : ColorHelper.getColor(255, 0, 0, 128); + protected void drawWidgetBackground(DrawContext context) { + int backgroundColor = this.isVisible() ? GlobalConfig.get().getHudActiveColor().getRGB() : GlobalConfig.get().getHudInactiveColor().getRGB(); WidgetBox box = this.getWidgetBox(); + DrawHelper.drawRectangle(context.getMatrices().peek().getPositionMatrix(), box.x, box.y, @@ -269,13 +323,22 @@ protected void displayBg(DrawContext context) { backgroundColor); } + /** + * Set the tooltip text of the widget + */ + protected void setTooltipText(Text text) { + this.tooltipText = text; + } public void readFromTag(NbtCompound tag) { modId = tag.getString("modId"); uid = new UID(tag.getString("UID")); - x = tag.getInt("x"); - y = tag.getInt("y"); - display = tag.getBoolean("Display"); + // x = tag.getInt("x"); + // y = tag.getInt("y"); + anchor = Anchor.valueOf(tag.getString("anchor")); + offsetX = tag.getInt("offsetX"); + offsetY = tag.getInt("offsetY"); + isVisible = tag.getBoolean("isVisible"); isDraggable = tag.getBoolean("isDraggable"); shouldScale = tag.getBoolean("shouldScale"); } @@ -291,27 +354,22 @@ public void writeToTag(NbtCompound tag) { tag.putString("UID", uid.getUniqueID()); tag.putBoolean("isDraggable", isDraggable); tag.putBoolean("shouldScale", shouldScale); - tag.putInt("x", x); - tag.putInt("y", y); - tag.putBoolean("Display", display); + // tag.putInt("x", x); + // tag.putInt("y", y); + tag.putString("anchor", anchor.name()); + tag.putInt("offsetX", offsetX); + tag.putInt("offsetY", offsetY); + tag.putBoolean("isVisible", isVisible); } - public boolean shouldDisplay() { - return display; + public boolean isVisible() { + return isVisible; } public WidgetBox getWidgetBox() { return widgetBox; } - public void setxPercent(float xPercent) { - this.xPercent = xPercent; - } - - public void setyPercent(float yPercent) { - this.yPercent = yPercent; - } - public void setUid(UID uid) { this.uid = uid; } @@ -326,17 +384,21 @@ public String getModId() { @Override public String toString() { - return "Widget{" + + return this.getClass().getName() + "{" + "uniqueId='" + uid.getUniqueID() + '\'' + ", x=" + x + ", y=" + y + - ", display=" + display + + ", offsetX=" + offsetX + + ", offsetY=" + offsetY + + ", isVisible=" + isVisible + ", isDraggable=" + isDraggable + - ", shiftDown=" + shiftDown + + ", shiftDown=" + isShiftDown + ", shouldScale=" + shouldScale + '}'; } + public enum Anchor {TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER} + public abstract static class WidgetBuilder { protected int x; protected int y; diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java index aea7f75..e12da3f 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java @@ -1,7 +1,7 @@ package com.tanishisherewith.dynamichud.widget; public class WidgetBox { - public float x = 0, y = 0; + public float x, y; private float width; private float height; @@ -45,14 +45,14 @@ public boolean intersects(WidgetBox other) { return !(this.y + this.height < other.y); } - public void setSizeAndPositionNoScale(float x, float y, float width, float height) { + public void setDimensionsNoScale(float x, float y, float width, float height) { this.x = x; this.y = y; this.height = height; this.width = width; } - public void setSizeAndPosition(float x, float y, float width, float height, boolean shouldScale, float scale) { + public 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); diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetData.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetData.java index 0305a78..e71f8d5 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetData.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetData.java @@ -16,5 +16,4 @@ public String description() { public Widget createWidget() { return widgetFactory.get(); } - } diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetManager.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetManager.java index 978feac..f32809c 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 net.fabricmc.fabric.api.util.NbtType; +import com.tanishisherewith.dynamichud.mixins.ScreenMixin; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtList; -import net.minecraft.util.math.MathHelper; import java.io.DataOutputStream; import java.io.File; @@ -89,7 +89,7 @@ public static void removeWidget(Widget widget) { * Larger the GUI scale, more accurate is the position on any resolution. *

*

- * Called in {@link com.tanishisherewith.dynamichud.mixins.ScreenMixin} + * Called in {@link ScreenMixin} *

*

* @@ -101,6 +101,10 @@ public static void removeWidget(Widget widget) { public static void onScreenResized(int newWidth, int newHeight, int previousWidth, int previousHeight) { for (Widget widget : widgets) { // To ensure that infinite coordinates is not returned for the first time its resized. + + widget.updatePosition(newWidth, newHeight); + + /* if (widget.xPercent <= 0.0f) { widget.xPercent = (float) widget.getX() / previousWidth; } @@ -108,20 +112,12 @@ public static void onScreenResized(int newWidth, int newHeight, int previousWidt widget.yPercent = (float) widget.getY() / previousHeight; } - // Use the stored percentages to calculate the new position - float newX = widget.xPercent * newWidth; - float newY = widget.yPercent * newHeight; - - // Ensure the widget is within the screen bounds - newX = MathHelper.clamp(newX, 0, newWidth - widget.getWidth()); - newY = MathHelper.clamp(newY, 0, newHeight - widget.getHeight()); + widget.updatePositionFromPercentages(newWidth, newHeight); - // Update the widget's position - widget.setPosition((int) newX, (int) newY); + widget.xPercent = (widget.getX() + widget.getWidth() / 2) / newWidth; + widget.yPercent = (widget.getY() + widget.getHeight() / 2) / newHeight; - // Update the stored percentages with the new screen size (after resize). - widget.xPercent = (float) widget.getX() / newWidth; - widget.yPercent = (float) widget.getY() / newHeight; + */ } } @@ -145,7 +141,13 @@ public static void saveWidgets(File file, List widgets) throws IOExcepti Set widgetSet = new HashSet<>(); for (Widget widget : widgets) { NbtCompound widgetTag = new NbtCompound(); - widget.writeToTag(widgetTag); + //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); + } catch (Throwable e){ + DynamicHUD.logger.error("Widget [{}] occurred an exception while writing to NBT. This may be due to duplicate entries or invalid entry values", widget.toString()); + } + // Check for duplicates if (widgetSet.add(widgetTag.toString())) { printInfo("Saving Widget: " + widget); @@ -181,27 +183,45 @@ public static void saveWidgets(File file, List widgets) throws IOExcepti * * @param file The file to load from */ - public static void loadWidgets(File file) throws IOException { - widgets.clear(); + public static List loadWidgets(File file) throws IOException { + if (!file.exists()) { + // The backup file check is done in the doesWidgetFileExist() function below this + DynamicHUD.logger.warn("Main file {} was not found... Loading from a found backup file", file.getAbsolutePath()); + file = new File(file.getAbsolutePath() + ".backup"); + } - if (file.exists()) { - NbtCompound rootTag = NbtIo.read(file.toPath()); - NbtList widgetList = rootTag.getList("widgets", NbtType.COMPOUND); - if (widgetList == null) { - printWarn("RootTag is null. File is either empty or corrupted," + file); - return; - } - for (int i = 0; i < widgetList.size(); i++) { - NbtCompound widgetTag = widgetList.getCompound(i); - WidgetData widgetData = widgetDataMap.get(widgetTag.getString("name")); - Widget widget = widgetData.createWidget(); - printInfo("Loading Widget: " + widget); - widget.readFromTag(widgetTag); - widgets.add(widget); + NbtCompound 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", NbtElement.COMPOUND_TYPE); + 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); + WidgetData widgetData = widgetDataMap.get(widgetTag.getString("name")); + if (widgetData == null) { + throw new IllegalStateException("A Widget named '" + widgetTag.getString("name") + "' was found in the save file, but no matching WidgetData for its class could be located. This may indicate that the mod responsible for saving this widget has been removed or its implementation is incorrect."); } - } else { - printWarn("Widget File does not exist. Try saving one first"); + Widget widget = widgetData.createWidget(); + widget.readFromTag(widgetTag); + printInfo("Loaded Widget: " + widget); + widgetsToAdd.add(widget); + widgets.add(widget); } + return widgetsToAdd; + } + + /** + * Checks if the given file exists, or if a backup file exists. + */ + public static boolean doesWidgetFileExist(File file) { + return file.exists() || new File(file.getAbsolutePath() + ".backup").exists(); } /** @@ -223,4 +243,8 @@ public static List getWidgetsForMod(String modID) { .filter(widget -> modID.equalsIgnoreCase(widget.getModId())) .toList(); } + + public static Map> getWidgetDataMap() { + return widgetDataMap; + } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java index 4c734c1..167d8d9 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java @@ -1,21 +1,27 @@ package com.tanishisherewith.dynamichud.widget; import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.config.GlobalConfig; +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 org.lwjgl.glfw.GLFW; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Predicate; -public class WidgetRenderer { - public final List> allowedScreens = new CopyOnWriteArrayList<>(); +public class WidgetRenderer implements Input { + private Predicate allowedScreens; public boolean isInEditor = false; public Widget selectedWidget = null; List widgets; private boolean renderInGameHud = true; + private int Z_Index = 100; /** * Add the list of widgets the widgetRenderer should render @@ -26,34 +32,72 @@ public class WidgetRenderer { */ public WidgetRenderer(List widgets) { this.widgets = widgets; - addScreen(GameMenuScreen.class); + // Render in GameMenuScreen + this.allowedScreens = screen -> screen.getClass() == GameMenuScreen.class || + System.getInstances(ContextMenuScreenRegistry.class, DynamicHUD.MOD_ID).stream().anyMatch(registry -> registry.screenKlass == screen.getClass()); + } + + public WidgetRenderer(String modID) { + this(WidgetManager.getWidgetsForMod(modID)); } public void addWidget(Widget widget) { - widgets.add(widget); + this.widgets.add(widget); + } + + public void removeWidget(Widget widget) { + this.widgets.remove(widget); + } + + public void clearAndAdd(List widgets) { + this.widgets.clear(); + this.widgets.addAll(widgets); } + /** + * Use this when you want to simply add more screens + */ public void addScreen(Class screen) { - allowedScreens.add(screen); + this.allowedScreens = allowedScreens.or(screen1 -> screen1.getClass() == screen); + } + + /** + * Use this when you want a more complex approach to rendering your widgets + */ + public Predicate getAllowedScreens() { + return this.allowedScreens; + } + + public void updateAllowedScreens(Predicate newAllowedScreens) { + this.allowedScreens = newAllowedScreens; + } + + public void negateAllowedScreens() { + allowedScreens = allowedScreens.negate(); + } + + public boolean isScreenAllowed(Screen screen) { + return allowedScreens.test(screen); } public void shouldRenderInGameHud(boolean renderInGameHud) { this.renderInGameHud = renderInGameHud; } + private boolean renderInDebugScreen() { + if (GlobalConfig.get().renderInDebugScreen()) { + return true; + } + return !DynamicHUD.MC.getDebugHud().shouldShowDebugHud(); + } + public void renderWidgets(DrawContext context, int mouseX, int mouseY) { - if (WidgetManager.getWidgets().isEmpty() || DynamicHUD.MC.getDebugHud().shouldShowDebugHud()) return; + if (WidgetManager.getWidgets().isEmpty() || !renderInDebugScreen()) return; Screen currentScreen = DynamicHUD.MC.currentScreen; - //Render in game hud - if (currentScreen == null && renderInGameHud) { - for (Widget widget : widgets) { - widget.isInEditor = false; - widget.render(context, 0, 0); - } - return; - } + context.getMatrices().push(); + context.getMatrices().translate(0, 0, Z_Index); //Render in editing screen if (currentScreen instanceof AbstractMoveableScreen) { @@ -63,15 +107,17 @@ public void renderWidgets(DrawContext context, int mouseX, int mouseY) { } return; } - //Render in any other screen - if (currentScreen != null && allowedScreens.contains(DynamicHUD.MC.currentScreen.getClass()) && !this.isInEditor) { + //Render in any other screen and the inGameHud + if ((currentScreen == null && renderInGameHud) || allowedScreens.test(currentScreen)) { for (Widget widget : widgets) { widget.isInEditor = false; widget.render(context, 0, 0); } } + context.getMatrices().pop(); } + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { Screen currentScreen = DynamicHUD.MC.currentScreen; if (currentScreen == null) { @@ -91,75 +137,95 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return false; } - public void mouseDragged(double mouseX, double mouseY, int button, int snapSize) { + @Override + public void mouseScrolled(double mouseX, double mouseY, double vAmount, double hAmount) { Screen currentScreen = DynamicHUD.MC.currentScreen; if (currentScreen == null) { return; } if (currentScreen instanceof AbstractMoveableScreen) { for (Widget widget : widgets) { - // This essentially acts as a Z - layer where the widget first in the list is moved and dragged - // if they are overlapped on each other. - if (widget.mouseDragged(mouseX, mouseY, button, snapSize)) { - selectedWidget = widget; - return; - } + widget.mouseScrolled(mouseX, mouseY, vAmount, hAmount); } - selectedWidget = null; } } - public void mouseScrolled(double mouseX, double mouseY, double vAmount, double hAmount) { - Screen currentScreen = DynamicHUD.MC.currentScreen; - if (currentScreen == null) { - return; - } - if (currentScreen instanceof AbstractMoveableScreen) { + @Override + public void charTyped(char c, int modifiers) { + } + + public void onCloseScreen() { + if (DynamicHUD.MC.currentScreen instanceof AbstractMoveableScreen) { for (Widget widget : widgets) { - widget.mouseScrolled(mouseX, mouseY, vAmount, hAmount); + widget.onClose(); } } } - public void keyPressed(int keyCode) { + public List getWidgets() { + return widgets; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { Screen currentScreen = DynamicHUD.MC.currentScreen; - if (currentScreen instanceof AbstractMoveableScreen && (keyCode == GLFW.GLFW_KEY_LEFT_SHIFT || keyCode == GLFW.GLFW_KEY_RIGHT_SHIFT)) { + if (currentScreen == null) { + return false; + } + if (currentScreen instanceof AbstractMoveableScreen) { for (Widget widget : widgets) { - widget.shiftDown = true; + widget.mouseReleased(mouseX, mouseY, button); } } + return false; + } + + @Override + public final boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + return false; } - public void keyReleased(int keyCode) { + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY, int snapSize) { Screen currentScreen = DynamicHUD.MC.currentScreen; - if (currentScreen instanceof AbstractMoveableScreen && (keyCode == GLFW.GLFW_KEY_LEFT_SHIFT || keyCode == GLFW.GLFW_KEY_RIGHT_SHIFT)) { + if (currentScreen == null) { + return false; + } + if (currentScreen instanceof AbstractMoveableScreen) { for (Widget widget : widgets) { - widget.shiftDown = false; + // This essentially acts as a Z - layer where the widget first in the list is moved and dragged + // if they are overlapped on each other. + if (widget.mouseDragged(mouseX, mouseY, button, deltaX, deltaY, snapSize)) { + selectedWidget = widget; + return true; + } } + selectedWidget = null; } + return false; } - public void onCloseScreen() { - if (DynamicHUD.MC.currentScreen instanceof AbstractMoveableScreen) { + @Override + public void keyPressed(int key, int scanCode, int modifiers) { + Screen currentScreen = DynamicHUD.MC.currentScreen; + if (currentScreen instanceof AbstractMoveableScreen && (key == GLFW.GLFW_KEY_LEFT_SHIFT || key == GLFW.GLFW_KEY_RIGHT_SHIFT)) { for (Widget widget : widgets) { - widget.onClose(); + widget.isShiftDown = true; } } } - public List getWidgets() { - return widgets; - } - - public void mouseReleased(double mouseX, double mouseY, int button) { + @Override + public void keyReleased(int key, int scanCode, int modifiers) { Screen currentScreen = DynamicHUD.MC.currentScreen; - if (currentScreen == null) { - return; - } - if (currentScreen instanceof AbstractMoveableScreen) { + if (currentScreen instanceof AbstractMoveableScreen && (key == GLFW.GLFW_KEY_LEFT_SHIFT || key == GLFW.GLFW_KEY_RIGHT_SHIFT)) { for (Widget widget : widgets) { - widget.mouseReleased(mouseX, mouseY, button); + widget.isShiftDown = false; } } } + + 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 new file mode 100644 index 0000000..83c4ce7 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/widgets/GraphWidget.java @@ -0,0 +1,592 @@ +package com.tanishisherewith.dynamichud.widgets; + +import com.mojang.blaze3d.systems.RenderSystem; +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.utils.DynamicValueRegistry; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuManager; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProvider; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.BooleanOption; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.ColorOption; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.DoubleOption; +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.gl.ShaderProgramKeys; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.*; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; +import org.joml.Matrix4f; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Graph widget to draw a simple but detailed graph. + * You need to use DynamicValueRegistry to pass a value to the graph. + * You can use null values to signify the graph should update with a new value yet. + */ +public class GraphWidget extends DynamicValueWidget implements ContextMenuProvider { + public static WidgetData DATA = new WidgetData<>("GraphWidget", "Show graph", GraphWidget::new); + + private ContextMenu menu; + private float[] dataPoints; + private int head = 0; + private int maxDataPoints; + private float minValue, prevMinValue; + private float maxValue, prevMaxValue; + private Color graphColor; + private Color backgroundColor; + private float lineThickness; + private boolean showGrid; + private int gridLines; + private float width; + private float height; + private String label; + /// Automatically update the min and max of the graph + private boolean autoUpdateRange = false; + + 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) { + super(DATA, modId, anchor, registryID, registryKey); + this.width = width; + this.height = height; + this.maxDataPoints = maxDataPoints; + this.graphColor = graphColor; + this.backgroundColor = backgroundColor; + this.lineThickness = lineThickness; + this.showGrid = showGrid; + this.gridLines = gridLines; + this.label = label; + this.setMinValue(minValue); + this.setMaxValue(maxValue); + + internal_init(); + + createMenu(); + ContextMenuManager.getInstance().registerProvider(this); + } + 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); + + setTooltipText(Text.of("Graph displaying: " + label)); + } + + public GraphWidget() { + this(DynamicValueRegistry.GLOBAL_ID, "unknown", "unknown", Anchor.CENTER, 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 + for (int i = 0; i < maxDataPoints; i++) { + dataPoints[i] = minValue; + } + } + + /// Automatically update the min and max of the graph + public GraphWidget autoUpdateRange() { + this.autoUpdateRange = true; + 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); + } + if (getMinValue() > value) { + setMinValue(value - 10); + float diff = getPrevMinValue() - getMinValue(); + setMaxValue(getMaxValue() - diff); + } + } + + int index = (head) % maxDataPoints; + dataPoints[index] = MathHelper.clamp(value, minValue, maxValue); + head = (head + 1) % maxDataPoints; // Buffer full, overwrite oldest and move head + } + + private List getInterpolatedPoints() { + List points = new ArrayList<>(); + if (dataPoints.length < 2) return points; + + float xStep = width / (dataPoints.length - 1); + for (int i = 0; i < dataPoints.length - 1; i++) { + int index1 = (head + i) % dataPoints.length; + int index2 = (head + i + 1) % dataPoints.length; + + 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); + + // Add interpolated points using hermite spline (simplified) + for (float t = 0; t <= 1; t += 0.03f) { + float t2 = t * t; + float t3 = t2 * t; + 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}); + } + } + return points; + } + + // draw a continuous interpolated curve + private void drawInterpolatedCurve(Matrix4f matrix, List points, int color, float thickness) { + if (points.size() < 2) return; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR); + + 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; + + bufferBuilder.vertex(matrix, x + offsetX, y + offsetY, 0).color(color); + bufferBuilder.vertex(matrix, x - offsetX, y - offsetY, 0).color(color); + } + + RenderSystem.enableBlend(); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); + BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); + RenderSystem.disableBlend(); + } + + // draw a gradient shadow under the curve + private void drawGradientShadow(Matrix4f matrix, List points, float bottomY, int startColor, int endColor) { + if (points.size() < 2) return; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR); + + for (float[] point : points) { + float x = point[0]; + float y = point[1]; + + bufferBuilder.vertex(matrix, x, y, 0).color(startColor); + bufferBuilder.vertex(matrix, x, bottomY, 0).color(endColor); + } + + RenderSystem.enableBlend(); + RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR); + BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); + RenderSystem.disableBlend(); + } + + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY) { + Matrix4f matrix = context.getMatrices().peek().getPositionMatrix(); + + if (valueSupplier != null) { + addDataPoint(getValue()); + } + + // Apply pulse1 animation to background alpha + float animatedAlpha = MathHelper.clamp(MathAnimations.pulse1(backgroundColor.getAlpha() / 255.0f, 0.2f, 0.001f), 0f, 1.0f); + Color animatedBackgroundColor = ColorHelper.changeAlpha(backgroundColor, (int) (animatedAlpha * 255)); + Color gradientColor = ColorHelper.changeAlpha(backgroundColor, (int) (animatedAlpha * 255 * 0.7f)); + + DrawHelper.enableScissor(widgetBox); + + // Draw gradient background with rounded corners + if (!isInEditor) { + DrawHelper.drawRoundedGradientRectangle( + matrix, + animatedBackgroundColor, + gradientColor, + gradientColor, + animatedBackgroundColor, + x, y, width, height, 5, + false, true, false, false + ); + } + + // Draw grid lines and value markings + if (showGrid) { + float stepY = height / (gridLines + 1); + float valueStep = (maxValue - minValue) / (gridLines + 1); + + //TODO: The scale is too small for grid lines for than 21 (20 is the barely visible threshold) + float scale = (float) MathHelper.clamp((stepY / 9.5), 0.0f, 1.0f); + + for (int i = 1; i <= gridLines; i++) { + float yPos = y + stepY * i; + DrawHelper.drawHorizontalLine(matrix, x, width, yPos, 0.5f, 0x4DFFFFFF); // Semi-transparent white + + // Draw value labels on the left axis + float value = maxValue - (i * valueStep); + String valueText = formatValue(value); + + //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, x - mc.textRenderer.getWidth(valueText), (int) (yPos - (mc.textRenderer.fontHeight * scale) / 2.0f), 0xFFFFFFFF, true); + DrawHelper.stopScaling(context.getMatrices()); + } + + // Draw vertical grid lines (time axis) + float stepX = width / 5; // 5 vertical lines + for (int i = 1; i < 5; i++) { + float xPos = x + stepX * i; + DrawHelper.drawVerticalLine(matrix, xPos, y, height, 0.5f, 0x4DFFFFFF); + } + } + + // Draw interpolated graph curve + List points = getInterpolatedPoints(); + drawInterpolatedCurve(matrix, points, graphColor.getRGB(), lineThickness); + + // Draw shadow effect under the graph + drawGradientShadow( + matrix, points, y + height, + ColorHelper.changeAlpha(graphColor, 50).getRGB(), + 0x00000000 + ); + + DrawHelper.drawChromaText( + context, label, + x + 5, y + 5, + 1.0f, 0.8f, 1.0f, 0.05f, true + ); + + + // Draw axes + DrawHelper.drawHorizontalLine(matrix, x, width, y + height - 1, 1.0f, 0xFFFFFFFF); // X-axis + DrawHelper.drawVerticalLine(matrix, x, y, height, 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); + 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()); + + this.widgetBox.setDimensions(x, y, width, height,shouldScale, GlobalConfig.get().getScale()); + 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); + } else if (Math.abs(value) >= 1_000) { + return String.format("%.1fK", value / 1_000); + } else { + return String.format("%.0f", value); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + menu.toggleDisplay(widgetBox, mouseX, mouseY, button); + return super.mouseClicked(mouseX, mouseY, button); + } + + public void createMenu() { + ContextMenuProperties properties = ContextMenuProperties.builder().build(); + menu = new ContextMenu<>(getX(), (int) (getY() + widgetBox.getHeight()), properties); + + menu.addOption(new BooleanOption(Text.of("Show Grid"), + () -> this.showGrid, value -> this.showGrid = value, + BooleanOption.BooleanType.YES_NO) + .description(Text.of("Shows a grid and Y axis values")) + ); + menu.addOption(new DoubleOption(Text.of("Number of Grid Lines"), + 1, 25, 1, + () -> (double) this.gridLines, value -> this.gridLines = value.intValue(), menu) + .renderWhen(() -> this.showGrid) + ); + menu.addOption(new ColorOption(Text.of("Graph Line Color"), + () -> this.graphColor, value -> this.graphColor = value, menu) + .description(Text.of("Specify the color you want for the graph's lines")) + ); + menu.addOption(new ColorOption(Text.of("Graph Background Color"), + () -> this.backgroundColor, value -> this.backgroundColor = value, menu) + .description(Text.of("Specify the color you want for the graph's background")) + ); + menu.addOption(new DoubleOption(Text.of("Line Thickness"), + 0.5f, 5.0f, 0.1f, + () -> (double) this.lineThickness, value -> this.lineThickness = value.floatValue(), menu) + ); + } + + public float getMinValue() { + return minValue; + } + + public void setMinValue(float minValue) { + this.prevMinValue = this.minValue; + this.minValue = minValue; + } + + public float getPrevMinValue() { + return prevMinValue; + } + + public float getMaxValue() { + return maxValue; + } + + public void setMaxValue(float maxValue) { + this.prevMaxValue = this.maxValue; + this.maxValue = maxValue; + } + + public float getPrevMaxValue() { + return prevMaxValue; + } + + public float getLineThickness() { + return lineThickness; + } + + public void setLineThickness(float lineThickness) { + this.lineThickness = lineThickness; + } + + public Color getBackgroundColor() { + return backgroundColor; + } + + public void setBackgroundColor(Color backgroundColor) { + this.backgroundColor = backgroundColor; + } + + public String getLabel() { + return label; + } + + 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; + } + + public boolean isShowGrid() { + return showGrid; + } + + public void setShowGrid(boolean showGrid) { + this.showGrid = showGrid; + } + + public Color getGraphColor() { + return graphColor; + } + + public void setGraphColor(Color graphColor) { + this.graphColor = graphColor; + } + + @Override + public Float getValue() { + if (valueSupplier.get() instanceof Number number) { + return number.floatValue(); + } + return null; + } + + @Override + public void onClose() { + super.onClose(); + menu.close(); + } + + @Override + public void writeToTag(NbtCompound tag) { + super.writeToTag(tag); + tag.putFloat("width", width); + tag.putFloat("height", height); + tag.putInt("maxDataPoints", maxDataPoints); + tag.putFloat("minValue", minValue); + tag.putFloat("maxValue", maxValue); + tag.putInt("graphColor", graphColor.getRGB()); + tag.putInt("backgroundColor", backgroundColor.getRGB()); + tag.putFloat("lineThickness", lineThickness); + tag.putBoolean("showGrid", showGrid); + tag.putInt("gridLines", gridLines); + tag.putString("label", label); + tag.putBoolean("autoUpdateRange", autoUpdateRange); + } + + @Override + public void readFromTag(NbtCompound tag) { + super.readFromTag(tag); + this.width = tag.getFloat("width"); + this.height = tag.getFloat("height"); + this.maxDataPoints = tag.getInt("maxDataPoints"); + this.minValue = tag.getFloat("minValue"); + this.maxValue = tag.getFloat("maxValue"); + this.graphColor = new Color(tag.getInt("graphColor")); + this.backgroundColor = new Color(tag.getInt("backgroundColor")); + this.lineThickness = tag.getFloat("lineThickness"); + this.showGrid = tag.getBoolean("showGrid"); + this.gridLines = tag.getInt("gridLines"); + this.label = tag.getString("label"); + this.autoUpdateRange = tag.getBoolean("autoUpdateRange"); + this.setMinValue(minValue); + this.setMaxValue(maxValue); + + internal_init(); + createMenu(); + } + + @Override + public ContextMenu getContextMenu() { + return menu; + } + + public static class GraphWidgetBuilder extends DynamicValueWidgetBuilder { + private Anchor anchor = Anchor.CENTER; + private float width = 100; + private float height = 50; + private int maxDataPoints = 50; + private float minValue = 0; + private float maxValue = 100; + private Color graphColor = new Color(0xFF00FF00); + private Color backgroundColor = new Color(0x80000000); + private float lineThickness = 1.0f; + private boolean showGrid = true; + private int gridLines = 4; + private String label = "Graph"; + + public GraphWidgetBuilder() { + } + + public GraphWidgetBuilder label(String label) { + this.label = label; + return this; + } + + public GraphWidgetBuilder anchor(Anchor anchor) { + this.anchor = anchor; + return this; + } + + public GraphWidgetBuilder width(float width) { + this.width = width; + return this; + } + + public GraphWidgetBuilder height(float height) { + this.height = height; + return this; + } + + public GraphWidgetBuilder maxDataPoints(int maxDataPoints) { + this.maxDataPoints = maxDataPoints; + return this; + } + + public GraphWidgetBuilder minValue(float minValue) { + this.minValue = minValue; + return this; + } + + public GraphWidgetBuilder maxValue(float maxValue) { + this.maxValue = maxValue; + return this; + } + + public GraphWidgetBuilder graphColor(Color graphColor) { + this.graphColor = graphColor; + return this; + } + + public GraphWidgetBuilder backgroundColor(Color backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + public GraphWidgetBuilder lineThickness(float lineThickness) { + this.lineThickness = lineThickness; + return this; + } + + public GraphWidgetBuilder showGrid(boolean showGrid) { + this.showGrid = showGrid; + return this; + } + + public GraphWidgetBuilder gridLines(int no_of_gridLines) { + this.gridLines = no_of_gridLines; + return this; + } + + @Override + protected GraphWidgetBuilder self() { + return this; + } + + @Override + public GraphWidget build() { + GraphWidget widget = new GraphWidget(registryID, registryKey, modID, anchor, width, height, maxDataPoints, minValue, maxValue, graphColor, backgroundColor, lineThickness, showGrid, gridLines, label); + widget.setPosition(x, y); + widget.setDraggable(isDraggable); + widget.setShouldScale(shouldScale); + widget.isVisible = display; + return widget; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/widgets/ItemWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widgets/ItemWidget.java index 7b570ba..894cb1a 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widgets/ItemWidget.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widgets/ItemWidget.java @@ -12,27 +12,28 @@ * This is just an example widget, not supposed to be used. */ public class ItemWidget extends Widget { - public static WidgetData DATA = new WidgetData<>("ItemWidget","Displays item texture", ItemWidget::new); public ItemStack item; + public static WidgetData DATA = new WidgetData<>("ItemWidget", "Displays item texture", ItemWidget::new); - public ItemWidget(ItemStack itemStack,String modId) { + public ItemWidget(ItemStack itemStack, String modId) { super(DATA, modId); this.item = itemStack; } + public ItemWidget() { this(ItemStack.EMPTY, "empty"); } @Override public void renderWidget(DrawContext context, int mouseX, int mouseY) { - context.drawItem(item,x,y); - widgetBox.setSizeAndPosition(getX(), getY(), 16, 16, this.shouldScale, GlobalConfig.get().getScale()); + context.drawItem(item, x, y); + widgetBox.setDimensions(getX(), getY(), 16, 16, this.shouldScale, GlobalConfig.get().getScale()); } @Override public void writeToTag(NbtCompound tag) { super.writeToTag(tag); - tag.putInt("ItemID",Item.getRawId(item.getItem())); + tag.putInt("ItemID", Item.getRawId(item.getItem())); } @Override @@ -45,9 +46,10 @@ public void setItemStack(ItemStack item) { this.item = item; } - public static class Builder extends WidgetBuilder{ - ItemStack itemStack; - public Builder setItemStack(ItemStack itemStack) { + public static class Builder extends WidgetBuilder { + ItemStack itemStack; + + public Builder itemStack(ItemStack itemStack) { this.itemStack = itemStack; return self(); } @@ -59,7 +61,7 @@ protected Builder self() { @Override public ItemWidget build() { - return new ItemWidget(itemStack,modID); + return new ItemWidget(itemStack, modID); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/widgets/TextWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widgets/TextWidget.java index 9e6fa6a..afab10a 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widgets/TextWidget.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widgets/TextWidget.java @@ -1,130 +1,143 @@ 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.utils.DynamicValueRegistry; import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuManager; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProperties; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenuProvider; import com.tanishisherewith.dynamichud.utils.contextmenu.options.*; -import com.tanishisherewith.dynamichud.widget.Widget; +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.ClassicSkin; +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.MinecraftSkin; +import com.tanishisherewith.dynamichud.utils.contextmenu.skinsystem.ModernSkin; +import com.tanishisherewith.dynamichud.widget.DynamicValueWidget; import com.tanishisherewith.dynamichud.widget.WidgetData; import net.minecraft.client.gui.DrawContext; import net.minecraft.nbt.NbtCompound; -import org.lwjgl.glfw.GLFW; +import net.minecraft.text.Text; -import java.awt.*; +import javax.swing.*; +import java.awt.Color; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -public class TextWidget extends Widget { +public class TextWidget extends DynamicValueWidget implements ContextMenuProvider { + public static WidgetData DATA = new WidgetData<>("TextWidget", "Display Text on screen", TextWidget::new); + + private ContextMenu menu; public Color textColor; protected boolean shadow; // Whether to draw a shadow behind the text - public static WidgetData DATA = new WidgetData<>("TextWidget", "Display Text on screen", TextWidget::new); protected boolean rainbow; // Whether to apply a rainbow effect to the text protected int rainbowSpeed = 2; //Speed of the rainbow effect - Supplier textSupplier; - String dynamicRegistryKey; - DynamicValueRegistry dynamicValueRegistry = null; - private ContextMenu menu; + protected float rainbowSpread = 0.01f, rainbowSaturation = 1.0f, rainbowBrightness = 1.0f; + private String registryKey; + //private DynamicValueRegistry valueRegistry; + private String registryID; + public TextWidget() { - this(null, null, false, false, Color.WHITE, "unknown"); + this(DynamicValueRegistry.GLOBAL_ID, "unknown", false, false, Color.WHITE, "unknown"); } - /** - * Searches for the supplier within the {@link DynamicValueRegistry#globalRegistry} using the given registryKey - * - * @param dynamicRegistryKey - * @param shadow - * @param rainbow - */ - public TextWidget(String dynamicRegistryKey, boolean shadow, boolean rainbow, Color color, String modID) { - super(DATA, modID); - this.dynamicRegistryKey = dynamicRegistryKey; - textSupplier = (Supplier) DynamicValueRegistry.getGlobal(dynamicRegistryKey); - this.shadow = shadow; - this.rainbow = rainbow; - this.textColor = color; - createMenu(); + public TextWidget(DynamicValueRegistry valueRegistry, String registryKey, boolean shadow, boolean rainbow, Color color, String modID) { + this(valueRegistry.getId(), registryKey, shadow, rainbow, color, modID); } - /** - * Searches for the supplier within the {@link DynamicValueRegistry#localRegistry} using the given registryKey and registryValue - * - * @param dynamicRegistryKey - * @param shadow - * @param rainbow - */ - public TextWidget(DynamicValueRegistry dynamicValueRegistry, String dynamicRegistryKey, boolean shadow, boolean rainbow, Color color, String modID) { - super(DATA, modID); - this.dynamicRegistryKey = dynamicRegistryKey; - this.dynamicValueRegistry = dynamicValueRegistry; - if (dynamicValueRegistry != null) { - textSupplier = (Supplier) dynamicValueRegistry.get(dynamicRegistryKey); - } - this.textColor = color; + public TextWidget(String registryID, String registryKey, boolean shadow, boolean rainbow, Color color, String modID) { + super(DATA, modID, registryID, registryKey); this.shadow = shadow; this.rainbow = rainbow; + this.textColor = color; createMenu(); + ContextMenuManager.getInstance().registerProvider(this); } public void createMenu() { - menu = new ContextMenu(getX(), getY()); - menu.addOption(new BooleanOption("Shadow", () -> this.shadow, value -> this.shadow = value)); - menu.addOption(new BooleanOption("Rainbow", () -> this.rainbow, value -> this.rainbow = value)); - menu.addOption(new ColorOption("TextColor", menu, () -> this.textColor, value -> this.textColor = value)); - menu.addOption(new DoubleOption("RainbowSpeed", 1, 4, 1.0f, () -> (double) this.rainbowSpeed, value -> this.rainbowSpeed = value.intValue(),menu)); - - /* TEST - AtomicReference enums = new AtomicReference<>(Enum.Enum1); - AtomicReference option = new AtomicReference<>("Enum1"); - List options = Arrays.asList("List1", "List2", "List3"); - AtomicBoolean running = new AtomicBoolean(false); - AtomicBoolean subMenu = new AtomicBoolean(false); - menu.addOption(new EnumOption<>("Enum", enums::get, enums::set, Enum.values())); - menu.addOption(new ListOption<>("List", option::get, option::set, options)); - menu.addOption(new RunnableOption("Runnable Test",running::get,running::set, this::printStuff)); - SubMenuOption subMenuOption = new SubMenuOption("SubMenu",menu,subMenu::get,subMenu::set); - subMenuOption.getSubMenu().addOption(new BooleanOption("Shadows2", () -> this.shadow, value -> this.shadow = value)); - subMenuOption.getSubMenu().addOption(new BooleanOption("Shadows3", () -> this.shadow, value -> this.shadow = value)); - subMenuOption.getSubMenu().addOption(new BooleanOption("Shadows4", () -> this.shadow, value -> this.shadow = value)); - menu.addOption(subMenuOption); - */ + ContextMenuProperties properties = ContextMenuProperties.builder().skin(new ModernSkin(Color.YELLOW)).build(); + menu = new ContextMenu<>(getX(), getY(), properties); + + // Boolean Option + menu.addOption(new BooleanOption(Text.of("Toggle Shadow"), + () -> this.shadow, value -> this.shadow = value, + BooleanOption.BooleanType.ON_OFF) + .description(Text.of("Enable or disable text shadow"))); + + // Color Option + menu.addOption(new ColorOption(Text.of("Text Color"), + () -> this.textColor, value -> this.textColor = value, menu) + .description(Text.of("Change the text color"))); + + // Double Option + menu.addOption(new DoubleOption(Text.of("Opacity"), + 0.0, 1.0, 0.1f, + () -> (double) this.rainbowBrightness, value -> this.rainbowBrightness = value.floatValue(), menu) + .description(Text.of("Adjust text opacity"))); + + // Runnable Option + AtomicBoolean ran = new AtomicBoolean(false); + menu.addOption(new RunnableOption(Text.of("Reset Position"), + ran::get, ran::set, + () -> this.setPosition(0, 0)) + .description(Text.of("Reset widget to default position"))); + + // List Option + AtomicReference style = new AtomicReference<>("Style1"); + List styles = Arrays.asList("Style1", "Style2", "Style3"); + menu.addOption(new ListOption<>(Text.of("Text Style"), + style::get, style::set, styles) + .description(Text.of("Choose a text style"))); + + // Enum Option + menu.addOption(new EnumOption<>(Text.of("Alignment"), + () -> GroupLayout.Alignment.CENTER, value -> {}, GroupLayout.Alignment.values()) + .description(Text.of("Set text alignment"))); + + // Option Group + OptionGroup group = new OptionGroup(Text.of("Display Options")); + group.addOption(new BooleanOption(Text.of("Bold Text"), + () -> false, value -> {}, BooleanOption.BooleanType.YES_NO) + .description(Text.of("Enable bold text"))); + group.addOption(new DoubleOption(Text.of("Font Size"), + 8.0, 24.0, 1.0f, + () -> 12.0, value -> {}, menu) + .description(Text.of("Adjust font size"))); + menu.addOption(group); + + // SubMenu Option + SubMenuOption subMenu = (SubMenuOption) new SubMenuOption(Text.of("Advanced Settings"), menu) + .description(Text.of("Open advanced settings")); + subMenu.getSubMenu().addOption(new BooleanOption(Text.of("Some Boolean"), + () -> false, value -> {}, BooleanOption.BooleanType.TRUE_FALSE) + .description(Text.of("True/False"))); + menu.addOption(subMenu); } @Override public void renderWidget(DrawContext drawContext, int mouseX, int mouseY) { - int color = rainbow ? ColorHelper.getColorFromHue((System.currentTimeMillis() % (5000 * rainbowSpeed) / (5000f * rainbowSpeed))) : textColor.getRGB(); - if (textSupplier != null) { - String text = textSupplier.get(); - drawContext.drawText(mc.textRenderer, text, getX() + 2, getY() + 2, color, shadow); - widgetBox.setSizeAndPosition(getX(), getY(), mc.textRenderer.getWidth(text) + 3, mc.textRenderer.fontHeight + 2, this.shouldScale, GlobalConfig.get().getScale()); + 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(); + if (rainbow) { + DrawHelper.drawChromaText(drawContext, text, getX() + 2, getY() + 2, rainbowSpeed / 2f, rainbowSaturation, rainbowBrightness, rainbowSpread, shadow); + } else { + drawContext.drawText(mc.textRenderer, text, getX() + 2, getY() + 2, color, shadow); + } + widgetBox.setDimensions(getX(), getY(), mc.textRenderer.getWidth(text) + 3, mc.textRenderer.fontHeight + 2, this.shouldScale, GlobalConfig.get().getScale()); } - menu.render(drawContext, getX(), getY(), (int) Math.ceil(getHeight()),mouseX,mouseY); + menu.set(getX(), getY(), (int) Math.ceil(getHeight())); + } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == GLFW.GLFW_MOUSE_BUTTON_RIGHT && widgetBox.isMouseOver(mouseX, mouseY)) { - menu.toggleDisplay(); - } - menu.mouseClicked(mouseX, mouseY, button); + menu.toggleDisplay(widgetBox, mouseX, mouseY, button); return super.mouseClicked(mouseX, mouseY, button); } - @Override - public void mouseReleased(double mouseX, double mouseY, int button) { - menu.mouseReleased(mouseX, mouseY, button); - super.mouseReleased(mouseX, mouseY, button); - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, int snapSize) { - menu.mouseDragged(mouseX, mouseY, button); - return super.mouseDragged(mouseX, mouseY, button, snapSize); - } - @Override public void onClose() { super.onClose(); @@ -134,71 +147,60 @@ public void onClose() { @Override public void writeToTag(NbtCompound tag) { super.writeToTag(tag); - tag.putString("DynamicRegistryKey", dynamicRegistryKey); tag.putBoolean("Shadow", shadow); tag.putBoolean("Rainbow", rainbow); tag.putInt("TextColor", textColor.getRGB()); tag.putInt("RainbowSpeed", rainbowSpeed); - // If true then it means that we should use local registry and if false (i.e. null) then use global registry - tag.putBoolean("DynamicValueRegistry", dynamicValueRegistry != null); + tag.putFloat("RainbowSpread", rainbowSpread); + tag.putFloat("RainbowSaturation", rainbowSaturation); + tag.putFloat("RainbowBrightness", rainbowBrightness); } @Override public void readFromTag(NbtCompound tag) { super.readFromTag(tag); - this.shadow = tag.getBoolean("Shadow"); - this.rainbow = tag.getBoolean("Rainbow"); - this.rainbowSpeed = tag.getInt("RainbowSpeed"); - this.textColor = new Color(tag.getInt("TextColor")); - this.dynamicRegistryKey = tag.getString("DynamicRegistryKey"); - - // If true then it means that we should use local registry and if false (i.e. null) then use global registry - boolean dvrObj = tag.getBoolean("DynamicValueRegistry"); - if (!dvrObj) { - this.textSupplier = (Supplier) DynamicValueRegistry.getGlobal(dynamicRegistryKey); - return; - } + shadow = tag.getBoolean("Shadow"); + rainbow = tag.getBoolean("Rainbow"); + rainbowSpeed = tag.getInt("RainbowSpeed"); + rainbowSpread = tag.getFloat("RainbowSpread"); + rainbowSaturation = tag.getFloat("RainbowSaturation"); + rainbowBrightness = tag.getFloat("RainbowBrightness"); + textColor = new Color(tag.getInt("TextColor")); + registryKey = tag.getString("RegistryKey"); + registryID = tag.getString("RegistryID"); + + //createMenu(); + } - for (DynamicValueRegistry dvr : DynamicValueRegistry.getInstances(modId)) { - //Unfortunately, this method takes the value from the first local registry with the key. - //It returns to prevent overriding with other registries - this.textSupplier = (Supplier) dvr.get(dynamicRegistryKey); - dynamicValueRegistry = dvr; - return; - } - createMenu(); + @Override + public String getValue() { + return (String) valueSupplier.get(); + } + + @Override + public ContextMenu getContextMenu() { + return menu; } - public static class Builder extends WidgetBuilder { - protected boolean shadow = false; - protected boolean rainbow = false; - protected String dynamicRegistryKey = ""; - DynamicValueRegistry dynamicValueRegistry = null; - Color textColor = Color.WHITE; + + public static class Builder extends DynamicValueWidgetBuilder { + private boolean shadow = false; + private boolean rainbow = false; + private Color textColor = Color.WHITE; public Builder shadow(boolean shadow) { this.shadow = shadow; - return self(); + return this; } public Builder rainbow(boolean rainbow) { this.rainbow = rainbow; - return self(); - } - - public Builder setDRKey(String dynamicRegistryKey) { - this.dynamicRegistryKey = dynamicRegistryKey; - return self(); - } - - public Builder setDVR(DynamicValueRegistry dynamicValueRegistry) { - this.dynamicValueRegistry = dynamicValueRegistry; - return self(); + return this; } - public Builder setTextColor(Color textColor) { + public Builder textColor(Color textColor) { this.textColor = textColor; - return self(); + return this; } @Override @@ -208,20 +210,12 @@ protected Builder self() { @Override public TextWidget build() { - TextWidget widget; - if (dynamicValueRegistry == null) { - widget = new TextWidget(dynamicRegistryKey, shadow, rainbow, textColor, modID); - } else { - widget = new TextWidget(dynamicValueRegistry, dynamicRegistryKey, shadow, rainbow, textColor, modID); - } + TextWidget widget = new TextWidget(registryID, registryKey, shadow, rainbow, textColor, modID); + widget.setPosition(x, y); widget.setDraggable(isDraggable); widget.setShouldScale(shouldScale); return widget; } } - - - - } diff --git a/src/main/resources/assets/dynamichud/textures/minecraftskin/group_panel.png b/src/main/resources/assets/dynamichud/textures/minecraftskin/group_panel.png new file mode 100644 index 0000000..a2aa5e4 Binary files /dev/null and b/src/main/resources/assets/dynamichud/textures/minecraftskin/group_panel.png differ diff --git a/src/main/resources/dynamichud.mixins.json b/src/main/resources/dynamichud.mixins.json index 2cff290..f5d7c16 100644 --- a/src/main/resources/dynamichud.mixins.json +++ b/src/main/resources/dynamichud.mixins.json @@ -2,13 +2,14 @@ "required": true, "minVersion": "0.8", "package": "com.tanishisherewith.dynamichud.mixins", - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "mixins": [ + "ScreenMixin" ], "injectors": { "defaultRequire": 1 }, "client": [ - "ScreenMixin" + "OptionsScreenMixin" ] } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6b684b8..6ff31d0 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -15,14 +15,14 @@ "repo": "https://github.com/V-Fast/DynamicHUD", "issues": "https://github.com/V-Fast/DynamicHUD/issues" }, - "license": "All-Rights-Reserved", + "license": "MIT", "environment": "client", "entrypoints": { + "dynamicHud": [ + "com.tanishisherewith.dynamichud.integration.DefaultIntegrationImpl" + ], "client": [ "com.tanishisherewith.dynamichud.DynamicHUD" - ], - "modmenu": [ - "com.tanishisherewith.dynamichud.ModMenuIntegration" ] }, "mixins": [ @@ -31,11 +31,17 @@ "depends": { "fabricloader": ">=${loader_version}", "fabric": "*", + "java": ">=21", "minecraft": "~${minecraft_version}" }, "custom": { "modmenu": { - "badges": ["library"] + "badges": [ + "library" + ] } + }, + "suggests": { + "yet_another_config_lib_v3": ">=3.6.6+1.21.4-fabric" } }