diff --git a/README.md b/README.md index 4cb8b3a..db46e55 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,32 @@ - # DynamicHUD for Minecraft Fabric DynamicHUD is a special tool for Minecraft mod creators who use Fabric. It works with Minecraft 1.19.1 and newer versions. With DynamicHUD, you can make your own HUD parts that players can change and interact with, making their game look and feel better. > _It may be ported back upto 1.16 fabric on demand._ +# Examples +- [_DynamicHUDtest.java_](src/main/java/com/tanishisherewith/dynamichud/DynamicHUDtest.java) +- +## Demo + +*May vary from version to version* + +### Developed stage +> In progress +--- + +### Mid stage (1.2.0) +
+ View Demo video + + [!Mid stage demo video](https://github.com/V-Fast/DynamicHUD/assets/120117618/2abfcdf5-d786-4e58-acae-aefe51b77b4a) +
+ +### Early stages +
+ View Demo video + + [!Early stage demo video](https://github.com/V-Fast/DynamicHUD/assets/120117618/04de9319-69cd-4456-a555-c026c7e053a2) +
## Features @@ -66,29 +89,3 @@ DynamicHUD is released under the MIT License. Feel free to use and modify it in ## Support Need assistance or have suggestions? Join our [Discord](https://discord.com/invite/Rqpn3C7yR5) community or submit an issue on our GitHub [repository](https://github.com/V-Fast/DynamicHUD). -# Examples -- [_DynamicHUDtest.java_](src/main/java/com/tanishisherewith/dynamichud/DynamicHUDtest.java) - -# [Modrinth](https://modrinth.com/mod/dynamichud) - -## A LIBRARY SIMILAR TO THIS WHICH SUPPORTS OLDER MINECRAFT VERSIONS: https://github.com/LaconicLizard/HudElements - -# Demo: -### *May vary from version to version* -## New Version 1.2.0 -https://github.com/V-Fast/DynamicHUD/assets/120117618/2abfcdf5-d786-4e58-acae-aefe51b77b4a - - -## Old Version -https://github.com/V-Fast/DynamicHUD/assets/120117618/04de9319-69cd-4456-a555-c026c7e053a2 - - - - - - - - - - - diff --git a/build.gradle b/build.gradle index 8511df6..904421d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.2-SNAPSHOT' + id 'fabric-loom' version '1.3-SNAPSHOT' id 'maven-publish' } @@ -18,7 +18,13 @@ repositories { // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. // See https://docs.gradle.org/current/userguide/declaring_repositories.html // for more information about repositories. + mavenCentral() + jcenter() maven { url "https://maven.terraformersmc.com/releases/" } + maven { + name 'Xander Maven' + url 'https://maven.isxander.dev/releases' + } } dependencies { @@ -28,7 +34,9 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - modApi "com.terraformersmc:modmenu:7.1.0" + modImplementation "dev.isxander.yacl:yet-another-config-lib-fabric:${project.yacl_version}" + + modApi "com.terraformersmc:modmenu:9.0.0" } processResources { diff --git a/gradle.properties b/gradle.properties index 3a255cd..6f22580 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,16 +1,17 @@ -# Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx3G +org.gradle.parallel=true # Fabric Properties - minecraft_version=1.20.1 - yarn_mappings=1.20.1+build.1 - loader_version=0.14.21 +# check these on https://fabricmc.net/develop + minecraft_version=1.20.4 + yarn_mappings=1.20.4+build.1 + loader_version=0.15.0 # Mod Properties - mod_version = 1.2.0 + mod_version = 2.0.0 maven_group = com.tanishisherewith archives_base_name = dynamichud # Dependencies - # check this on https://modmuss50.me/fabric.html - fabric_version=0.83.1+1.20.1 + fabric_version=0.91.1+1.20.4 + yacl_version=3.3.2+1.20.4 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..3499ded 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.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-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 6f43839..84166ea 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java +++ b/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java @@ -1,109 +1,48 @@ package com.tanishisherewith.dynamichud; -import com.tanishisherewith.dynamichud.huds.AbstractMoveableScreen; -import com.tanishisherewith.dynamichud.interfaces.IWigdets; -import com.tanishisherewith.dynamichud.util.DynamicUtil; -import net.fabricmc.api.ModInitializer; +import com.tanishisherewith.dynamichud.config.GlobalConfig; +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 com.tanishisherewith.dynamichud.widgets.TextWidget; +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.fabric.api.client.keybinding.v1.KeyBindingHelper; 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 net.minecraft.client.util.InputUtil; -import net.minecraft.resource.LifecycledResourceManager; -import net.minecraft.server.MinecraftServer; -import org.lwjgl.glfw.GLFW; 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; -public class DynamicHUD implements ModInitializer { - - private static final Logger logger = LoggerFactory.getLogger("DynamicHud"); - static AbstractMoveableScreen Screen; - private static String keybingCategory = "DynamicHud"; - private static String TranslationKey = "DynamicHud Editor Screen"; - private static InputUtil.Type inputType = InputUtil.Type.KEYSYM; - private static int key = GLFW.GLFW_KEY_RIGHT_SHIFT; - public static final KeyBinding EditorScreenKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding( - TranslationKey, - inputType, - key, - keybingCategory - )); - private static String filename = "widgets.nbt"; - private static File fileDirectory = FabricLoader.getInstance().getConfigDir().toFile(); - public static final File WIDGETS_FILE = new File(fileDirectory, filename); - private static DynamicUtil dynamicutil; - private static IWigdets iWigdets; - MinecraftClient mc = MinecraftClient.getInstance(); - - public static File getFileDirectory() { - return fileDirectory; - } - - public static String getFilename() { - return filename; - } - - public static String getTranslationKey() { - return TranslationKey; - } - - public static String getKeybingCategory() { - return keybingCategory; - } - - public static InputUtil.Type getInputType() { - return inputType; - } - - public static void setInputType(InputUtil.Type inputType) { - DynamicHUD.inputType = inputType; - } - - public static void setKeyBindKey(int key) { - DynamicHUD.key = key; - } - - public static void setKeybingCategory(String keybingCategory) { - DynamicHUD.keybingCategory = keybingCategory; - } - - public static void setTranslationKey(String translationKey) { - TranslationKey = translationKey; - } +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 String MOD_ID = "dynamichud"; - public static void setFilename(String filename) { - DynamicHUD.filename = filename; + public static void addWidgetRenderer(WidgetRenderer widgetRenderer) { + widgetRenderers.add(widgetRenderer); } - public static void setFileDirectory(File fileDirectory) { - DynamicHUD.fileDirectory = fileDirectory; - } - - public static void setAbstractScreen(AbstractMoveableScreen screen) { - Screen = screen; - } - - public static AbstractMoveableScreen getScreen() { - return Screen; - } - - public static DynamicUtil getDynamicUtil() { - return dynamicutil; - } - - public static IWigdets getIWigdets() { - return iWigdets; - } - - public static void setIWigdets(IWigdets iWigdets) { - DynamicHUD.iWigdets = iWigdets; + public static List getWidgetRenderers() { + return widgetRenderers; } public static void printInfo(String msg) { @@ -114,48 +53,125 @@ 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 onInitialize() { - dynamicutil = new DynamicUtil(mc); - printInfo("DynamicHud Initialised"); - - //Save and Load - ClientTickEvents.START_CLIENT_TICK.register(server -> { - if (iWigdets != null) { - if (!WIDGETS_FILE.exists()) { - if (!dynamicutil.WidgetAdded) { - iWigdets.addWigdets(dynamicutil); - } - if (!dynamicutil.MainMenuWidgetAdded) { - iWigdets.addMainMenuWigdets(dynamicutil); + public void onInitializeClient() { + printInfo("Initialising DynamicHud"); + + // Add WidgetData of included widgets + WidgetManager.registerCustomWidgets( + TextWidget.DATA + ); + + //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 = 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 incorrect implementation of DynamicHUD", modId, e); + } } - } - - if (WIDGETS_FILE.exists() && !dynamicutil.WidgetLoaded) { - iWigdets.loadWigdets(dynamicutil); - printInfo("Widgets loaded"); - File FileDirectory = new File(fileDirectory, filename); - printInfo("Load file Directory: " + FileDirectory); - } - } - DynamicUtil.openDynamicScreen(EditorScreenKeyBinding, Screen); - }); - - //RenderCallBack - HudRenderCallback.EVENT.register((drawContext, tickDelta) -> dynamicutil.render(drawContext, tickDelta)); - - // Save during exiting a world, server or Minecraft itself - ServerLifecycleEvents.SERVER_STOPPING.register(this::onServerStopping); - ServerLifecycleEvents.END_DATA_PACK_RELOAD.register(this::onEndDataPackReload); - ServerPlayConnectionEvents.DISCONNECT.register((handler, packetSender) -> dynamicutil.getWidgetManager().saveWidgets(WIDGETS_FILE)); - Runtime.getRuntime().addShutdownHook(new Thread(() -> dynamicutil.getWidgetManager().saveWidgets(WIDGETS_FILE))); - } + }); + + 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()); - private void onEndDataPackReload(MinecraftServer server, LifecycledResourceManager lifecycledResourceManager, boolean b) { - dynamicutil.getWidgetManager().saveWidgets(WIDGETS_FILE); + + HudRenderCallback.EVENT.register(new HudRender()); } - private void onServerStopping(MinecraftServer server) { - dynamicutil.getWidgetManager().saveWidgets(WIDGETS_FILE); + 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 d606440..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHUDtest.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.tanishisherewith.dynamichud; - -import com.tanishisherewith.dynamichud.helpers.TextureHelper; -import com.tanishisherewith.dynamichud.huds.MoveableScreen; -import com.tanishisherewith.dynamichud.interfaces.IWigdets; -import com.tanishisherewith.dynamichud.interfaces.WidgetLoading; -import com.tanishisherewith.dynamichud.util.DynamicUtil; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.armor.ArmorWidget; -import com.tanishisherewith.dynamichud.widget.item.ItemWidget; -import com.tanishisherewith.dynamichud.widget.text.TextWidget; -import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.item.Items; -import net.fabricmc.api.ClientModInitializer; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.text.Text; - -import java.awt.*; -import java.util.HashSet; -import java.util.Set; - -import static com.tanishisherewith.dynamichud.DynamicHUD.WIDGETS_FILE; -import static com.tanishisherewith.dynamichud.DynamicHUD.printInfo; - -public class DynamicHUDtest implements ClientModInitializer, IWigdets, WidgetLoading { - protected Set widgets = new HashSet<>(); - protected Set MainMenuwidgets = new HashSet<>(); - MinecraftClient mc = MinecraftClient.getInstance(); - private DynamicUtil dynamicutil; - - - @Override - public void onInitializeClient() { - dynamicutil = DynamicHUD.getDynamicUtil(); - widgets.clear(); - MainMenuwidgets.clear(); - - DynamicHUD.setAbstractScreen(new MoveableScreen(Text.of("Editor Screen"), dynamicutil)); - DynamicHUD.setIWigdets(new DynamicHUDtest()); - dynamicutil.getWidgetManager().setWidgetLoading(new DynamicHUDtest()); - } - - @Override - public void addWigdets(DynamicUtil dynamicUtil) { - if (mc.player != null) { - printInfo("Widgets added"); - addTextWidgets(dynamicUtil); - addArmorWidgets(dynamicUtil); - addItemWidgets(dynamicUtil); - dynamicUtil.WidgetAdded = true; - } - } - - private void addTextWidgets(DynamicUtil dynamicUtil) { - widgets.add(new TextWidget(mc, "Non Draggable FPS: ", () -> mc.fpsDebugString.split(" ")[0], 0.5f, 0.5f, true, true, -1, -1, true)); - widgets.add(new TextWidget(mc, "Dynamic", () -> "HUD", 0.7f, 0.3f, false, false, -1, -1, true)); - widgets.add(new TextWidget(mc, "Test", () -> "", 0.08f, 0.5f, false, false, -1, -1, true)); - widgets.add(new TextWidget(mc, "", () -> "Data Test", 0.4f, 0.8f, false, false, -1, -1, true)); - widgets.add(new TextWidget(mc, "HUD Test ", () -> "", 0.83f, 0.8f, false, false, -1, -1, true)); - - for (Widget widget : widgets) { - if (widget instanceof TextWidget textWidget) { - if (textWidget.getText().equalsIgnoreCase("Non Draggable FPS: ")) { - textWidget.setDraggable(false); - } - } - dynamicUtil.getWidgetManager().addWidget(widget); - } - } - - private void addArmorWidgets(DynamicUtil dynamicUtil) { - String text = "Text"; - widgets.add(new ArmorWidget(mc, EquipmentSlot.CHEST, 0.01f, 0.01f, true, - TextureHelper.Position.ABOVE, - () -> text, - () -> Color.RED, - true, - "Text")); - widgets.add(new ArmorWidget(mc, - EquipmentSlot.LEGS, - 0.05f, - 0.01f, - true, - TextureHelper.Position.LEFT, - () -> String.valueOf(MinecraftClient.getInstance().getCurrentFps()), - () -> Color.WHITE, - true, - "FPS")); - - for (Widget widget : widgets) { - if (widget instanceof ArmorWidget armorWidget) { - dynamicUtil.getWidgetManager().addWidget(armorWidget); - } - } - } - - private void addItemWidgets(DynamicUtil dynamicUtil) { - widgets.add(new ItemWidget(mc, - Items.DIAMOND_AXE::getDefaultStack, - 0.15f, - 0.15f, - true, - TextureHelper.Position.ABOVE, - () -> "Label", - () -> Color.RED, - true, - "Label")); - - for (Widget widgetItem : widgets) { - if (widgetItem instanceof ItemWidget itemWidgetItem) { - dynamicUtil.getWidgetManager().addWidget(itemWidgetItem); - } - } - } - - - @Override - public void addMainMenuWigdets(DynamicUtil dynamicUtil) { - printInfo("MainMenu Widgets added"); - - MainMenuwidgets.add(new TextWidget(mc, "Test ", () -> "", 0.83f, 0.8f, false, false, -1, -1, true)); - MainMenuwidgets.add(new TextWidget(mc, "E Test ", () -> "", 0.85f, 0.3f, false, false, -1, -1, true)); - MainMenuwidgets.add(new TextWidget(mc, "Non Draggable FPS: ", () -> String.valueOf(mc.getCurrentFps()), 0.67f, 0.5f, false, false, -1, -1, true)); - for (Widget mmwigdet : MainMenuwidgets) { - if (mmwigdet instanceof TextWidget textWidget) { - if (textWidget.getText().equalsIgnoreCase("Non Draggable FPS: ")) { - textWidget.setDraggable(false); - } - } - dynamicUtil.getWidgetManager().addMainMenuWidget(mmwigdet); - } - dynamicUtil.MainMenuWidgetAdded = true; - } - - @Override - public void loadWigdets(DynamicUtil dynamicUtil) { - Set widgets = dynamicUtil.getWidgetManager().loadWigdets(WIDGETS_FILE); - Set MainMenuWidget = dynamicUtil.getWidgetManager().loadMainMenuWigdets(WIDGETS_FILE); - - Widget.addTextGenerator("Non Draggable FPS: ", () -> String.valueOf(mc.getCurrentFps())); - Widget.addTextGenerator("Dynamic", () -> "HUD"); - Widget.addTextGenerator("Test", () -> ""); - Widget.addTextGenerator("", () -> "Data Test"); - Widget.addTextGenerator("HUD Test ", () -> ""); - Widget.addTextGenerator("Text", () -> "Text"); - Widget.addTextGenerator("Test ", () -> String.valueOf(mc.getCurrentFps())); - Widget.addTextGenerator("Non Draggable FPS: ", () -> String.valueOf(mc.getCurrentFps())); - Widget.addTextGenerator("Label", () -> "Label"); - - for (Widget widget : widgets) { - dynamicUtil.getWidgetManager().addWidget(widget); - } - - for (Widget widgetItem : MainMenuWidget) { - dynamicUtil.getWidgetManager().addMainMenuWidget(widgetItem); - } - - dynamicUtil.WidgetLoaded = true; - } - - - @Override - public Widget loadWidgetsFromTag(String className, NbtCompound widgetTag) { - //SAMPLE CODE EXAMPLE : - /*if (className.equals(ItemWidget.class.getName())) { - ItemWidget widget = new ItemWidget(MinecraftClient.getInstance(), ItemStack.EMPTY, 0, 0, true, TextureHelper.Position.ABOVE, () -> "", Color.BLUE); - widget.readFromTag(widgetTag); - return widget; - }*/ - System.out.println("widget tag loaded"); - return WidgetLoading.super.loadWidgetsFromTag(className, widgetTag); - } -} - - - diff --git a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java b/src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java new file mode 100644 index 0000000..d8959f7 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java @@ -0,0 +1,134 @@ +package com.tanishisherewith.dynamichud; + +import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; +import com.tanishisherewith.dynamichud.widget.WidgetData; +import com.tanishisherewith.dynamichud.widget.WidgetManager; +import com.tanishisherewith.dynamichud.widget.WidgetRenderer; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import org.lwjgl.glfw.GLFW; + +import java.io.File; + +/** + * This interface provides methods for integrating DynamicHud into a mod. + */ +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. + */ + KeyBinding EDITOR_SCREEN_KEY_BINDING = KeyBindingHelper.registerKeyBinding(new KeyBinding( + "DynamicHud Editor Screen", + InputUtil.Type.KEYSYM, + GLFW.GLFW_KEY_RIGHT_SHIFT, + "DynamicHud" + )); + + /** + * The filename for the widgets file. + */ + String FILENAME = "widgets.nbt"; + + /** + * The directory for the widgets file. + */ + File FILE_DIRECTORY = FabricLoader.getInstance().getConfigDir().toFile(); + + /** + * The file where widgets are saved. + */ + File WIDGETS_FILE = new File(FILE_DIRECTORY, FILENAME); + + /** + * Initializes the DynamicHud integration. + *

+ * Suggested to be used to initialize {@link com.tanishisherewith.dynamichud.utils.DynamicValueRegistry} and widgets with their respective values + *

+ */ + void init(); + + /** + * To be used to add widgets using {@link WidgetManager}. + */ + void addWidgets(); + + /** + * To register custom widgets. This method can be overridden by implementations. + *

+ * Use {@link WidgetManager#registerCustomWidget(WidgetData)} to register custom widgets. + *

+     * Example:
+     *     {@code
+     *     WidgetManager.registerCustomWidget(TextWidget.DATA);
+     *     }
+     * 
+ */ + 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. + * + * @return The widgets file. + */ + default File getWidgetsFile() { + return WIDGETS_FILE; + } + + /** + * Returns the keybind to open the {@link AbstractMoveableScreen} instance. + * + * @return The keybind. + */ + default KeyBinding getKeyBind() { + return EDITOR_SCREEN_KEY_BINDING; + } + + /** + * Returns the movable screen for the DynamicHud. + * + * @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/DynamicHudTest.java b/src/main/java/com/tanishisherewith/dynamichud/DynamicHudTest.java new file mode 100644 index 0000000..6f1b2f4 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/DynamicHudTest.java @@ -0,0 +1,98 @@ +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.client.gui.screen.multiplayer.MultiplayerScreen; +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!"); + 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); + 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 new file mode 100644 index 0000000..b4e301e --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/HudRender.java @@ -0,0 +1,18 @@ +package com.tanishisherewith.dynamichud; + +import com.tanishisherewith.dynamichud.widget.WidgetRenderer; +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; +import net.minecraft.client.gui.DrawContext; + +/** + * 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 { + @Override + public void onHudRender(DrawContext drawContext, float tickDelta) { + for (WidgetRenderer widgetRenderer : DynamicHUD.getWidgetRenderers()) { + widgetRenderer.renderWidgets(drawContext, -120, -120); + } + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java b/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java index 07cb3aa..30fffb4 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java +++ b/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java @@ -1,11 +1,16 @@ 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 -> DynamicHUD.getScreen(); + 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 new file mode 100644 index 0000000..7c34869 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java @@ -0,0 +1,55 @@ +package com.tanishisherewith.dynamichud.config; + +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; +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.gui.screen.Screen; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class GlobalConfig { + public static final ConfigClassHandler HANDLER = ConfigClassHandler.createBuilder(GlobalConfig.class) + .id(new Identifier("dynamichud", "dynamichud_config")) + .serializer(config -> GsonConfigSerializerBuilder.create(config) + .setPath(FabricLoader.getInstance().getConfigDir().resolve("dynamichud.json5")) + .setJson5(true) + .build()) + .build(); + private static final GlobalConfig INSTANCE = new GlobalConfig(); + /** + * Common scale for all widgets. Set by the user using YACL. + */ + @SerialEntry + private float scale = 1.0f; + + public static GlobalConfig get() { + return INSTANCE; + } + + public final Screen createYACLGUI() { + return YetAnotherConfigLib.createBuilder() + .title(Text.literal("DynamicHUD config screen.")) + .category(ConfigCategory.createBuilder() + .name(Text.literal("General")) + .tooltip(Text.literal("Set the general settings for all widgets.")) + .group(OptionGroup.createBuilder() + .name(Text.literal("Global")) + .description(OptionDescription.of(Text.literal("Global settings for all widgets."))) + .option(Option.createBuilder() + .name(Text.literal("Scale")) + .description(OptionDescription.of(Text.literal("Set scale for all widgets."))) + .binding(1.0f, () -> this.scale, newVal -> this.scale = newVal) + .controller(floatOption -> FloatSliderControllerBuilder.create(floatOption).range(0.1f, 2.5f).step(0.1f)) + .build()) + .build()) + .build()) + .build() + .generateScreen(null); + } + public float getScale(){ + return scale; + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/handlers/DefaultDragHandler.java b/src/main/java/com/tanishisherewith/dynamichud/handlers/DefaultDragHandler.java deleted file mode 100644 index 02e77db..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/handlers/DefaultDragHandler.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.tanishisherewith.dynamichud.handlers; - -import com.tanishisherewith.dynamichud.widget.Widget; - -public class DefaultDragHandler implements DragHandler { - private int dragStartX = 0; - private int dragStartY = 0; - - @Override - public boolean startDragging(Widget widget, double mouseX, double mouseY) { - if (widget.getWidgetBox().contains(widget, mouseX, mouseY, Widget.getScale())) { - dragStartX = (int) (mouseX - widget.getX()); - dragStartY = (int) (mouseY - widget.getY()); - return true; - } - return false; - } - - @Override - public void updateDragging(Widget widget, double mouseX, double mouseY) { - int newX = (int) (dragStartX + mouseX); - int newY = (int) (dragStartY + mouseY); - widget.setX(newX + (newX / 2)); - widget.setY(newY + (newY / 2)); - } - - @Override - public void stopDragging(Widget widget) { - // Nothing to do here in the default implementation - } -} - diff --git a/src/main/java/com/tanishisherewith/dynamichud/handlers/DefaultMouseHandler.java b/src/main/java/com/tanishisherewith/dynamichud/handlers/DefaultMouseHandler.java deleted file mode 100644 index 5a9c03f..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/handlers/DefaultMouseHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.tanishisherewith.dynamichud.handlers; - -import com.tanishisherewith.dynamichud.util.colorpicker.ColorGradientPicker; -import com.tanishisherewith.dynamichud.util.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.widget.slider.SliderWidget; -import com.tanishisherewith.dynamichud.widget.text.TextWidget; - -import java.util.List; - -public class DefaultMouseHandler implements MouseHandler { - private final ColorGradientPicker colorPicker; - private final List contextMenu; - private final List sliderWidget; - - public DefaultMouseHandler(ColorGradientPicker colorPicker, List contextMenu, List sliderWidget) { - this.colorPicker = colorPicker; - this.contextMenu = contextMenu; - this.sliderWidget = sliderWidget; - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (contextMenu != null && sliderWidget != null) { - for (ContextMenu contextMenu : contextMenu) { - if (contextMenuClicked(mouseX, mouseY, button, contextMenu)) { - return true; - } - } - for (SliderWidget sliderWidget : sliderWidget) { - if (sliderClicked(mouseX, mouseY, button, sliderWidget)) { - return true; - } - } - } - return colorPickerClicked(mouseX, mouseY, button); - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - if (sliderWidget != null) { - for (SliderWidget sliderWidget : sliderWidget) { - if (sliderWidget != null && sliderWidget.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) { - return true; - } - } - } - if (this.colorPicker != null) { - colorPicker.mouseDragged(mouseX, mouseY, button); - return true; - } - return false; - } - - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - if (this.colorPicker != null) { - colorPicker.mouseReleased(mouseX, mouseY, button); - return true; - } - return false; - } - - - @Override - public boolean contextMenuClicked(double mouseX, double mouseY, int button, ContextMenu contextMenu) { - return contextMenu != null && contextMenu.mouseClicked(mouseX, mouseY, button); - } - - @Override - public boolean colorPickerClicked(double mouseX, double mouseY, int button) { - return colorPicker != null && colorPicker.mouseClicked(mouseX, mouseY, button); - } - - @Override - public boolean sliderClicked(double mouseX, double mouseY, int button, SliderWidget sliderWidget) { - return sliderWidget != null && sliderWidget.mouseClicked(mouseX, mouseY, button); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/handlers/DragHandler.java b/src/main/java/com/tanishisherewith/dynamichud/handlers/DragHandler.java deleted file mode 100644 index 8c2c090..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/handlers/DragHandler.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.tanishisherewith.dynamichud.handlers; - -import com.tanishisherewith.dynamichud.widget.Widget; - -public interface DragHandler { - boolean startDragging(Widget widget, double mouseX, double mouseY); - - void updateDragging(Widget widget, double mouseX, double mouseY); - - void stopDragging(Widget widget); -} - diff --git a/src/main/java/com/tanishisherewith/dynamichud/handlers/MouseHandler.java b/src/main/java/com/tanishisherewith/dynamichud/handlers/MouseHandler.java deleted file mode 100644 index bb587f3..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/handlers/MouseHandler.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tanishisherewith.dynamichud.handlers; - -import com.tanishisherewith.dynamichud.util.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.widget.slider.SliderWidget; - -public interface MouseHandler { - boolean mouseClicked(double mouseX, double mouseY, int button); - - boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY); - - boolean mouseReleased(double mouseX, double mouseY, int button); - - boolean contextMenuClicked(double mouseX, double mouseY, int button, ContextMenu contextMenu); - - boolean colorPickerClicked(double mouseX, double mouseY, int button); - - boolean sliderClicked(double mouseX, double mouseY, int button, SliderWidget sliderWidget); - - -} - diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java index 2194e4b..b119142 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java @@ -1,6 +1,5 @@ package com.tanishisherewith.dynamichud.helpers; -import net.minecraft.network.packet.Packet; import net.minecraft.util.math.MathHelper; import java.awt.*; @@ -10,28 +9,18 @@ */ public class ColorHelper { public static int r, g, b, a; + public ColorHelper(int r, int g, int b) { - this.r = r; - this.g = g; - this.b = b; - this.a = 255; + ColorHelper.r = r; + ColorHelper.g = g; + ColorHelper.b = b; + a = 255; validate(); } + public ColorHelper() { } - public void validate() { - if (r < 0) r = 0; - else if (r > 255) r = 255; - - if (g < 0) g = 0; - else if (g > 255) g = 255; - - if (b < 0) b = 0; - else if (b > 255) b = 255; - if (a < 0) a = 0; - else if (a > 255) a = 255; - } /** * Returns a color as an integer value given its red, green and blue components. * @@ -83,10 +72,10 @@ public static int getColorFromHue(float hue) { public static int ColorToInt(Color color) { return color.getRGB(); } - public static float[] getRainbowColor() - { + + public static float[] getRainbowColor() { float x = System.currentTimeMillis() % 2000 / 1000F; - float pi = (float)Math.PI; + float pi = (float) Math.PI; float[] rainbow = new float[3]; rainbow[0] = 0.5F + 0.5F * MathHelper.sin(x * pi); @@ -94,6 +83,32 @@ public static float[] getRainbowColor() rainbow[2] = 0.5F + 0.5F * MathHelper.sin((x + 8F / 3F) * pi); return rainbow; } + + /** + * Rainbow color with custom speed. + * + * @param speed + * @return Current rainbow color. + */ + public static Color getRainbowColor(int speed) { + float hue = (System.currentTimeMillis() % (speed * 100)) / (speed * 100.0f); + return Color.getHSBColor(hue, 1.0f, 1.0f); + } + + /** + * Changes alpha on color. + * + * @param color Target color. + * @param alpha Target alpha. + * @return Color with changed alpha. + */ + public static Color changeAlpha(Color color, int alpha) { + if (color != null) + return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha); + else + return new Color(0); + } + public static int fromRGBA(int r, int g, int b, int a) { return (r << 16) + (g << 8) + (b) + (a << 24); } @@ -113,8 +128,22 @@ public static int toRGBAB(int color) { public static int toRGBAA(int color) { return (color >> 24) & 0x000000FF; } - public int toInt() - { - return new Color(r,b,g,a).getRGB(); + + public void validate() { + if (r < 0) r = 0; + else if (r > 255) r = 255; + + if (g < 0) g = 0; + else if (g > 255) g = 255; + + if (b < 0) b = 0; + else if (b > 255) b = 255; + + if (a < 0) a = 0; + else if (a > 255) a = 255; + } + + public int toInt() { + return new Color(r, b, g, a).getRGB(); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java index 34f9115..3794f06 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java @@ -1,195 +1,787 @@ package com.tanishisherewith.dynamichud.helpers; +import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; -import com.tanishisherewith.dynamichud.util.CustomTextRenderer; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; +import com.tanishisherewith.dynamichud.DynamicHUD; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.render.*; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.MathHelper; import org.joml.Matrix4f; +import org.lwjgl.opengl.GL40C; -public class DrawHelper extends DrawContext { - public static CustomTextRenderer customTextRenderer; +import java.awt.*; - public DrawHelper(MinecraftClient client, VertexConsumerProvider.Immediate vertexConsumers) { - super(client, vertexConsumers); - } +/** + * Credits: HeliosClient + */ +public class DrawHelper { /** - * Fills a box on the screen with a specified color. + * Draws a singular gradient rectangle on screen with the given parameters * - * @param drawContext The matrix stack used for rendering - * @param x The x position of the rectangle - * @param y The y position of the rectangle - * @param width The width of the rectangle - * @param height The height of the rectangle - * @param color The color to fill the rectangle with + * @param matrix4f Matrix4f object to draw the gradient + * @param x X position of the gradient + * @param y Y position of the gradient + * @param width Width of the gradient + * @param height Height of the gradient + * @param startColor start color of the gradient + * @param endColor end color of the gradient + * @param direction Draws the gradient in the given direction */ - public static void drawBox(DrawContext drawContext, int x, int y, int width, int height, int color) { - int x1 = x - width / 2 - 2; - int y1 = y - height / 2 - 2; - int x2 = x + width / 2 + 2; - int y2 = y + height / 2 + 2; - drawContext.fill(x1, y1, x2, y2, color); + public static void drawGradient(Matrix4f matrix4f, float x, float y, float width, float height, int startColor, int endColor, Direction direction) { + float startRed = (float) (startColor >> 16 & 255) / 255.0F; + float startGreen = (float) (startColor >> 8 & 255) / 255.0F; + float startBlue = (float) (startColor & 255) / 255.0F; + float startAlpha = (float) (startColor >> 24 & 255) / 255.0F; + + float endRed = (float) (endColor >> 16 & 255) / 255.0F; + float endGreen = (float) (endColor >> 8 & 255) / 255.0F; + float endBlue = (float) (endColor & 255) / 255.0F; + float endAlpha = (float) (endColor >> 24 & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + + bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); + + switch (direction) { + case LEFT_RIGHT: + bufferBuilder.vertex(matrix4f, x, y + height, 0.0F).color(startRed, startGreen, startBlue, startAlpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y + height, 0.0F).color(endRed, endGreen, endBlue, endAlpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y, 0.0F).color(endRed, endGreen, endBlue, endAlpha).next(); + bufferBuilder.vertex(matrix4f, x, y, 0.0F).color(startRed, startGreen, startBlue, startAlpha).next(); + break; + case TOP_BOTTOM: + bufferBuilder.vertex(matrix4f, x, y + height, 0.0F).color(endRed, endGreen, endBlue, endAlpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y + height, 0.0F).color(endRed, endGreen, endBlue, endAlpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y, 0.0F).color(startRed, startGreen, startBlue, startAlpha).next(); + bufferBuilder.vertex(matrix4f, x, y, 0.0F).color(startRed, startGreen, startBlue, startAlpha).next(); + break; + case RIGHT_LEFT: + bufferBuilder.vertex(matrix4f, x, y + height, 0.0F).color(endRed, endGreen, endBlue, endAlpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y + height, 0.0F).color(startRed, startGreen, startBlue, startAlpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y, 0.0F).color(startRed, startGreen, startBlue, startAlpha).next(); + bufferBuilder.vertex(matrix4f, x, y, 0.0F).color(endRed, endGreen, endBlue, endAlpha).next(); + break; + case BOTTOM_TOP: + bufferBuilder.vertex(matrix4f, x, y + height, 0.0F).color(startRed, startGreen, startBlue, startAlpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y + height, 0.0F).color(startRed, startGreen, startBlue, startAlpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y, 0.0F).color(endRed, endGreen, endBlue, endAlpha).next(); + bufferBuilder.vertex(matrix4f, x, y, 0.0F).color(endRed, endGreen, endBlue, endAlpha).next(); + break; + } + + tessellator.draw(); + + RenderSystem.disableBlend(); + } + + public static void enableScissor(int x, int y, int width, int height) { + double scaleFactor = DynamicHUD.MC.getWindow().getScaleFactor(); + + int scissorX = (int) (x * scaleFactor); + int scissorY = (int) (DynamicHUD.MC.getWindow().getHeight() - ((y + height) * scaleFactor)); + int scissorWidth = (int) (width * scaleFactor); + int scissorHeight = (int) (height * scaleFactor); + + RenderSystem.enableScissor(scissorX, scissorY, scissorWidth, scissorHeight); + } + + public static void disableScissor() { + RenderSystem.disableScissor(); } /** - * Fills a rectangle on the screen with a specified color. + * Draws a singular rectangle on screen with the given parameters * - * @param x1 The x position of the top left corner of the rectangle - * @param y1 The y position of the top left corner of the rectangle - * @param x2 The x position of the bottom right corner of the rectangle - * @param y2 The y position of the bottom right corner of the rectangle - * @param color The color to fill the rectangle with + * @param matrix4f Matrix4f object to draw the rectangle + * @param x X position of the rectangle + * @param y Y position of the rectangle + * @param width Width of the rectangle + * @param height Height of the rectangle + * @param color Color of the rectangle */ - public static void fill(DrawContext drawContext, int x1, int y1, int x2, int y2, int color) { - drawContext.fill(x1, y1, x2, y2, color); + public static void drawRectangle(Matrix4f matrix4f, float x, float y, float width, float height, int color) { + float red = (float) (color >> 16 & 255) / 255.0F; + float green = (float) (color >> 8 & 255) / 255.0F; + float blue = (float) (color & 255) / 255.0F; + float alpha = (float) (color >> 24 & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + + bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); + + bufferBuilder.vertex(matrix4f, x, y + height, 0.0F).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y + height, 0.0F).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, x + width, y, 0.0F).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, x, y, 0.0F).color(red, green, blue, alpha).next(); + + tessellator.draw(); + + RenderSystem.disableBlend(); } + /* ==== Drawing Rectangles ==== */ + /** - * Draws text on screen. + * Draws a singular outline rectangle on screen with the given parameters * - * @param textRenderer - TextRenderer instance used for rendering. - * @param text - Text to be drawn. - * @param x - X position to draw at. - * @param y - Y position to draw at. - * @param color - Color to draw with. + * @param matrix4f Matrix4f object to draw the rectangle + * @param x X position of the rectangle + * @param y Y position of the rectangle + * @param width Width of the rectangle + * @param height Height of the rectangle + * @param color Color of the rectangle */ - public static void drawText(DrawContext drawContext, - TextRenderer textRenderer, - String text, - int x, - int y, - int color, - boolean shadow) { - drawContext.drawText(textRenderer, text, x, y, color, shadow); + public static void drawOutlineBox(Matrix4f matrix4f, float x, float y, float width, float height, float thickness, int color) { + drawRectangle(matrix4f, x, y, width, thickness, color); + drawRectangle(matrix4f, x, y + height - thickness, width, thickness, color); + drawRectangle(matrix4f, x, y + thickness, thickness, height - thickness * 2, color); + drawRectangle(matrix4f, x + width - thickness, y + thickness, thickness, height - thickness * 2, color); } + /** + * Draws a singular rectangle with a dark shadow on screen with the given parameters + * Bad way because there is a better way + * + * @param matrix4f Matrix4f object to draw the rectangle and shadow + * @param x X position of the rectangle + * @param y Y position of the rectangle + * @param width Width of the rectangle + * @param height Height of the rectangle + * @param color Color of the rectangle + * @param shadowOpacity Opacity of the shadow (Dark --> Lighter) + * @param shadowOffsetX X position Offset of the shadow from the main rectangle X pos + * @param shadowOffsetY Y position Offset of the shadow from the main rectangle Y pos + */ + public static void drawRectangleWithShadowBadWay(Matrix4f matrix4f, float x, float y, float width, float height, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { + // First, render the shadow + drawRectangle(matrix4f, x + shadowOffsetX, y + shadowOffsetY, width, height, ColorHelper.getColor(0, 0, 0, shadowOpacity)); + + // Then, render the rectangle + drawRectangle(matrix4f, x, y, width, height, color); + } /** - * Draws text on screen. + * Draws an outline rounded rectangle by drawing 4 side rectangles, and 4 arcs * - * @param drawContext - drawContext used for rendering. - * @param text - Text to be drawn. - * @param x - X position to draw at. - * @param y - Y position to draw at. - * @param color - Color to draw with. + * @param matrix4f Matrix4f object to draw the rounded rectangle + * @param x X pos + * @param y Y pos + * @param width Width of rounded rectangle + * @param height Height of rounded rectangle + * @param radius Radius of the quadrants / the rounded rectangle + * @param color Color of the rounded rectangle + * @param thickness thickness of the outline */ - public static void drawTextWithScale(DrawContext drawContext, - String text, - int x, - int y, - int color, - boolean shadow, - float scale) { - customTextRenderer = new CustomTextRenderer(MinecraftClient.getInstance(), scale); - customTextRenderer.draw(drawContext, text, x, y, color, shadow); + public static void drawOutlineRoundedBox(Matrix4f matrix4f, float x, float y, float width, float height, float radius, float thickness, int color) { + // Draw the rectangles for the outline + drawRectangle(matrix4f, x + radius, y, width - radius * 2, thickness, color); // Top rectangle + drawRectangle(matrix4f, x + radius, y + height - thickness, width - radius * 2, thickness, color); // Bottom rectangle + drawRectangle(matrix4f, x, y + radius, thickness, height - radius * 2, color); // Left rectangle + drawRectangle(matrix4f, x + width - thickness, y + radius, thickness, height - radius * 2, color); // Right rectangle + + // Draw the arcs at the corners for the outline + drawArc(matrix4f, x + radius, y + radius, radius, thickness, color, 180, 270); // Top-left arc + drawArc(matrix4f, x + width - radius, y + radius, radius, thickness, color, 90, 180); // Top-right arc + drawArc(matrix4f, x + width - radius, y + height - radius, radius, thickness, color, 0, 90); // Bottom-right arc + drawArc(matrix4f, x + radius, y + height - radius, radius, thickness, color, 270, 360); // Bottom-left arc } + public static void drawRainbowGradientRectangle(Matrix4f matrix4f, float x, float y, float width, float height, float alpha) { + BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); - public static void drawCutRectangle(DrawContext drawContext, int x1, int y1, int x2, int y2, int z, int color, int cornerRadius) { - // Draw the rectangles - drawContext.fill(x1 + cornerRadius, y1, x2 - cornerRadius, y1 + cornerRadius, z, color); - drawContext.fill(x1 + cornerRadius, y2 - cornerRadius, x2 - cornerRadius, y2, z, color); - drawContext.fill(x1, y1 + cornerRadius, x2, y2 - cornerRadius, z, color); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + + bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); + + for (int i = 0; i <= width; i++) { + float hue = (float) i / width; + int color = Color.HSBtoRGB(hue, 1.0f, 1.0f); + color = (color & 0x00FFFFFF) | ((int) (alpha * 255) << 24); + + float red = (color >> 16 & 255) / 255.0F; + float green = (color >> 8 & 255) / 255.0F; + float blue = (color & 255) / 255.0F; + float alphaVal = (color >> 24 & 255) / 255.0F; + + bufferBuilder.vertex(matrix4f, x + i, y, 0.0f).color(red, green, blue, alphaVal).next(); + bufferBuilder.vertex(matrix4f, x + i, y + height, 0.0f).color(red, green, blue, alphaVal).next(); + } + + for (int i = (int) width; i >= 0; i--) { + float hue = (float) i / width; + int color = Color.HSBtoRGB(hue, 1.0f, 1.0f); + color = (color & 0x00FFFFFF) | ((int) (alpha * 255) << 24); + + float red = (color >> 16 & 255) / 255.0F; + float green = (color >> 8 & 255) / 255.0F; + float blue = (color & 255) / 255.0F; + float alphaVal = (color >> 24 & 255) / 255.0F; + + bufferBuilder.vertex(matrix4f, x + i, y + height, 0.0f).color(red, green, blue, alphaVal).next(); + bufferBuilder.vertex(matrix4f, x + i, y, 0.0f).color(red, green, blue, alphaVal).next(); + } + + Tessellator.getInstance().draw(); + + RenderSystem.disableBlend(); } + public static void drawRainbowGradient(Matrix4f matrix, 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.colorMask(true, true, true, true); + + drawRectangle(matrix4f, x, y, width, height, Color.BLACK.getRGB()); + + RenderSystem.blendFunc(GL40C.GL_DST_ALPHA, GL40C.GL_ONE_MINUS_DST_ALPHA); + + RenderSystem.enableBlend(); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + + BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); + bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); + for (float i = 0; i < width; i += 1.0f) { + float hue = (i / width); // Multiply by 1 to go through the whole color spectrum once (red to red) + Color color = Color.getHSBColor(hue, 1.0f, 1.0f); // Full saturation and brightness + + bufferBuilder.vertex(matrix4f, x + i, y, 0.0F).color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()).next(); + bufferBuilder.vertex(matrix4f, x + i + 1.0f, y, 0.0F).color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()).next(); + bufferBuilder.vertex(matrix4f, x + i + 1.0f, y + height, 0.0F).color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()).next(); + bufferBuilder.vertex(matrix4f, x + i, y + height, 0.0F).color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()).next(); + } + + BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); + RenderSystem.disableBlend(); + + RenderSystem.defaultBlendFunc(); + } + + + /* ==== Drawing filled and outline circles ==== */ + /** - * Fills a rounded rectangle on screen with specified color. - * This causes a lot of problems and for some reason does not work for ArmorWidget when used for contextMenu + * Draws an outline of a circle * - * @param matrix4f - Matrix4f used for rendering. - * @param x1 - X position of top left corner of rectangle. - * @param y1 - Y position of top left corner of rectangle. - * @param x2 - X position of bottom right corner of rectangle. - * @param y2 - Y position of bottom right corner of rectangle. - * @param cornerRadius - Radius of rounded corners. - * @param color - Color to fill rectangle with. + * @param matrix4f Matrix4f object to draw the circle outline + * @param xCenter X position of the circle outline + * @param yCenter Y position of the circle outline + * @param radius radius of the circle outline + * @param color color of the circle outline */ - public static void fillRoundedRect(Matrix4f matrix4f, int x1, int y1, int x2, int y2, int cornerRadius, int color) { + public static void drawOutlineCircle(Matrix4f matrix4f, float xCenter, float yCenter, float radius, float lineWidth, int color) { + float red = (float) (color >> 16 & 255) / 255.0F; + float green = (float) (color >> 8 & 255) / 255.0F; + float blue = (float) (color & 255) / 255.0F; float alpha = (float) (color >> 24 & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + bufferBuilder.begin(VertexFormat.DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR); + + for (int i = 0; i <= 360; i++) { + double x = xCenter + Math.sin(Math.toRadians(i)) * radius; + double y = yCenter + Math.cos(Math.toRadians(i)) * radius; + double x2 = xCenter + Math.sin(Math.toRadians(i)) * (radius + lineWidth); + double y2 = yCenter + Math.cos(Math.toRadians(i)) * (radius + lineWidth); + bufferBuilder.vertex(matrix4f, (float) x, (float) y, 0).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, (float) x2, (float) y2, 0).color(red, green, blue, alpha).next(); + } + + + tessellator.draw(); + } + + /** + * Draws a filled circle + * + * @param matrix4f Matrix4f object to draw the circle outline + * @param xCenter X position of the circle outline + * @param yCenter Y position of the circle outline + * @param radius radius of the circle outline + * @param color color of the circle outline + */ + public static void drawFilledCircle(Matrix4f matrix4f, float xCenter, float yCenter, float radius, int color) { float red = (float) (color >> 16 & 255) / 255.0F; float green = (float) (color >> 8 & 255) / 255.0F; float blue = (float) (color & 255) / 255.0F; + float alpha = (float) (color >> 24 & 255) / 255.0F; + Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferBuilder = tessellator.getBuffer(); + + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + bufferBuilder.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); - bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); + RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); - // Draw the center rectangle - bufferBuilder.vertex(matrix4f, x1 + cornerRadius, y2 - cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x2 - cornerRadius, y2 - cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x2 - cornerRadius, y1 + cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x1 + cornerRadius, y1 + cornerRadius, 0).color(red, green, blue, alpha).next(); - - // Draw the side rectangles - bufferBuilder.vertex(matrix4f, x1, y1 + cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x1 + cornerRadius, y1 + cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x1 + cornerRadius, y2 - cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x1, y2 - cornerRadius, 0).color(red, green, blue, alpha).next(); - - bufferBuilder.vertex(matrix4f, x2, y1 + cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x2 - cornerRadius, y1 + cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x2 - cornerRadius, y2 - cornerRadius, 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, x2, y2 - cornerRadius, 0).color(red, green, blue, alpha).next(); - - - // Draw the rounded corners - for (int i = 0; i <= 90; i += 5) { - double angle = Math.toRadians(i); - double sin = Math.sin(angle); - double cos = Math.cos(angle); - bufferBuilder.vertex(matrix4f, (float) (x1 + cornerRadius * (1 - cos)), (float) (y1 + cornerRadius * (1 - sin)), 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, (float) (x1 + cornerRadius * (1 - cos)), (float) (y2 - cornerRadius * (1 - sin)), 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, (float) (x2 - cornerRadius * (1 - cos)), (float) (y2 - cornerRadius * (1 - sin)), 0).color(red, green, blue, alpha).next(); - bufferBuilder.vertex(matrix4f, (float) (x2 - cornerRadius * (1 - cos)), (float) (y1 + cornerRadius * (1 - sin)), 0).color(red, green, blue, alpha).next(); + + bufferBuilder.vertex(matrix4f, xCenter, yCenter, 0).color(red, green, blue, alpha).next(); + + for (int i = 0; i <= 360; i++) { + double x = xCenter + Math.sin(Math.toRadians(i)) * radius; + double y = yCenter + Math.cos(Math.toRadians(i)) * radius; + bufferBuilder.vertex(matrix4f, (float) x, (float) y, 0).color(red, green, blue, alpha).next(); } + tessellator.draw(); RenderSystem.disableBlend(); } - public static void fillRoundedRect(DrawContext drawContext, int left, int top, int right, int bottom, int color) { - drawContext.fill(left + 1, top, right - 1, top + 1, color); - drawContext.fill(left + 1, bottom - 1, right - 1, bottom, color); - drawContext.fill(left, top + 1, left + 1, bottom - 1, color); - drawContext.fill(right - 1, top + 1, right, bottom - 1, color); - drawContext.fill(left + 1, top + 1, right - 1, bottom - 1, color); + /** + * Draws a filled circle with a shadow bad way + * + * @param matrix4f Matrix4f object to draw the circle + * @param xCenter X position of the circle + * @param yCenter Y position of the circle + * @param radius Radius of the circle + * @param color Color of the circle + * @param shadowOffsetX X position of the circle shadow offset from main circle + * @param shadowOffsetY X position of the circle shadow offset from main circle + * @param shadowOpacity Opacity of the circle shadow offset from main circle + */ + public static void drawCircleWithShadow(Matrix4f matrix4f, float xCenter, float yCenter, float radius, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { + // First, render the shadow + drawFilledCircle(matrix4f, xCenter + shadowOffsetX, yCenter + shadowOffsetY, radius, ColorHelper.getColor(0, 0, 0, shadowOpacity)); + + // Then, render the circle + drawFilledCircle(matrix4f, xCenter, yCenter, radius, color); } /** - * Fills a rectangle on screen with a gradient. + * Not Tested * - * @param matrix4f - Matrix4f used for rendering. - * @param x1 - X position of top left corner of rectangle. - * @param y1 - Y position of top left corner of rectangle. - * @param x2 - X position of bottom right corner of rectangle. - * @param y2 - Y position of bottom right corner of rectangle. - * @param topColor - Color at top of gradient. - * @param bottomColor - Color at bottom of gradient. + * @param matrix4f + * @param x + * @param y + * @param radius + * @param startAngle + * @param endAngle + * @param color */ - public static void fillGradient(Matrix4f matrix4f, int x1, int y1, int x2, int y2, int topColor, int bottomColor) { - float topAlpha = (float) (topColor >> 24 & 255) / 255.0F; - float topRed = (float) (topColor >> 16 & 255) / 255.0F; - float topGreen = (float) (topColor >> 8 & 255) / 255.0F; - float topBlue = (float) (topColor & 255) / 255.0F; - float bottomAlpha = (float) (bottomColor >> 24 & 255) / 255.0F; - float bottomRed = (float) (bottomColor >> 16 & 255) / 255.0F; - float bottomGreen = (float) (bottomColor >> 8 & 255) / 255.0F; - float bottomBlue = (float) (bottomColor & 255) / 255.0F; + @Deprecated + public static void drawFilledArc(Matrix4f matrix4f, float x, float y, float radius, float startAngle, float endAngle, int color) { + float red = (float) (color >> 16 & 255) / 255.0F; + float green = (float) (color >> 8 & 255) / 255.0F; + float blue = (float) (color & 255) / 255.0F; + float alpha = (float) (color >> 24 & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferBuilder = tessellator.getBuffer(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + bufferBuilder.begin(VertexFormat.DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR); RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); - bufferBuilder.vertex(matrix4f, x1, y2, 0).color(topRed, topGreen, topBlue, topAlpha).next(); - bufferBuilder.vertex(matrix4f, x2, y2, 0).color(topRed, topGreen, topBlue, topAlpha).next(); - bufferBuilder.vertex(matrix4f, x2, y1, 0).color(bottomRed, bottomGreen, bottomBlue, bottomAlpha).next(); - bufferBuilder.vertex(matrix4f, x1, y1, 0).color(bottomRed, bottomGreen, bottomBlue, bottomAlpha).next(); + + for (float angle = startAngle; angle <= endAngle; angle += 1.0F) { + float x1 = x + MathHelper.cos(angle * 0.017453292F) * radius; + float y1 = y + MathHelper.sin(angle * 0.017453292F) * radius; + float x2 = x + MathHelper.cos((angle + 1.0F) * 0.017453292F) * radius; + float y2 = y + MathHelper.sin((angle + 1.0F) * 0.017453292F) * radius; + + bufferBuilder.vertex(matrix4f, x, y, 0).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, x1, y1, 0).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, x2, y2, 0).color(red, green, blue, alpha).next(); + } tessellator.draw(); RenderSystem.disableBlend(); } + /* ==== Drawing Quadrants, Arcs, and Triangles ==== */ + + /** + * Draws a filled Gradient quadrant + * + * @param matrix4f Matrix4f object to draw the quadrant + * @param xCenter X position of the quadrant + * @param yCenter Y position of the quadrant + * @param radius Radius of the quadrant + * @param startColor start color of the gradient + * @param endColor end color of the gradient + * @param quadrant Integer value of the quadrant of the circle. 1 == Top Right, 2 == Top Left, 3 == Bottom Right, 4 == Bottom Left + */ + public static void drawFilledGradientQuadrant(Matrix4f matrix4f, float xCenter, float yCenter, float radius, int startColor, int endColor, int quadrant) { + float startRed = (float) (startColor >> 16 & 255) / 255.0F; + float startGreen = (float) (startColor >> 8 & 255) / 255.0F; + float startBlue = (float) (startColor & 255) / 255.0F; + float startAlpha = (float) (startColor >> 24 & 255) / 255.0F; + + float endRed = (float) (endColor >> 16 & 255) / 255.0F; + float endGreen = (float) (endColor >> 8 & 255) / 255.0F; + float endBlue = (float) (endColor & 255) / 255.0F; + float endAlpha = (float) (endColor >> 24 & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + bufferBuilder.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); + RenderSystem.enableBlend(); + + bufferBuilder.vertex(matrix4f, xCenter, yCenter, 0).color(startRed, startGreen, startBlue, startAlpha).next(); + + for (int i = quadrant * 90; i <= quadrant * 90 + 90; i++) { + double x = xCenter + Math.sin(Math.toRadians(i)) * radius; + double y = yCenter + Math.cos(Math.toRadians(i)) * radius; + + // Interpolate the color based on the angle + float t = (float) (i - quadrant * 90) / 90.0f; + float red = startRed * (1 - t) + endRed * t; + float green = startGreen * (1 - t) + endGreen * t; + float blue = startBlue * (1 - t) + endBlue * t; + float alpha = startAlpha * (1 - t) + endAlpha * t; + + bufferBuilder.vertex(matrix4f, (float) x, (float) y, 0).color(red, green, blue, alpha).next(); + } + + tessellator.draw(); + RenderSystem.disableBlend(); + } + + /** + * Draws an arc + * + * @param matrix4f Matrix4f object to draw the arc + * @param xCenter X position of the arc's center + * @param yCenter Y position of the arc's center + * @param radius Radius of the arc's center circle + * @param startAngle start Angle of the arc + * @param endAngle end Angle of the arc + * @param thickness Thickness of the arc (width of the arc) + */ + public static void drawArc(Matrix4f matrix4f, float xCenter, float yCenter, float radius, float thickness, int color, int startAngle, int endAngle) { + float red = (float) (color >> 16 & 255) / 255.0F; + float green = (float) (color >> 8 & 255) / 255.0F; + float blue = (float) (color & 255) / 255.0F; + float alpha = (float) (color >> 24 & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + bufferBuilder.begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR); + RenderSystem.enableBlend(); + + for (int i = startAngle; i <= endAngle; i++) { + double innerX = xCenter + Math.sin(Math.toRadians(i)) * (radius - thickness); + double innerY = yCenter + Math.cos(Math.toRadians(i)) * (radius - thickness); + double outerX = xCenter + Math.sin(Math.toRadians(i)) * radius; + double outerY = yCenter + Math.cos(Math.toRadians(i)) * radius; + + bufferBuilder.vertex(matrix4f, (float) innerX, (float) innerY, 0).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, (float) outerX, (float) outerY, 0).color(red, green, blue, alpha).next(); + } + + tessellator.draw(); + + RenderSystem.disableBlend(); + } + + /** + * Draws a filled quadrant + * + * @param matrix4f Matrix4f object to draw the quadrant + * @param xCenter X position of the quadrant + * @param yCenter Y position of the quadrant + * @param radius Radius of the quadrant + * @param color color of the quadrant + * @param quadrant Integer value of the quadrant of the circle. 1 == Top Right, 2 == Top Left, 3 == Bottom Right, 4 == Bottom Left + */ + public static void drawFilledQuadrant(Matrix4f matrix4f, float xCenter, float yCenter, float radius, int color, int quadrant) { + float red = (float) (color >> 16 & 255) / 255.0F; + float green = (float) (color >> 8 & 255) / 255.0F; + float blue = (float) (color & 255) / 255.0F; + float alpha = (float) (color >> 24 & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + bufferBuilder.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); + RenderSystem.enableBlend(); + + bufferBuilder.vertex(matrix4f, xCenter, yCenter, 0).color(red, green, blue, alpha).next(); + + for (int i = quadrant * 90; i <= quadrant * 90 + 90; i++) { + double x = xCenter + Math.sin(Math.toRadians(i)) * radius; + double y = yCenter + Math.cos(Math.toRadians(i)) * radius; + bufferBuilder.vertex(matrix4f, (float) x, (float) y, 0).color(red, green, blue, alpha).next(); + } + + tessellator.draw(); + RenderSystem.disableBlend(); + + } + + /** + * Draws a Triangle with the given coordinates + * + * @param matrix4f + * @param x1 + * @param y1 + * @param x2 + * @param y2 + * @param x3 + * @param y3 + * @param color + */ + public static void drawOutlineTriangle(Matrix4f matrix4f, int x1, int y1, int x2, int y2, int x3, int y3, int color) { + float red = (float) (color >> 16 & 255) / 255.0F; + float green = (float) (color >> 8 & 255) / 255.0F; + float blue = (float) (color & 255) / 255.0F; + float alpha = (float) (color >> 24 & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferBuilder = tessellator.getBuffer(); + bufferBuilder.begin(VertexFormat.DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR); + + bufferBuilder.vertex(matrix4f, x1, y1, 0).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, x2, y2, 0).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, x3, y3, 0).color(red, green, blue, alpha).next(); + bufferBuilder.vertex(matrix4f, x1, y1, 0).color(red, green, blue, alpha).next(); + + tessellator.draw(); + } + + /** + * Draws a outline quadrant + * + * @param matrix4f Matrix4f object to draw the quadrant + * @param xCenter X position of the quadrant + * @param yCenter Y position of the quadrant + * @param radius Radius of the quadrant + * @param color color of the quadrant + * @param quadrant Integer value of the quadrant of the circle. 1 == Top Right, 2 == Top Left, 3 == Bottom Right, 4 == Bottom Left + */ + public static void drawOutlineQuadrant(Matrix4f matrix4f, float xCenter, float yCenter, float radius, int quadrant, int color) { + int startAngle = 0; + int endAngle = 0; + + if (quadrant == 1) { + startAngle = 270; + endAngle = 360; + } else if (quadrant == 2) { + startAngle = 180; + endAngle = 270; + } else if (quadrant == 3) { + startAngle = 90; + endAngle = 180; + } else if (quadrant == 4) { + endAngle = 90; + } + + drawArc(matrix4f, xCenter, yCenter, radius, 1f, color, startAngle, endAngle); + } + + /** + * Draws a filled rounded rectangle by drawing 1 main rectangle, 4 side rectangles, and 4 filled quadrants + * + * @param matrix4f Matrix4f object to draw the rounded rectangle + * @param x X pos + * @param y Y pos + * @param width Width of rounded rectangle + * @param height Height of rounded rectangle + * @param radius Radius of the quadrants / the rounded rectangle + * @param color Color of the rounded rectangle + */ + public static void drawRoundedRectangle(Matrix4f matrix4f, float x, float y, float width, float height, float radius, int color) { + drawRoundedRectangle(matrix4f, x, y, true, true, true, true, width, height, radius, color); + } + + /* ==== Drawing Rounded Rectangles ==== */ + + /** + * Draws a filled rounded rectangle by drawing 1 main rectangle, 4 side rectangles, and specified filled quadrants + * + * @param matrix4f Matrix4f object to draw the rounded rectangle + * @param x X pos + * @param y Y pos + * @param TL Whether to draw the top left quadrant + * @param TR Whether to draw the top right quadrant + * @param BL Whether to draw the bottom left quadrant + * @param BR Whether to draw the bottom right quadrant + * @param width Width of rounded rectangle + * @param height Height of rounded rectangle + * @param radius Radius of the quadrants / the rounded rectangle + * @param color Color of the rounded rectangle + */ + public static void drawRoundedRectangle(Matrix4f matrix4f, float x, float y, boolean TL, boolean TR, boolean BL, boolean BR, float width, float height, float radius, int color) { + // Draw the main rectangle + drawRectangle(matrix4f, x + radius, y + radius, width - 2 * radius, height - 2 * radius, color); + + // Draw rectangles at the sides + drawRectangle(matrix4f, x + radius, y, width - 2 * radius, radius, color); // top + drawRectangle(matrix4f, x + radius, y + height - radius, width - 2 * radius, radius, color); // bottom + drawRectangle(matrix4f, x, y + radius, radius, height - 2 * radius, color); // left + drawRectangle(matrix4f, x + width - radius, y + radius, radius, height - 2 * radius, color); // right + + if (TL) { + drawFilledQuadrant(matrix4f, x + radius, y + radius, radius, color, 2); + } else { + drawRectangle(matrix4f, x, y, radius, radius, color); + } + if (TR) { + drawFilledQuadrant(matrix4f, x + width - radius, y + radius, radius, color, 1); + } else { + drawRectangle(matrix4f, x + width - radius, y, radius, radius, color); + } + if (BL) { + drawFilledQuadrant(matrix4f, x + radius, y + height - radius, radius, color, 3); + } else { + drawRectangle(matrix4f, x, y + height - radius, radius, radius, color); + } + if (BR) { + drawFilledQuadrant(matrix4f, x + width - radius, y + height - radius, radius, color, 4); + } else { + drawRectangle(matrix4f, x + width - radius, y + height - radius, radius, radius, color); + } + } + + /** + * Draws an outline rounded gradient rectangle + * + * @param matrix4f Matrix4f object to draw the rounded gradient rectangle + * @param color1 is applied to the bottom-left vertex (x, y + height). + * @param color2 is applied to the bottom-right vertex (x + width, y + height). + * @param color3 is applied to the top-right vertex (x + width, y). + * @param color4 is applied to the top-left vertex (x, y). + * @param x X pos + * @param y Y pos + * @param width Width of rounded gradient rectangle + * @param height Height of rounded gradient rectangle + * @param radius Radius of the quadrants / the rounded gradient rectangle + */ + public static void drawOutlineGradientRoundedBox(Matrix4f matrix4f, float x, float y, float width, float height, float radius, float thickness, Color color1, Color color2, Color color3, Color color4) { + // Draw the rectangles for the outline with gradient + drawGradient(matrix4f, x + radius, y, width - radius * 2, thickness, color1.getRGB(), color2.getRGB(), Direction.LEFT_RIGHT); // Top rectangle + drawGradient(matrix4f, x + radius, y + height - thickness, width - radius * 2, thickness, color3.getRGB(), color4.getRGB(), Direction.RIGHT_LEFT); // Bottom rectangle + + drawGradient(matrix4f, x, y + radius, thickness, height - radius * 2, color4.getRGB(), color1.getRGB(), Direction.BOTTOM_TOP); // Left rectangle + drawGradient(matrix4f, x + width - thickness, y + radius, thickness, height - radius * 2, color2.getRGB(), color3.getRGB(), Direction.TOP_BOTTOM); // Right rectangle + + // Draw the arcs at the corners for the outline with gradient + drawArc(matrix4f, x + radius, y + radius, radius, thickness, color1.getRGB(), 180, 270); // Top-left arc + drawArc(matrix4f, x + width - radius, y + radius, radius, thickness, color2.getRGB(), 90, 180); // Top-right arc + drawArc(matrix4f, x + width - radius, y + height - radius, radius, thickness, color3.getRGB(), 0, 90); // Bottom-right arc + drawArc(matrix4f, x + radius, y + height - radius, radius, thickness, color4.getRGB(), 270, 360); // Bottom-left arc + } + + public static void drawCutRectangle(DrawContext drawContext, int x1, int y1, int x2, int y2, int z, int color, int cornerRadius) { + // Draw the rectangles + drawContext.fill(x1 + cornerRadius, y1, x2 - cornerRadius, y1 + cornerRadius, z, color); + drawContext.fill(x1 + cornerRadius, y2 - cornerRadius, x2 - cornerRadius, y2, z, color); + drawContext.fill(x1, y1 + cornerRadius, x2, y2 - cornerRadius, z, color); + } + + /** + * Draws a rounded rectangle with a shadow in a bad way + * + * @param matrix4f Matrix4f object to draw the rounded rectangle + * @param x X pos + * @param y Y pos + * @param width Width of rounded rectangle + * @param height Height of rounded rectangle + * @param radius Radius of the quadrants / the rounded rectangle + * @param color Color of the rounded rectangle + * @param shadowOpacity opacity of the shadow + * @param shadowOffsetX X offset of the shadow + * @param shadowOffsetY Y offset of the shadow + */ + public static void drawRoundedRectangleWithShadowBadWay(Matrix4f matrix4f, float x, float y, float width, float height, float radius, int color, int shadowOpacity, float shadowOffsetX, float shadowOffsetY) { + // First, render the shadow + drawRoundedRectangle(matrix4f, x + shadowOffsetX, y + shadowOffsetY, width, height, radius, ColorHelper.getColor(0, 0, 0, shadowOpacity)); + + // Then, render the rounded rectangle + drawRoundedRectangle(matrix4f, x, y, width, height, radius, color); + } + + /** + * Draws a rounded gradient rectangle + * + * @param matrix Matrix4f object to draw the rounded gradient rectangle + * @param color1 is applied to the bottom-left vertex (x, y + height). + * @param color2 is applied to the bottom-right vertex (x + width, y + height). + * @param color3 is applied to the top-right vertex (x + width, y). + * @param color4 is applied to the top-left vertex (x, y). + * @param x X pos + * @param y Y pos + * @param width Width of rounded gradient rectangle + * @param height Height of rounded gradient rectangle + * @param radius Radius of the quadrants / the rounded gradient rectangle + */ + public static void drawRoundedGradientRectangle(Matrix4f matrix, Color color1, Color color2, Color color3, Color color4, float x, float y, float width, float height, float radius) { + drawRoundedGradientRectangle(matrix, color1, color2, color3, color4, x, y, width, height, radius, true, true, true, true); + } + + /** + * Draws a rounded gradient rectangle + * + * @param matrix Matrix4f object to draw the rounded gradient rectangle + * @param color1 is applied to the bottom-left vertex (x, y + height). + * @param color2 is applied to the bottom-right vertex (x + width, y + height). + * @param color3 is applied to the top-right vertex (x + width, y). + * @param color4 is applied to the top-left vertex (x, y). + * @param x X pos + * @param y Y pos + * @param width Width of rounded gradient rectangle + * @param height Height of rounded gradient rectangle + * @param radius Radius of the quadrants / the rounded gradient rectangle + */ + public static void drawRoundedGradientRectangle(Matrix4f matrix, Color color1, Color color2, Color color3, Color color4, float x, float y, float width, float height, float radius, boolean TL, boolean TR, boolean BL, boolean BR) { + 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.colorMask(true, true, true, true); + + drawRoundedRectangle(matrix, x, y, TL, TR, BL, BR, width, height, (int) radius, color1.getRGB()); + + RenderSystem.blendFunc(GL40C.GL_DST_ALPHA, GL40C.GL_ONE_MINUS_DST_ALPHA); + + RenderSystem.enableBlend(); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + + BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); + bufferBuilder.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); + + bufferBuilder.vertex(matrix, x, y + height, 0.0F).color(color1.getRGB()).next(); + bufferBuilder.vertex(matrix, x + width, y + height, 0.0F).color(color2.getRGB()).next(); + bufferBuilder.vertex(matrix, x + width, y, 0.0F).color(color3.getRGB()).next(); + bufferBuilder.vertex(matrix, x, y, 0.0F).color(color4.getRGB()).next(); + BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); + RenderSystem.disableBlend(); + + RenderSystem.defaultBlendFunc(); + } + + /* ==== Drawing Lines ==== */ + public static void drawVerticalLine(Matrix4f matrix4f, float x, float y1, float height, float thickness, int color) { + drawRectangle(matrix4f, x, y1, thickness, height, color); + } + + public static void drawHorizontalLine(Matrix4f matrix4f, float x1, float width, float y, float thickness, int color) { + drawRectangle(matrix4f, x1, y, width, thickness, color); + } /** * Draws an outlined box on the screen. @@ -207,4 +799,53 @@ public static void drawOutlinedBox(DrawContext drawContext, int x1, int y1, int drawContext.fill(x2 - 1, y1 + 1, x2, y2 - 1, color); } -} + /** + * This method assumes that the x, y coords are the origin of a widget. + * + * @param x X position of widget + * @param y Y position of widget + * @param scale Scale the matrices + */ + public static void scaleAndPosition(MatrixStack matrices, float x, float y, float scale) { + matrices.push(); // Save the current transformation state + + // Translate the origin back to the desired position + matrices.translate(x, y, 0); + + // Scale the matrix + matrices.scale(scale, scale, 1.0F); + + matrices.translate(-x, -y, 0); + } + + /** + * This method scales the matrices by the centre of the widget + * + * @param x X position of widget + * @param y Y position of widget + * @param height height of widget + * @param width width of widget + * @param scale Scale the matrices + */ + public static void scaleAndPosition(MatrixStack matrices, float x, float y, float width, float height, float scale) { + matrices.push(); // Save the current transformation state + + // Translate the origin back to the desired position + matrices.translate(x + width / 2.0f, y + height / 2.0f, 0); + + // Scale the matrix + matrices.scale(scale, scale, 1.0F); + + matrices.translate(-(x + width / 2.0f), -(y + height / 2.0f), 0); + } + + public static void stopScaling(MatrixStack matrices) { + matrices.pop(); // Restore the previous transformation state + } + + public enum Direction { + /* LEFT_RIGHT means from left to right. Same for others */ + LEFT_RIGHT, TOP_BOTTOM, RIGHT_LEFT, BOTTOM_TOP + } + +} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java index 0fade01..5301aaa 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java +++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java @@ -1,232 +1,269 @@ package com.tanishisherewith.dynamichud.helpers; -import com.mojang.blaze3d.systems.RenderSystem; -import com.tanishisherewith.dynamichud.util.CustomItemRenderer; -import com.tanishisherewith.dynamichud.util.CustomTextRenderer; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.VertexConsumerProvider; -import net.minecraft.item.ItemStack; +import net.minecraft.client.texture.NativeImage; import net.minecraft.util.Identifier; -/** - * This class provides helper methods for drawing textures on the screen. - */ -public class TextureHelper extends DrawContext { - public static CustomTextRenderer customTextRenderer; - public static CustomItemRenderer customItemRenderer; +import java.io.IOException; +import java.io.InputStream; +public class TextureHelper { + static MinecraftClient mc = MinecraftClient.getInstance(); - public TextureHelper(MinecraftClient client, VertexConsumerProvider.Immediate vertexConsumers) { - super(client, vertexConsumers); + public static NativeImage loadTexture(Identifier textureId) { + if(mc.getResourceManager().getResource(textureId).isPresent()) { + try (InputStream inputStream = mc.getResourceManager().getResource(textureId).get().getInputStream()) { + return NativeImage.read(inputStream); + } catch (IOException e) { + throw new RuntimeException("Failed to load texture " + textureId, e); + } + } + return null; } + public static NativeImage resizeTexture(NativeImage image, int newWidth, int newHeight) { + NativeImage result = new NativeImage(newWidth, newHeight, false); - /** - * Draws an item texture on the screen. - * - * @param itemStack The item stack to render the texture for - * @param x The x position to draw the texture at - * @param y The y position to draw the texture at - */ - public static void drawItemTexture(DrawContext drawContext, - ItemStack itemStack, - int x, - int y) { - drawContext.drawItem(itemStack, x, y); - } + int oldWidth = image.getWidth(); + int oldHeight = image.getHeight(); - /** - * Draws the texture of the item in the player's main hand on the screen. - * - * @param client The Minecraft client instance - * @param x The x position to draw the texture at - * @param y The y position to draw the texture at - */ - public static void drawMainHandTexture(DrawContext drawContext, - MinecraftClient client, - int x, - int y) { - assert client.player != null; - ItemStack mainHandItem = client.player.getMainHandStack(); - drawItemTexture(drawContext, mainHandItem, x, y); - } + for (int y = 0; y < newHeight; y++) { + for (int x = 0; x < newWidth; x++) { + int srcX = x * oldWidth / newWidth; + int srcY = y * oldHeight / newHeight; - /** - * Draws a textured rectangle on the screen. - * - * @param x The x position of the top left corner of the rectangle - * @param y The y position of the top left corner of the rectangle - * @param u The x position of the texture within the texture image - * @param v The y position of the texture within the texture image - * @param width The width of the rectangle - * @param height The height of the rectangle - * @param textureWidth The width of the texture image - * @param textureHeight The height of the texture image - */ - public static void drawTexture(DrawContext drawContext, Identifier texture, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight) { - drawContext.drawTexture(texture, x, y, u, v, width, height, textureWidth, textureHeight); - } + result.setColor(x, y, image.getColor(srcX, srcY)); + } + } - /** - * Draws a textured rectangle on the screen with a specified color. - * - * @param x The x position of the top left corner of the rectangle - * @param y The y position of the top left corner of the rectangle - * @param u The x position of the texture within the texture image - * @param v The y position of the texture within the texture image - * @param width The width of the rectangle - * @param height The height of the rectangle - * @param color The color to draw the rectangle with - */ - public static void drawTexturedRect(DrawContext drawContext, Identifier texture, int x, int y, int u, int v, int width, int height, int color) { - RenderSystem.setShaderColor((color >> 16 & 255) / 255.0F, - (color >> 8 & 255) / 255.0F, - (color & 255) / 255.0F, - (color >> 24 & 255) / 255.0F); - drawContext.drawTexture(texture, x, y, u, v, width, height); + 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_diff, y_diff, blue, red, green; + int offset, a, b, c, d, index; + + for (int i=0;i { - textX = x + (16 - textWidth) / 2; - textY = y - textHeight; + // Indexes of the 4 surrounding pixels + a = image.getColor(x, y); + b = image.getColor(x+1, y); + c = image.getColor(x, y+1); + d = image.getColor(x+1, y+1); + + // Blue element + blue = (a&0xff)*(1-x_diff)*(1-y_diff) + (b&0xff)*(x_diff)*(1-y_diff) + + (c&0xff)*(y_diff)*(1-x_diff) + (d&0xff)*(x_diff*y_diff); + + // Green element + 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); + + result.setColor(j, i, + ((((int)red)<<16)&0xff0000) | + ((((int)green)<<8)&0xff00) | + ((int)blue)&0xff); } - case BELOW -> { - textX = x + (17 - textWidth) / 2; - textY = y + 16; + } + + return result; + } + 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 alpha = (argb >> 24) & 0xFF; + int red = 255 - ((argb >> 16) & 0xFF); + int green = 255 - ((argb >> 8) & 0xFF); + int blue = 255 - (argb & 0xFF); + + int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; + + result.setColor(x, y, newArgb); } - case LEFT -> { - textX = x - textWidth - 2; - textY = y + (16 - textHeight) / 2; + } + + return result; + } + + public static NativeImage rotateTexture(NativeImage image, int degrees) { + int width = image.getWidth(); + int height = image.getHeight(); + NativeImage result = new NativeImage(height, width, false); + + double centerX = width / 2.0; + double centerY = height / 2.0; + double angle = Math.toRadians(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); + + if (newX >= 0 && newX < width && newY >= 0 && newY < height) { + result.setColor(newY, newX, image.getColor(x, y)); + } } - case RIGHT -> { - textX = x + 18; - textY = y + (16 - textHeight) / 2; + } + + return result; + } + + private static NativeImage flipTextureHorizontally(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++) { + result.setColor(width - x - 1, y, image.getColor(x, y)); } } - // Draw semi-opaque black rectangle - if (text != null) { - if (textBackground && !text.trim().isEmpty()) { - int backgroundColor = 0x40000000; // ARGB format: 50% opaque black - drawContext.fill(textX - 1, textY - 1, textX + textWidth + 1, textY + textHeight + 1, backgroundColor); + return result; + } + + private static NativeImage flipTextureVertically(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++) { + result.setColor(x, height - y - 1, image.getColor(x, y)); } - // Draw the scaled text at the calculated position - drawContext.getMatrices().push(); - drawContext.getMatrices().scale(scale, scale, 1.0f); - float scaledX = textX / scale; - float scaledY = textY / scale; - drawContext.drawText(textRenderer, text, (int) scaledX, (int) scaledY, color, false); - drawContext.getMatrices().pop(); } - // Draw the item texture - drawContext.drawItem(itemStack, x, y); + + return result; } - /** - * Draws an item texture on the screen with text at a specified position relative to it. - * - * @param itemScale The scale for the item to be rendered at - * @param textRenderer The text renderer instance used for rendering the text - * @param itemStack The item stack to render the texture for - * @param x The x position to draw the texture at - * @param y The y position to draw the texture at - * @param text The text to draw relative to the texture - * @param color The color to draw the text with - * @param position The position of the text relative to the texture (ABOVE, BELOW, LEFT, or RIGHT) - * @param textScale The scale factor to apply to the text (1.0 is normal size) - */ - public static void drawItemTextureWithTextAndScale(DrawContext drawContext, - float itemScale, - TextRenderer textRenderer, - ItemStack itemStack, - int x, - int y, - String text, - int color, - Position position, - float textScale, - boolean textBackground) { - if (text != null && !text.trim().isEmpty()) { - // Calculate the position of the text based on its size and the specified position - int textWidth = (int) (textRenderer.getWidth(text) * textScale); - int textHeight = (int) (textRenderer.fontHeight * textScale); - int textX = switch (position) { - case ABOVE, BELOW -> x + (int) ((16 * itemScale - textWidth) / 2); - case LEFT -> x - textWidth - 2; - case RIGHT -> x + (int) (16 * itemScale + 2); - }; - int textY = switch (position) { - case ABOVE -> y - textHeight - 2; - case BELOW -> y + (int) (16 * itemScale + 2); - case LEFT, RIGHT -> y + (int) ((16 * itemScale - textHeight) / 2); - }; - - // Draw semi-opaque black rectangle - if (textBackground) { - int backgroundColor = 0x40000000; // ARGB format: 50% opaque black - drawContext.fill(textX, textY - 1, textX + textWidth + 1, textY + textHeight + 1, backgroundColor); + public static NativeImage flipTexture(NativeImage image, boolean flipVertically) { + if (flipVertically) { + return flipTextureVertically(image); + } else { + return flipTextureHorizontally(image); + } + } + + public static NativeImage applyGrayScaleFilter(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 alpha = (argb >> 24) & 0xFF; + int red = (argb >> 16) & 0xFF; + int green = (argb >> 8) & 0xFF; + int blue = argb & 0xFF; + + int gray = (red + green + blue) / 3; + int newArgb = (alpha << 24) | (gray << 16) | (gray << 8) | gray; + + result.setColor(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); - // Draw the scaled text at the calculated position - drawContext.getMatrices().push(); - drawContext.getMatrices().scale(textScale, textScale, 11.0f); - float scaledX = textX / textScale; - float scaledY = textY / textScale; - drawContext.drawText(textRenderer, text, (int) scaledX + 1, (int) scaledY +1, color, false); - drawContext.getMatrices().pop(); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + result.setColor(j, i, image.getColor(x + j, y + i)); + } } - customItemRenderer = new CustomItemRenderer(itemStack, itemScale); - customItemRenderer.draw(drawContext, x, y, color); + + return result; } - public enum Position { - ABOVE("Above"), - RIGHT("Right"), - BELOW("Below"), - LEFT("Left"); - private final String name; + public static NativeImage tintTexture(NativeImage image, int color) { + 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 alpha = (argb >> 24) & 0xFF; + int red = ((argb >> 16) & 0xFF) * ((color >> 16) & 0xFF) / 255; + int green = ((argb >> 8) & 0xFF) * ((color >> 8) & 0xFF) / 255; + int blue = (argb & 0xFF) * (color & 0xFF) / 255; - Position(String name) { - this.name = name; + int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; + + result.setColor(x, y, newArgb); + } } - public static Position getByUpperCaseName(String name) { - if (name == null || name.isEmpty()) { - return null; + return result; + } + + public static NativeImage overlayTexture(NativeImage image, NativeImage overlay) { + 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 argb1 = image.getColor(x, y); + int argb2 = overlay.getColor(x, y); + + int alpha = Math.max((argb1 >> 24) & 0xFF, (argb2 >> 24) & 0xFF); + int red = Math.min(255, ((argb1 >> 16) & 0xFF) + ((argb2 >> 16) & 0xFF)); + int green = Math.min(255, ((argb1 >> 8) & 0xFF) + ((argb2 >> 8) & 0xFF)); + int blue = Math.min(255, (argb1 & 0xFF) + (argb2 & 0xFF)); + + int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue; + + result.setColor(x, y, newArgb); } + } + + return result; + } + + public static int getAverageColor(NativeImage image) { + long redTotal = 0; + long greenTotal = 0; + long blueTotal = 0; + int pixelCount = image.getWidth() * image.getHeight(); - return Position.valueOf(name.toUpperCase()); + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int argb = image.getColor(x, y); + + redTotal += (argb >> 16) & 0xFF; + greenTotal += (argb >> 8) & 0xFF; + blueTotal += argb & 0xFF; + } } + + 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/huds/AbstractMoveableScreen.java b/src/main/java/com/tanishisherewith/dynamichud/huds/AbstractMoveableScreen.java deleted file mode 100644 index 5d33fb5..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/huds/AbstractMoveableScreen.java +++ /dev/null @@ -1,214 +0,0 @@ -package com.tanishisherewith.dynamichud.huds; - -import com.tanishisherewith.dynamichud.handlers.DefaultDragHandler; -import com.tanishisherewith.dynamichud.handlers.DefaultMouseHandler; -import com.tanishisherewith.dynamichud.handlers.DragHandler; -import com.tanishisherewith.dynamichud.handlers.MouseHandler; -import com.tanishisherewith.dynamichud.util.DynamicUtil; -import com.tanishisherewith.dynamichud.util.colorpicker.ColorGradientPicker; -import com.tanishisherewith.dynamichud.util.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.slider.SliderWidget; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -public abstract class AbstractMoveableScreen extends Screen { - protected final DynamicUtil dynamicutil; // The DynamicUtil instance used by this screen - protected MinecraftClient mc = MinecraftClient.getInstance(); - protected Widget selectedWidget = null; // The currently selected widget - protected int dragStartX = 0, dragStartY = 0; // The starting position of a drag operation - protected List contextMenus = new ArrayList<>(); // The context menu that is currently displayed - protected ColorGradientPicker colorPicker = null; // The color picker that is currently displayed - protected Widget sliderWigdet = null; // The widget that is currently being edited by the slider - protected List Sliders = new ArrayList<>(); // The List of sliders - protected MouseHandler mouseHandler; - protected DragHandler dragHandler; - protected int gridSize = 3; // The size of each grid cell in pixels - protected boolean ShouldPause = false; // To pause if the screen is opened or not - protected boolean ShouldBeAffectedByResize = false; // If the stuff drawn on screen to be affected by screen resize or not - protected int widgetX; - protected int widgetY; - - - /** - * Constructs a AbstractMoveableScreen object. - * - * @param dynamicutil The DynamicUtil instance used by this screen - */ - public AbstractMoveableScreen(Text title, DynamicUtil dynamicutil) { - super(title); - this.dynamicutil = dynamicutil; - updateMouseHandler(this.colorPicker, contextMenus, Sliders); - dragHandler = new DefaultDragHandler(); - } - - /** - * Handles mouse dragging on this screen. - * - * @param mouseX - Current X position of mouse cursor. - * @param mouseY - Current Y position of mouse cursor. - * @param button - Mouse button being dragged. - * @param deltaX - Change in X position since last call to this method. - * @param deltaY - Change in Y position since last call to this method. - * @return true if mouse dragging was handled by this screen. - */ - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - if (mouseHandler.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) { - return true; - } - if (selectedWidget != null && selectedWidget.isDraggable) { - // Update the position of the widget while dragging - int newX = (int) (mouseX - dragStartX); - int newY = (int) (mouseY - dragStartY); - - // Snap the widget to the grid - newX = (newX / gridSize) * gridSize; - newY = (newY / gridSize) * gridSize; - - selectedWidget.setX(newX); - selectedWidget.setY(newY); - return true; - } - return false; - } - - /** - * Handles mouse clicks on this screen. - * - * @param mouseX - X position of mouse cursor. - * @param mouseY - Y position of mouse cursor. - * @param button - Mouse button that was clicked. - * @return true if mouse click was handled by this screen, false otherwise. - */ - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (mouseHandler.mouseClicked(mouseX, mouseY, button)) { - return true; - } - - for (Widget widget : dynamicutil.getWidgetManager().getWidgets()) { - if (widget.getWidgetBox().contains(widget, mouseX, mouseY, Widget.getScale())) { - // Start dragging the widget - colorPicker = null; - contextMenus.clear(); - Sliders.clear(); - if (button == 1) { // Right-click - handleRightClickOnWidget(widget); - } else if (button == 0) { - widget.enabled = !widget.enabled; - } - if (dragHandler.startDragging(widget, mouseX, mouseY) && button == 0 && widget.isDraggable) { - selectedWidget = widget; - for (ContextMenu contextmenu : contextMenus) { - contextmenu.updatePosition(); - } - for (SliderWidget sliderWidget : Sliders) { - sliderWidget.updatePosition(); - } - return true; - } - } - } - return false; - } - - /** - * Handles mouse release events on this screen. - * - * @param mouseX The current x position of the mouse cursor - * @param mouseY The current y position of the mouse cursor - * @param button The mouse button that was released - * @return True if the mouse release event was handled by this screen, false otherwise - */ - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - // Stop dragging or scaling the widget - if (mouseHandler.mouseReleased(mouseX, mouseY, button)) { - return true; - } - if (selectedWidget != null) { - selectedWidget = null; - return true; - } - return contextMenus != null; - } - - /** - * Renders this screen and its widgets on the screen. - * - * @param drawContext The matrix stack used for rendering - * @param mouseX The current x position of the mouse cursor - * @param mouseY The current y position of the mouse cursor - * @param delta The time elapsed since the last frame in seconds - */ - @Override - public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { - super.render(drawContext, mouseX, mouseY, delta); - - // Draw each widget - for (Widget widget : dynamicutil.getWidgetManager().getWidgets()) { - widget.render(drawContext); - } - - // Draw the slider and other stuff - for (SliderWidget sliderWidget : Sliders) { - sliderWidget.render(drawContext); - } - for (ContextMenu contextMenu : contextMenus) { - contextMenu.render(drawContext); - } - - if (colorPicker != null) { - colorPicker.render(drawContext); - } - - if (selectedWidget != null) { - widgetX = selectedWidget.getX(); - widgetY = selectedWidget.getY(); - } - - updateMouseHandler(colorPicker, contextMenus, Sliders); - } - - private void updateMouseHandler(ColorGradientPicker colorPicker, List contextMenus, List Sliders) { - this.colorPicker = colorPicker; - this.contextMenus = contextMenus; - this.Sliders = Sliders; - mouseHandler = new DefaultMouseHandler(colorPicker, contextMenus, Sliders); - } - - public void setGridSize(int gridSize) { - this.gridSize = gridSize; - } - - public void setShouldPause(boolean shouldpause) { - this.ShouldPause = shouldpause; - } - - public void setShouldBeAffectedByResize(boolean shouldBeAffectedByResize) { - this.ShouldBeAffectedByResize = shouldBeAffectedByResize; - } - - @Override - public void resize(MinecraftClient client, int width, int height) { - if (ShouldBeAffectedByResize) - super.resize(client, width, height); - } - - @Override - public boolean shouldPause() { - return ShouldPause; - } - - protected abstract boolean handleRightClickOnWidget(Widget widget); - - protected abstract void menu(Widget widget, int x, int y); -} - diff --git a/src/main/java/com/tanishisherewith/dynamichud/huds/MoveableScreen.java b/src/main/java/com/tanishisherewith/dynamichud/huds/MoveableScreen.java deleted file mode 100644 index 58c7a07..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/huds/MoveableScreen.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.tanishisherewith.dynamichud.huds; - -import com.tanishisherewith.dynamichud.helpers.ColorHelper; -import com.tanishisherewith.dynamichud.helpers.TextureHelper; -import com.tanishisherewith.dynamichud.util.DynamicUtil; -import com.tanishisherewith.dynamichud.util.colorpicker.ColorGradientPicker; -import com.tanishisherewith.dynamichud.util.contextmenu.ContextMenu; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.armor.ArmorWidget; -import com.tanishisherewith.dynamichud.widget.item.ItemWidget; -import com.tanishisherewith.dynamichud.widget.slider.SliderWidget; -import com.tanishisherewith.dynamichud.widget.slider.SliderWidgetBuilder; -import com.tanishisherewith.dynamichud.widget.text.TextWidget; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.text.Text; - -import java.awt.*; - -public class MoveableScreen extends AbstractMoveableScreen { - SliderWidget SliderWidget; - /** - * Constructs a AbstractMoveableScreen object. - * - * @param title - * @param dynamicutil The DynamicUtil instance used by this screen - */ - public MoveableScreen(Text title, DynamicUtil dynamicutil) { - super(title, dynamicutil); - setGridSize(1); - setShouldPause(false); - setShouldBeAffectedByResize(false); - } - - @Override - public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { - super.render(drawContext, mouseX, mouseY, delta); - assert client != null; - /*SliderWidget = new SliderWidgetBuilder(client) - .setX(client.getWindow().getScaledWidth() - 120) - .setY(client.getWindow().getScaledHeight() - 20) - .setWidth(105) - .setHeight(10) - .setLabel("Scale") - .setValue(Widget.getScale()) - .getValue(Widget::setScale) - .setMinValue(0.5f) - .setMaxValue(4f) - .setSelectedWidget(null) - .build();*/ - drawContext.drawTextWithShadow(textRenderer, "Editors Screen", (int) (MinecraftClient.getInstance().getWindow().getScaledWidth() / 2f - textRenderer.getWidth("Editor Screen") / 2f), 5, ColorHelper.ColorToInt(Color.WHITE)); - // SliderWidget.render(drawContext); - } - - @Override - protected boolean handleRightClickOnWidget(Widget widget) { - selectedWidget = widget; - sliderWigdet = widget; - // Show context menu - menu(widget, widgetX, widgetY); - return true; - } - - @Override - protected void menu(Widget widget, int x, int y) { - contextMenus.clear(); - contextMenus.add(new ContextMenu(mc, x, y + widget.getHeight() + 5, selectedWidget, this)); - if (widget instanceof ArmorWidget armorWidget) { - ArmorWidgetMenu(armorWidget, x, y); - } - if (widget instanceof ItemWidget itemWidget) { - ItemWidgetMenu(itemWidget, x, y); - } - if (widget instanceof TextWidget textWidget) { - TextWidgetMenu(textWidget, x, y); - } - } - - protected void ItemWidgetMenu(ItemWidget itemWidget, int x, int y) { - Sliders.clear(); - colorPicker = null; - contextMenus.clear(); - } - - protected void ArmorWidgetMenu(ArmorWidget armorWidget, int x, int y) { - Sliders.clear(); - contextMenus.get(0).setHeightFromWidget(14); - contextMenus.get(0).setPadding(5); - contextMenus.get(0).addEnumCycleOption("", TextureHelper.Position.values(), () -> armorWidget.currentTextPosition[0], newPosition -> { - armorWidget.currentTextPosition[0] = newPosition; - }); - } - - protected void TextWidgetMenu(TextWidget textWidget, int x, int y) { - contextMenus.get(0).setHeightFromWidget(2); - contextMenus.get(0).setPadding(5); - - contextMenus.get(0).addOption("Shadow", () -> { - textWidget.setShadow(!textWidget.hasShadow()); - }); - contextMenus.get(0).addOption("Rainbow", () -> { - textWidget.setRainbow(!textWidget.hasRainbow()); - }); - if (!textWidget.getText().isEmpty()) { - contextMenus.get(0).addOption("TextColor", () -> { - textWidget.toggleTextColorOption(); - - if (textWidget.isTextcolorOptionEnabled()) - colorPicker = new ColorGradientPicker(mc, widgetX + 110, widgetY + textWidget.getHeight() + 5, textWidget.getTextcolor(), textWidget::setTextColor, 50, 100, selectedWidget); - else - colorPicker = null; - }); - } - if (!textWidget.getDataText().trim().isEmpty()) { - contextMenus.get(0).addOption("DataColor", () -> { - textWidget.toggleDataColorOption(); - - if (textWidget.isDatacolorOptionEnabled()) - colorPicker = new ColorGradientPicker(mc, widgetX + 110, widgetY + textWidget.getHeight() + 5, textWidget.getDatacolor(), textWidget::setDataColor, 50, 100, selectedWidget); - else - colorPicker = null; - }); - } - /* contextMenus.get(0).addOption("SubMenu: ",()-> - { - if (contextMenus.size()>1) contextMenus.remove(1); - contextMenus.add(new ContextMenu(mc, contextMenus.get(0).getX() + 40, contextMenus.get(0).getOptionY(6), selectedWidget,this)); - contextMenus.get(1).addOption("Option: 1",()->System.out.println("Pressed 1 ")); - contextMenus.get(1).addOption("Option: 2",()->System.out.println("Pressed 2 ")); - contextMenus.get(1).addOption("Option: 3",()->System.out.println("Pressed 3 ")); - }); - contextMenus.get(0).addDataTextOption(("Enter data"), data -> { - System.out.println("Entered data: " + data); - },widgetX,widgetY); - contextMenus.get(0).addDoubleTextOption(("Enter double"), data -> { - System.out.println("Entered data: " + data); - },widgetX,widgetY);*/ - - SliderWidget sliderWidget =new SliderWidgetBuilder(client) - .setX(x) - .setY(y) - .setWidth(105) - .setHeight(contextMenus.get(0).getHeight() + 7) - .setLabel("Rainbow Speed") - .setValue(textWidget.getRainbowSpeed()) - .setMinValue(5f) - .setMaxValue(25.0f) - .getValue(TextWidget::setRainbowSpeed) - .setSelectedWidget(selectedWidget) - .build(); - Sliders.add(sliderWidget); - } - - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - // if(SliderWidget.mouseDragged(mouseX,mouseY,button,deltaX,deltaY)) - //{ - // return true; - //} - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - // if(SliderWidget.mouseClicked(mouseX,mouseY,button)) - // { - // SliderWidget.updatePosition(); - // return true; - // } - return super.mouseClicked(mouseX, mouseY, button); - } -} - - diff --git a/src/main/java/com/tanishisherewith/dynamichud/interfaces/IWigdets.java b/src/main/java/com/tanishisherewith/dynamichud/interfaces/IWigdets.java deleted file mode 100644 index d27675e..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/interfaces/IWigdets.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.tanishisherewith.dynamichud.interfaces; - -import com.tanishisherewith.dynamichud.util.DynamicUtil; - -public interface IWigdets { - - void addWigdets(DynamicUtil dynamicUtil); - - void addMainMenuWigdets(DynamicUtil dynamicUtil); - - void loadWigdets(DynamicUtil dynamicUtil); -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/interfaces/TextGenerator.java b/src/main/java/com/tanishisherewith/dynamichud/interfaces/TextGenerator.java deleted file mode 100644 index 009f0dc..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/interfaces/TextGenerator.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.tanishisherewith.dynamichud.interfaces; - -@FunctionalInterface -public interface TextGenerator { - String generateText(); -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/interfaces/WidgetLoading.java b/src/main/java/com/tanishisherewith/dynamichud/interfaces/WidgetLoading.java deleted file mode 100644 index fe7a7be..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/interfaces/WidgetLoading.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.tanishisherewith.dynamichud.interfaces; - -import com.tanishisherewith.dynamichud.helpers.TextureHelper; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.armor.ArmorWidget; -import com.tanishisherewith.dynamichud.widget.item.ItemWidget; -import com.tanishisherewith.dynamichud.widget.text.TextWidget; -import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; - -import java.awt.*; - -public interface WidgetLoading { - default Widget loadWidgetsFromTag(String className, NbtCompound widgetTag) { - if (className.equals(TextWidget.class.getName())) { - TextWidget widget = new TextWidget(MinecraftClient.getInstance(), "", () -> "", 0, 0, false, false, -1, -1, true); - widget.readFromTag(widgetTag); - return widget; - } - if (className.equals(ArmorWidget.class.getName())) { - ArmorWidget widget = new ArmorWidget(MinecraftClient.getInstance(), EquipmentSlot.CHEST, 0, 0, false, TextureHelper.Position.ABOVE, () -> "", () -> Color.RED, true, ""); - widget.readFromTag(widgetTag); - return widget; - } - if (className.equals(ItemWidget.class.getName())) { - ItemWidget widget = new ItemWidget(MinecraftClient.getInstance(), () -> ItemStack.EMPTY, 0, 0, true, TextureHelper.Position.ABOVE, () -> "", () -> Color.WHITE, true, ""); - widget.readFromTag(widgetTag); - return widget; - } - return null; - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java deleted file mode 100644 index 345e818..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/mixins/OptionsScreenMixin.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.tanishisherewith.dynamichud.mixins; - - -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.slider.ScaleSliderWidget; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.option.OptionsScreen; -import net.minecraft.client.gui.widget.EmptyWidget; -import net.minecraft.client.gui.widget.GridWidget; -import net.minecraft.client.gui.widget.SimplePositioningWidget; -import net.minecraft.client.option.GameOptions; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.text.DecimalFormat; -@Mixin(OptionsScreen.class) -public class OptionsScreenMixin extends Screen { - private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##"); - private final GameOptions settings = MinecraftClient.getInstance().options; - - - protected OptionsScreenMixin(Text title) { - super(title); - } - - @Inject(at = @At("TAIL"), method = "init") - public void addButtons(CallbackInfo ci) { - int width = 100; - int height = 20; - int x = this.width - width - 10; - int y = this.height - height- 5; - String formattedValue = DECIMAL_FORMAT.format(Widget.getScale()); - ScaleSliderWidget scaleSlider = new ScaleSliderWidget(x, y, width, height, Text.of("Widgets Scale: " + formattedValue), Widget.getScale(), 0.5f, 3f); - this.addDrawableChild(scaleSlider); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java new file mode 100644 index 0000000..1ce36e5 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/mixins/ScreenMixin.java @@ -0,0 +1,41 @@ +package com.tanishisherewith.dynamichud.mixins; + +import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.widget.WidgetManager; +import com.tanishisherewith.dynamichud.widget.WidgetRenderer; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import 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; + +@Mixin(Screen.class) +public abstract class ScreenMixin { + @Shadow + public int width; + + @Shadow + public int height; + + @Inject(at = @At("TAIL"), method = "render") + private void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + for (WidgetRenderer widgetRenderer : DynamicHUD.getWidgetRenderers()) { + widgetRenderer.renderWidgets(context, mouseX, mouseY); + } + } + + @Inject(at = @At("HEAD"), method = "resize") + private void onScreenResize(MinecraftClient client, int width, int height, CallbackInfo ci) { + WidgetManager.onScreenResized(width, height, this.width, this.height); + } + + @Inject(at = @At("HEAD"), method = "close") + private void onClose(CallbackInfo ci) { + for (WidgetRenderer widgetRenderer : DynamicHUD.getWidgetRenderers()) { + widgetRenderer.onCloseScreen(); + } + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/mixins/TitleScreenMixin.java b/src/main/java/com/tanishisherewith/dynamichud/mixins/TitleScreenMixin.java deleted file mode 100644 index 6abfe5e..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/mixins/TitleScreenMixin.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.tanishisherewith.dynamichud.mixins; - -import com.tanishisherewith.dynamichud.DynamicHUD; -import com.tanishisherewith.dynamichud.handlers.DefaultDragHandler; -import com.tanishisherewith.dynamichud.handlers.DefaultMouseHandler; -import com.tanishisherewith.dynamichud.handlers.DragHandler; -import com.tanishisherewith.dynamichud.handlers.MouseHandler; -import com.tanishisherewith.dynamichud.util.DynamicUtil; -import com.tanishisherewith.dynamichud.widget.Widget; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(TitleScreen.class) -public abstract class TitleScreenMixin extends Screen { - protected MouseHandler mouseHandler = new DefaultMouseHandler(null, null, null); - protected DragHandler dragHandler = new DefaultDragHandler(); - protected Widget selectedWidget; - protected int dragStartX = 0, dragStartY = 0; // The starting position of a drag operation - protected int gridSize = 1; // The size of each grid cell in pixels - DynamicUtil dynamicUtil = DynamicHUD.getDynamicUtil(); - - protected TitleScreenMixin(Text title) { - super(title); - } - - public void setGridSize(int gridSize) { - this.gridSize = gridSize; - } - - @Inject(at = @At("TAIL"), method = "render") - private void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { - // Draw custom text on the title screen - if (dynamicUtil != null && (dynamicUtil.MainMenuWidgetAdded || dynamicUtil.WidgetLoaded)) { - dynamicUtil.render(context, delta); - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (mouseHandler.mouseClicked(mouseX, mouseY, button)) { - return true; - } - - for (Widget widget : dynamicUtil.getWidgetManager().getMainMenuWidgets()) { - if (widget.getWidgetBox().contains(widget, mouseX, mouseY,widget.getScale())) { - if (button == 1) { // Right-click - handleRightClickOnWidget(widget); - } else if (button == 0) { - widget.enabled = !widget.enabled; - } - if (dragHandler.startDragging(widget, mouseX, mouseY) && button == 0 && widget.isDraggable) { - selectedWidget = widget; - return true; - } - } - } - return super.mouseClicked(mouseX, mouseY, button); - } - - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - if (mouseHandler.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) { - return true; - } - if (selectedWidget != null && selectedWidget.isDraggable) { - // Update the position of the widget while dragging - int newX = (int) (mouseX - dragStartX); - int newY = (int) (mouseY - dragStartY); - - // Snap the widget to the grid - newX = (newX / gridSize) * gridSize; - newY = (newY / gridSize) * gridSize; - - selectedWidget.setX(newX); - selectedWidget.setY(newY); - return true; - } - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - } - - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - if (mouseHandler.mouseReleased(mouseX, mouseY, button)) { - return true; - } - if (selectedWidget != null) { - selectedWidget = null; - return true; - } - return super.mouseReleased(mouseX, mouseY, button); - } - - protected void handleRightClickOnWidget(Widget widget) { - - } - -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java b/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java new file mode 100644 index 0000000..9c3bc60 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/screens/AbstractMoveableScreen.java @@ -0,0 +1,103 @@ +package com.tanishisherewith.dynamichud.screens; + +import com.tanishisherewith.dynamichud.widget.WidgetRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; + +public abstract class AbstractMoveableScreen extends Screen { + public final WidgetRenderer widgetRenderer; + public int snapSize = 100; + protected boolean shouldPause = false; // To pause if the screen is opened or not + + /** + * Constructs a AbstractMoveableScreen object. + */ + public AbstractMoveableScreen(Text title, WidgetRenderer renderer) { + super(title); + this.widgetRenderer = renderer; + } + + @Override + public void onDisplayed() { + super.onDisplayed(); + widgetRenderer.isInEditor = true; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + widgetRenderer.mouseDragged(mouseX, mouseY, button, snapSize); + return false; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + widgetRenderer.mouseClicked(mouseX, mouseY, button); + return false; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + widgetRenderer.mouseReleased(mouseX, mouseY, button); + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + widgetRenderer.keyPressed(keyCode); + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean keyReleased(int keyCode, int scanCode, int modifiers) { + widgetRenderer.keyReleased(keyCode); + return super.keyReleased(keyCode, scanCode, modifiers); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + widgetRenderer.mouseScrolled(mouseX, mouseY, verticalAmount, horizontalAmount); + return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + /** + * Renders this screen and its widgets on the screen. + * + * @param drawContext The matrix stack used for rendering + * @param mouseX The current x position of the mouse cursor + * @param mouseY The current y position of the mouse cursor + * @param delta The time elapsed since the last frame in seconds + */ + @Override + public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { + assert this.client != null; + if (this.client.world == null) { + this.renderBackgroundTexture(drawContext); + } + 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); + } + + @Override + public void close() { + widgetRenderer.isInEditor = false; + widgetRenderer.onCloseScreen(); + super.close(); + } + + public void setShouldPause(boolean shouldPause) { + this.shouldPause = shouldPause; + } + + @Override + public boolean shouldPause() { + return shouldPause; + } + + public void setSnapSize(int size) { + this.snapSize = size; + } +} + diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/CustomItemRenderer.java b/src/main/java/com/tanishisherewith/dynamichud/util/CustomItemRenderer.java deleted file mode 100644 index d5beee6..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/util/CustomItemRenderer.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tanishisherewith.dynamichud.util; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.item.ItemRenderer; -import net.minecraft.item.ItemStack; - -public class CustomItemRenderer { - private final ItemStack itemStack; // The text renderer instance - private final float scale; // The scaling factor of the text - - /** - * Constructs a CustomTextRenderer object. - * - * @param stack The stack to render - * @param scale The scaling factor of the text - */ - public CustomItemRenderer(ItemStack stack, float scale) { - // The Minecraft client instance - this.itemStack = stack; - this.scale = scale; - } - - /** - * Draws a text with shadow on the screen with the given parameters. - * - * @param context The drawContext to use for rendering - * @param x The x position of the text in pixels - * @param y The y position of the text in pixels - * @param color The color of the text in ARGB format - */ - public void draw(DrawContext context, int x, int y, int color) { - context.getMatrices().push(); // Pushes the current matrix onto the stack - context.getMatrices().scale(scale, scale, scale); // Scales the matrix by the scaling factor - context.drawItem(itemStack,(int) (x/scale), (int) (y/scale),color); - context.getMatrices().pop(); // Pops the current matrix from the stack and restores the previous one - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/CustomTextRenderer.java b/src/main/java/com/tanishisherewith/dynamichud/util/CustomTextRenderer.java deleted file mode 100644 index 0bb0621..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/util/CustomTextRenderer.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tanishisherewith.dynamichud.util; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; - -public class CustomTextRenderer { - private final TextRenderer textRenderer; // The text renderer instance - private final float scale; // The scaling factor of the text - - /** - * Constructs a CustomTextRenderer object. - * - * @param client The Minecraft client instance - * @param scale The scaling factor of the text - */ - public CustomTextRenderer(MinecraftClient client, float scale) { - // The Minecraft client instance - this.textRenderer = client.textRenderer; - this.scale = scale; - } - - /** - * Draws a text with shadow on the screen with the given parameters. - * - * @param context The drawContext to use for rendering - * @param text The text to draw - * @param x The x position of the text in pixels - * @param y The y position of the text in pixels - * @param color The color of the text in ARGB format - */ - public void draw(DrawContext context, String text, int x, int y, int color, boolean shadow) { - context.getMatrices().push(); // Pushes the current matrix onto the stack - context.getMatrices().scale(scale, scale, scale); // Scales the matrix by the scaling factor - context.drawText(textRenderer,text, (int) (x/scale), (int) (y/scale),color,shadow); - context.getMatrices().pop(); // Pops the current matrix from the stack and restores the previous one - } -} - diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/DynamicUtil.java b/src/main/java/com/tanishisherewith/dynamichud/util/DynamicUtil.java deleted file mode 100644 index 742792b..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/util/DynamicUtil.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.tanishisherewith.dynamichud.util; - - -import com.tanishisherewith.dynamichud.helpers.ColorHelper; -import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.huds.AbstractMoveableScreen; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.WidgetBox; -import com.tanishisherewith.dynamichud.widget.WidgetManager; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.client.option.KeyBinding; -import net.minecraft.client.render.BufferBuilder; -import net.minecraft.client.render.VertexConsumerProvider; - -import java.util.Set; - - -/** - * This class provides utility methods for working with the DynamicHUD mod. - */ -public class DynamicUtil extends DrawContext { - static MinecraftClient client = MinecraftClient.getInstance(); - private final WidgetManager widgetManager; // The WidgetManager instance used by this class - public boolean WidgetAdded = false; - public boolean MainMenuWidgetAdded = false; - public boolean WidgetLoaded = false; - - - /** - * Constructs a DynamicUtil object. - * - * @param client The Minecraft client instance - */ - public DynamicUtil(MinecraftClient client) { - super(client, VertexConsumerProvider.immediate(new BufferBuilder(3))); - this.widgetManager = new WidgetManager(); - } - - /** - * Opens the MoveScreen 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()) { - client.setScreen(screen); - } - } - - /** - * Renders widgets on screen. - * - * @param context - MatrixStack used for rendering. - * @param delta - Time elapsed since last frame in seconds. - */ - public void render(DrawContext context, float delta) { - Set mainMenuWidgets = widgetManager.getMainMenuWidgets(); - Set widgets = widgetManager.getWidgets(); - - if (client.currentScreen instanceof TitleScreen) { - // Draw each Menu widget - for (Widget widget : mainMenuWidgets) { - widget.render(context); - widget.updatePosition(); - int backgroundColor = widget.isEnabled() ? ColorHelper.getColor(0, 0, 0, 128) : ColorHelper.getColor(255, 0, 0, 128); - WidgetBox box = widget.getWidgetBox(); - DrawHelper.fill(context, (int) (box.x1 - 2), (int) (box.y1 - 2), (int) (box.x2 + 2), (int) (box.y2 + 2), backgroundColor); - } - return; - } - - // Draw each widget - if (!client.options.debugEnabled || client.currentScreen instanceof AbstractMoveableScreen) { - for (Widget widget : widgets) { - if (client.currentScreen instanceof AbstractMoveableScreen) { - widget.render(context); - } else if (widget.isEnabled()) { - widget.render(context); - } - // Draw a red box around the widget if the HUD is disabled - if (client.currentScreen instanceof AbstractMoveableScreen) { - int backgroundColor = widget.isEnabled() ? ColorHelper.getColor(0, 0, 0, 128) : ColorHelper.getColor(255, 0, 0, 128); - WidgetBox box = widget.getWidgetBox(); - DrawHelper.fill(context, (int) (box.x1 - 2), (int) (box.y1 - 2), (int) (box.x2 + 2), (int) (box.y2 + 2), backgroundColor); - } - widget.updatePosition(); - } - } - } - - - /** - * Returns WidgetManager instance used by this class. - * - * @return WidgetManager instance used by this class. - */ - public WidgetManager getWidgetManager() { - return widgetManager; - } - -} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/ContextMenu.java b/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/ContextMenu.java deleted file mode 100644 index e76e540..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/ContextMenu.java +++ /dev/null @@ -1,442 +0,0 @@ -package com.tanishisherewith.dynamichud.util.contextmenu; - -import com.tanishisherewith.dynamichud.helpers.ColorHelper; -import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.widget.Widget; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; - -import java.awt.*; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; - - -public class ContextMenu { - private static int optionY; - private final MinecraftClient client; // The Minecraft client instance - private final List options = new ArrayList<>(); // The list of options in the context menu - private final Widget selectedWidget; // The widget that this context menu is associated with - private final Screen parentScreen; - private int width = 0; // The width of the context menu - private int x; // The x position of the context menu - private int y; // The y position of the context menu - private int backgroundColor = 0x90C0C0C0;// Semi-transparent light grey color - private int padding = 5; // The amount of padding around the rectangle - private int HeightFromWidget = 5; // The amount of padding around the rectangle - private float scale = 0.0f; - private int height = 0; - private String dataInputValue = ""; - private String doubleInputValue = ""; - - - /** - * Constructs a ContextMenu object. - * - * @param client The Minecraft client instance - * @param x The x position of the context menu - * @param y The y position of the context menu - * @param selectedWidget The widget that this context menu is associated with - */ - public ContextMenu(MinecraftClient client, int x, int y, Widget selectedWidget, Screen parentScreen) { - this.client = client; - this.selectedWidget = selectedWidget; - this.parentScreen = parentScreen; - this.x = x; - this.y = Math.round(y + selectedWidget.getWidgetBox().getHeight()); - } - - public static int getOptionY() { - return optionY; - } - - /** - * Sets the options to enable or disable based on values - * - * @param label The label of the option - * @param option Context Menu options - */ - public void setOptions(String label, ContextMenuOption option) { - if (selectedWidget instanceof ContextMenuOptionsProvider optionsProvider) { - option.enabled = optionsProvider.isOptionEnabled(label); - } - } - - /** - * Adds an option to the context menu. - * - * @param label The label of the option - * @param action The action to perform when the option is clicked - */ - public void addOption(String label, Runnable action) { - ContextMenuOption option = new ContextMenuOption(label, action); - if (selectedWidget != null) { - setOptions(label, option); - } - options.add(option); - } - - public void addDataTextOption(String label, Consumer action, int WidgetX, int WidgetY) { - int OptionY = WidgetY + HeightFromWidget + 2; - WidgetX += client.textRenderer.getWidth(label + dataInputValue); - OptionY += options.size() * (client.textRenderer.fontHeight + 2); - DataInputOption option = new DataInputOption(label + dataInputValue, text -> { - action.accept(text); - dataInputValue = text; - }, WidgetX, OptionY); - if (selectedWidget != null) { - setOptions(label, option); - } - options.add(option); - } - - public void addDoubleTextOption(String label, Consumer action, int WidgetX, int WidgetY) { - int OptionY = WidgetY + HeightFromWidget + 2; - WidgetX += client.textRenderer.getWidth(label + dataInputValue); - OptionY += options.size() * (client.textRenderer.fontHeight + 2); - DoubleInputOption option = new DoubleInputOption(label + doubleInputValue, text -> { - action.accept(text); - doubleInputValue = String.valueOf(text); - }, WidgetX, OptionY); - if (selectedWidget != null) { - setOptions(label, option); - } - options.add(option); - } - - public void setBackgroundColor(int backgroundColor) { - this.backgroundColor = backgroundColor; - } - - /** - * Returns whether the given point is within the bounds of this context menu. - * - * @param x - X position of the point. - * @param y - Y position of the point. - * @return true if the point is within the bounds of this context menu, false otherwise. - */ - public boolean contains(double x, double y) { - return x >= this.x - 3 && x <= this.x + width + 13 && y >= this.y + HeightFromWidget - 3 && y <= this.y + height + HeightFromWidget + 3; - } - - public int getX() { - return x; - } - - public int getY() { - return y; - } - - public int getWidth() { - return width; - } - - public Screen getParentScreen() { - return parentScreen; - } - - public Widget getSelectedWidget() { - return selectedWidget; - } - - public float getScale() { - return scale; - } - - public int getHeight() { - return height; - } - - public int getOptionY(int optionIndex) { - int OptionY = y + HeightFromWidget + 2; - OptionY += optionIndex * (client.textRenderer.fontHeight + 2); - return OptionY; - } - - /** - * Adds an option to the context menu that cycles through the values of an enum. - *

- * Usage Example: - * Position currentPosition = Position.ABOVE; - *

- * ContextMenu contextMenu = new ContextMenu(client, x, y); - *

- * contextMenu.addEnumCycleOption("Position", Position.values(), () -> currentPosition, newPosition -> { - * currentPosition = newPosition; - * }); - * - * @param labelPrefix The label to display for this option in the context menu - * @param values An array of enum values that specifies the possible values that this option can cycle through - * @param getter A Supplier that returns the current value of the enum - * @param setter A Consumer that sets the new value of the enum - * @param The type of the enum - */ - public > void addEnumCycleOption(String labelPrefix, T[] values, Supplier getter, Consumer setter) { - ContextMenuOption option = new EnumCycleContextMenuOption<>(labelPrefix, values, getter, () -> { - // Get the current value of the enum - T currentValue = getter.get(); - - // Find the index of the current value in the values array - int index = -1; - for (int i = 0; i < values.length; i++) { - if (values[i] == currentValue) { - index = i; - break; - } - } - - // Increment the index and wrap around if necessary - index = (index + 1) % values.length; - - // Set the new value of the enum - setter.accept(values[index]); - }); - options.add(option); - } - - - public void tick() { - // Update the scale - float scaleSpeed = 0.1f; - scale += scaleSpeed; - if (scale > 1.0f) { - scale = 1.0f; - } - } - - /** - * Updates the position of this context menu to avoid getting out of the screen. - */ - public void updatePosition() { - // Check if the context menu is outside the bounds of the screen - int screenWidth = client.getWindow().getScaledWidth(); - int screenHeight = client.getWindow().getScaledHeight(); - if (x + width + 14 > screenWidth) { - x = screenWidth - width - 14; - } - if (y + HeightFromWidget - 2 < 0) { - y = HeightFromWidget + 2; - } - if (y + height + HeightFromWidget + 2 > screenHeight) { - y = screenHeight - height - HeightFromWidget - 2; - } - } - - /** - * Renders this context menu on screen. - * - * @param drawContext - MatrixStack used for rendering. - */ - public void render(DrawContext drawContext) { - tick(); - TextRenderer textRenderer = client.textRenderer; - calculateSize(textRenderer); - applyScale(drawContext); - int x1 = x - 1; - int y1 = y + HeightFromWidget - 2; - int x2 = x + width + 8; - int y2 = y + height + HeightFromWidget + 2; - // Draw the background - DrawHelper.drawCutRectangle(drawContext, x1, y1, x2, y2, 0, backgroundColor, 1); - optionY = y + HeightFromWidget + 2; - drawOptions(drawContext, textRenderer); - if (selectedWidget != null) - setPosition(selectedWidget.getX(), selectedWidget.getY() + textRenderer.fontHeight + 4); - drawContext.getMatrices().pop(); - updatePosition(); - } - - private void applyScale(DrawContext drawContext) { - // Apply the scale - drawContext.getMatrices().push(); - drawContext.getMatrices().translate(x + width / 2.0f + 5, y + height / 2.0f + HeightFromWidget, 300); - drawContext.getMatrices().scale(scale, scale, 1.0f); - drawContext.getMatrices().translate(-(x + width / 2.0f + 5), -(y + height / 2.0f + HeightFromWidget), 300); - } - - private void calculateSize(TextRenderer textRenderer) { - // Calculate the size of the context menu - width = 0; - height = 0; - for (ContextMenuOption option : options) { - width = Math.max(width, textRenderer.getWidth(option.label) + padding); - height += textRenderer.fontHeight + 2; - } - } - - private void drawOptions(DrawContext drawContext, TextRenderer textRenderer) { - int labelTextcolor; - for (ContextMenuOption option : options) { - if (option instanceof EnumCycleContextMenuOption enumOption) { - enumOption.updateLabel(); - labelTextcolor = ColorHelper.ColorToInt(Color.WHITE); - } else if (option instanceof DataInputOption) { - labelTextcolor = ColorHelper.ColorToInt(Color.YELLOW); - - // Draw a black box around the value text - String[] splitLabel = option.label.split(":"); - if (splitLabel.length > 1) { - String valueText = splitLabel[1].trim(); - int valueTextWidth = textRenderer.getWidth(valueText); - int labelWidth = textRenderer.getWidth(splitLabel[0].trim()); - int boxX = x + labelWidth + 9; - int boxY = optionY - 2; - int boxWidth = valueTextWidth + 2; - int boxHeight = textRenderer.fontHeight + 1; - DrawHelper.fill(drawContext, boxX, boxY, boxX + boxWidth, boxY + boxHeight, 0x7F000000); - - } - - } else if (option instanceof DoubleInputOption) { - labelTextcolor = ColorHelper.ColorToInt(Color.BLUE.brighter()); - // Draw a black box around the value text - String[] splitLabel = option.label.split(":"); - if (splitLabel.length > 1) { - String valueText = splitLabel[1].trim(); - int valueTextWidth = textRenderer.getWidth(valueText); - int labelWidth = textRenderer.getWidth(splitLabel[0].trim()); - int boxX = x + labelWidth + 9; - int boxY = optionY - 2; - int boxWidth = valueTextWidth + 2; - int boxHeight = textRenderer.fontHeight + 1; - DrawHelper.fill(drawContext, boxX, boxY, boxX + boxWidth, boxY + boxHeight, 0x7F000000); - } - } else { - labelTextcolor = option.enabled ? 0xFF00FF00 : 0xFFFF0000; - } - drawContext.drawText(textRenderer, option.label, x + 5, optionY, labelTextcolor, false); - optionY += textRenderer.fontHeight + 2; - } - } - - /** - * Sets position of this context menu. - * - * @param x - X position to set. - * @param y - Y position to set. - */ - public void setPosition(int x, int y) { - this.x = x; - this.y = y; - } - - public void setPadding(int padding) { - this.padding = padding; - } - - public void setHeightFromWidget(int HeightFromWidget) { - this.HeightFromWidget = HeightFromWidget; - } - - public List getOptions() { - return options; - } - - /** - * Handles mouse clicks on this context menu. - * - * @param mouseX - X position of mouse cursor. - * @param mouseY - Y position of mouse cursor. - * @param button - Mouse button that was clicked. - * @return true if mouse click was handled by this context menu. - */ - public boolean mouseClicked(double mouseX, double mouseY, int button) { - TextRenderer textRenderer = client.textRenderer; - int optionY = y + HeightFromWidget + 2; - for (ContextMenuOption option : options) { - if (mouseX >= x && mouseX <= x + textRenderer.getWidth(option.label) + 10 && mouseY >= optionY && mouseY <= optionY + textRenderer.fontHeight + 2) { - // Run the action of the selected option - option.action.run(); - option.enabled = !option.enabled; - return true; - } - optionY += textRenderer.fontHeight + 2; - } - return false; - } - - - private static class ContextMenuOption { - Runnable action; // The action to perform when the option is clicked - String label; // The label of the option - private boolean enabled = false; // Whether the option is enabled - - /** - * Constructs a ContextMenuOption object. - * - * @param label - Label of this option. - * @param action - Action to perform when this option is clicked. - */ - public ContextMenuOption(String label, Runnable action) { - this.label = label; - this.action = action; - } - - public String getLabel() { - return label; - } - - public void setLabel(String newLabel) { - this.label = newLabel; - } - - public void setAction(Runnable action) { - this.action = action; - } - } - - private static class EnumCycleContextMenuOption> extends ContextMenuOption { - private final String labelPrefix; - private final T[] values; - private final Supplier getter; - - public EnumCycleContextMenuOption(String labelPrefix, T[] values, Supplier getter, Runnable action) { - super(labelPrefix + getter.get(), action); - this.labelPrefix = labelPrefix; - this.values = values; - this.getter = getter; - } - - public void updateLabel() { - label = labelPrefix + getter.get(); - } - } - - public class DataInputOption extends ContextMenuOption { - private final Consumer labelSetter; - - public DataInputOption(String label, Consumer consumer, int x, int y) { - super(label, null); - setAction(() -> { - // Open a new screen to allow the player to input data - MinecraftClient.getInstance().setScreen(new DataInputScreen(consumer, x, y, parentScreen, this)); - }); - this.labelSetter = text -> setLabel(label + ": " + text); - } - - public Consumer getLabelSetter() { - return labelSetter; - } - } - - public class DoubleInputOption extends ContextMenuOption { - private final Consumer labelSetter; - - public DoubleInputOption(String label, Consumer consumer, int x, int y) { - super(label, null); - setAction(() -> { - // Open a new screen to allow the player to input data - MinecraftClient.getInstance().setScreen(new DoubleInputScreen(consumer, x, y, parentScreen, this)); - }); - this.labelSetter = text -> setLabel(label + ": " + text); - } - - public Consumer getLabelSetter() { - return labelSetter; - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/ContextMenuOptionsProvider.java b/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/ContextMenuOptionsProvider.java deleted file mode 100644 index 33ab859..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/ContextMenuOptionsProvider.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.tanishisherewith.dynamichud.util.contextmenu; - -public interface ContextMenuOptionsProvider { - boolean isOptionEnabled(String label); -} - diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/DataInputScreen.java b/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/DataInputScreen.java deleted file mode 100644 index 31c68c5..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/DataInputScreen.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.tanishisherewith.dynamichud.util.contextmenu; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; - -import java.util.function.Consumer; - -public class DataInputScreen extends Screen { - private final Consumer consumer; - private final Screen parentScreen; - private final ContextMenu.DataInputOption DataInputOption; - private TextWidgetButtonExt textField; - private int x, y; - - - public DataInputScreen(Consumer consumer, int x, int y, Screen parentScreen, ContextMenu.DataInputOption dataInputOption) { - super(Text.of("Data Input")); - this.consumer = consumer; - this.x = x; - this.y = y; - this.parentScreen = parentScreen; - this.DataInputOption = dataInputOption; - if (this.x > MinecraftClient.getInstance().getWindow().getScaledWidth()) - this.x = MinecraftClient.getInstance().getWindow().getScaledWidth() - 110; - if (this.y > MinecraftClient.getInstance().getWindow().getScaledWidth()) - this.y = MinecraftClient.getInstance().getWindow().getScaledHeight() - 14; - if (this.x < 0) this.x = MinecraftClient.getInstance().getWindow().getScaledWidth() + 110; - if (this.y < 0) this.y = MinecraftClient.getInstance().getWindow().getScaledHeight() + 14; - } - - @Override - protected void init() { - // Create a text field for the player to input data - this.textField = new TextWidgetButtonExt(this.textRenderer, x, y, 100, 14, Text.of("")); - this.addDrawableChild(this.textField); - } - - @Override - public boolean shouldPause() { - return false; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { - // Close the screen and pass the entered data to the consumer when the Enter key is pressed - String text = this.textField.getText(); - this.consumer.accept(text); - MinecraftClient.getInstance().setScreen(parentScreen); - DataInputOption.getLabelSetter().accept(text); - } - return super.keyPressed(keyCode, scanCode, modifiers); - } -} - diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/DoubleInputScreen.java b/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/DoubleInputScreen.java deleted file mode 100644 index cfd074b..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/DoubleInputScreen.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.tanishisherewith.dynamichud.util.contextmenu; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; - -import java.util.function.Consumer; - -public class DoubleInputScreen extends Screen { - private final Consumer consumer; - private final Screen parentScreen; - private final ContextMenu.DoubleInputOption doubleInputOption; - private TextWidgetButtonExt textField; - private int x, y; - - public DoubleInputScreen(Consumer consumer, int x, int y, Screen parentScreen, ContextMenu.DoubleInputOption doubleInputOption) { - super(Text.of("Double Input")); - this.consumer = consumer; - this.x = x; - this.y = y; - this.parentScreen = parentScreen; - this.doubleInputOption = doubleInputOption; - if (this.x > MinecraftClient.getInstance().getWindow().getScaledWidth()) - this.x = MinecraftClient.getInstance().getWindow().getScaledWidth() - 110; - if (this.y > MinecraftClient.getInstance().getWindow().getScaledWidth()) - this.y = MinecraftClient.getInstance().getWindow().getScaledHeight() - 14; - if (this.x < 0) this.x = MinecraftClient.getInstance().getWindow().getScaledWidth() + 110; - if (this.y < 0) this.y = MinecraftClient.getInstance().getWindow().getScaledHeight() + 14; - } - - @Override - protected void init() { - // Create a text field for the player to input data - this.textField = new TextWidgetButtonExt(this.textRenderer, x, y, 100, 14, Text.of("")); - this.addDrawableChild(this.textField); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { - // Close the screen and pass the entered data to the consumer when the Enter key is pressed - try { - double value = Double.parseDouble(this.textField.getText()); - this.consumer.accept(value); - MinecraftClient.getInstance().setScreen(parentScreen); - doubleInputOption.getLabelSetter().accept(value); - } catch (NumberFormatException e) { - // Handle invalid input - this.textField.setText(""); - } - } - return super.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public boolean shouldPause() { - return false; - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/TextWidgetButtonExt.java b/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/TextWidgetButtonExt.java deleted file mode 100644 index fa9b90c..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/util/contextmenu/TextWidgetButtonExt.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.tanishisherewith.dynamichud.util.contextmenu; - -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.text.Text; - -public class TextWidgetButtonExt extends TextFieldWidget { - public TextWidgetButtonExt(TextRenderer textRenderer, int x, int y, int width, int height, Text text) { - super(textRenderer, x, y, width, height, text); - } - - @Override - public void renderButton(DrawContext context, int mouseX, int mouseY, float delta) { - super.renderButton(context, mouseX, mouseY, delta); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/DynamicValueRegistry.java b/src/main/java/com/tanishisherewith/dynamichud/utils/DynamicValueRegistry.java new file mode 100644 index 0000000..f3c8390 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/DynamicValueRegistry.java @@ -0,0 +1,103 @@ +package com.tanishisherewith.dynamichud.utils; + +import java.util.ArrayList; +import java.util.HashMap; +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. + *

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

+ *     {@code
+ *     DynamicValueRegistry dvr = new DynamicValueRegistry("mod_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<>(); + + /** + * A map that holds the local registry of suppliers. + * + * @see #globalRegistry + */ + private final Map> localRegistry = new HashMap<>(); + + /** + * Constructor for the DynamicValueRegistry class. + * + * @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. + */ + public DynamicValueRegistry(String modId) { + super(modId); + instances.computeIfAbsent(modId, k -> new ArrayList<>()).add(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. + */ + public static void registerGlobal(String key, Supplier supplier) { + globalRegistry.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. + */ + public static Supplier getGlobal(String key) { + return globalRegistry.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. + */ + 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. + * + * @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. + */ + public Supplier get(String key) { + // First, try to get the supplier from the local registry + Supplier supplier = localRegistry.get(key); + + // If the supplier is not in the local registry, try the global registry + if (supplier == null) { + supplier = globalRegistry.get(key); + } + + return supplier; + } + + /** + * Sets the local registry to the given map. + * + * @param map The map to be set as the local registry. + */ + public void setLocalRegistry(Map> map) { + localRegistry.putAll(map); + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/System.java b/src/main/java/com/tanishisherewith/dynamichud/utils/System.java new file mode 100644 index 0000000..3cbc6a0 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/System.java @@ -0,0 +1,23 @@ +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/UID.java b/src/main/java/com/tanishisherewith/dynamichud/utils/UID.java new file mode 100644 index 0000000..6a1735f --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/UID.java @@ -0,0 +1,30 @@ +package com.tanishisherewith.dynamichud.utils; + +import java.util.Random; + +public class UID { + private static final String ALPHANUMERIC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"; + private static final int LENGTH = 6; + private static final Random RANDOM = new Random(); + public String uniqueID; + + public UID(String id) { + this.uniqueID = id; + } + + public static UID generate() { + StringBuilder sb = new StringBuilder(LENGTH); + for (int i = 0; i < LENGTH; i++) { + sb.append(ALPHANUMERIC.charAt(RANDOM.nextInt(ALPHANUMERIC.length()))); + } + return new UID(sb.toString()); + } + + public String getUniqueID() { + return uniqueID; + } + + public void setUniqueID(String uniqueID) { + this.uniqueID = uniqueID; + } +} \ 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 new file mode 100644 index 0000000..b7f2ccc --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java @@ -0,0 +1,29 @@ +package com.tanishisherewith.dynamichud.utils; + +import com.tanishisherewith.dynamichud.DynamicHUD; + +public class Util { + public static Quadrant getQuadrant(int x, int y) { + int screenWidth = DynamicHUD.MC.getWindow().getScaledWidth(); + int screenHeight = DynamicHUD.MC.getWindow().getScaledHeight(); + + if (x < screenWidth / 2) { + if (y < screenHeight / 2) { + return Quadrant.UPPER_LEFT; + } else { + return Quadrant.BOTTOM_LEFT; + } + } else { + if (y < screenHeight / 2) { + return Quadrant.UPPER_RIGHT; + } else { + return Quadrant.BOTTOM_RIGHT; + } + } + } + + public enum Quadrant { + UPPER_LEFT, UPPER_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT + } + +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java new file mode 100644 index 0000000..17d58e4 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java @@ -0,0 +1,149 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu; + +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import net.minecraft.client.gui.DrawContext; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class ContextMenu { + private final List> options = new ArrayList<>(); // The list of options in the context menu + public int x, y; + public int width = 0, finalWidth = 0; + public int height = 0; + public Color backgroundColor = new Color(107, 112, 126, 124); + private 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 float scale = 0.0f; + + public ContextMenu(int x, int y) { + this.x = x; + this.y = y + heightOffset; + } + + public void addOption(Option option) { + 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; + + update(); + DrawHelper.scaleAndPosition(drawContext.getMatrices(), x, y, scale); + + // 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()); + } + + 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); + + 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) { + scale = 1.0f; + } + } + + public void close() { + shouldDisplay = false; + scale = 0.0f; + } + + public void open() { + shouldDisplay = true; + update(); + } + + public void toggleDisplay() { + if (shouldDisplay) { + close(); + } else { + open(); + } + } + + public void mouseClicked(double mouseX, double mouseY, int button) { + if (!shouldDisplay) return; + for (Option option : options) { + option.mouseClicked(mouseX, mouseY, button); + } + } + + public void mouseReleased(double mouseX, double mouseY, int button) { + if (!shouldDisplay) return; + for (Option option : options) { + option.mouseReleased(mouseX, mouseY, button); + } + } + + public void mouseDragged(double mouseX, double mouseY, int button) { + if (!shouldDisplay) return; + for (Option option : options) { + option.mouseDragged(mouseX, mouseY, button); + } + } + + public void keyPressed(int key) { + if (!shouldDisplay) return; + for (Option option : options) { + option.keyPressed(key); + } + } + + public void keyReleased(int key) { + if (!shouldDisplay) return; + for (Option option : options) { + option.keyReleased(key); + } + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return width; + } + + public List> getOptions() { + return options; + } + + public int getHeight() { + return height; + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/Option.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/Option.java new file mode 100644 index 0000000..dfea08c --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/Option.java @@ -0,0 +1,86 @@ +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/options/BooleanOption.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/BooleanOption.java new file mode 100644 index 0000000..5b32b6b --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/BooleanOption.java @@ -0,0 +1,39 @@ +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 BooleanOption extends Option { + public String name = "Empty"; + + public BooleanOption(String name, Supplier getter, Consumer setter) { + super(getter, setter); + this.name = name; + } + + @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; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + super.mouseClicked(mouseX, mouseY, button); + if (isMouseOver(mouseX, mouseY)) { + value = !value; + set(value); + } + 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 new file mode 100644 index 0000000..b30f8a6 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ColorOption.java @@ -0,0 +1,78 @@ +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 net.minecraft.text.Text; + +import java.awt.*; +import java.util.function.Consumer; +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; + + public ColorOption(String name, ContextMenu parentMenu, Supplier getter, Consumer setter) { + super(getter, setter); + this.name = name; + this.parentMenu = parentMenu; + System.out.println(get()); + 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); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isMouseOver(mouseX, mouseY)) { + isVisible = !isVisible; + if (isVisible) { + colorPicker.setPos(x + parentMenu.width + 10, y - 10); + colorPicker.display(); + } else { + colorPicker.close(); + } + } + colorPicker.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); + 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); + } +} 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 new file mode 100644 index 0000000..90123ee --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/DoubleOption.java @@ -0,0 +1,110 @@ +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 net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import org.apache.commons.lang3.Validate; + +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; + 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; + this.value = get(); + this.minValue = minValue; + this.maxValue = maxValue; + this.width = 30; + this.height = 16; + this.step = step; + this.parentMenu = parentMenu; + Validate.isTrue(this.step > 0.0f, "Step cannot be less than or equal to 0 (zero)"); + } + + @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) { + 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()); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + super.mouseClicked(mouseX, mouseY, button); + if (isMouseOver(mouseX, mouseY)) { + step(mouseX); + isDragging = true; + } + return true; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + isDragging = false; + return super.mouseReleased(mouseX, mouseY, button); + } + + private void step(double mouseX) { + double newValue = minValue + (float) (mouseX - x) / width * (maxValue - minValue); + // Round the new value to the nearest step + newValue = Math.round(newValue / step) * step; + set(newValue); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button) { + if (isMouseOver(mouseX, mouseY) && isDragging) { + step(mouseX); + } + return super.mouseDragged(mouseX, mouseY, button); + } +} 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 new file mode 100644 index 0000000..1351d17 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/EnumOption.java @@ -0,0 +1,62 @@ +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; + this.values = values; + this.value = get(); + for (int i = 0; i < values.length; i++) { + if (values[i] == value) { + currentIndex = i; + 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); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + super.mouseClicked(mouseX, mouseY, button); + if (isMouseOver(mouseX, mouseY)) { + if (button == 0) { + currentIndex = (currentIndex + 1) % values.length; + if (currentIndex > values.length - 1) { + currentIndex = 0; + } + value = values[currentIndex]; + } else if (button == 1) { + currentIndex = (currentIndex - 1) % values.length; + if (currentIndex < 0) { + currentIndex = values.length - 1; + } + value = values[currentIndex]; + } + set(value); + } + return true; + } +} 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 new file mode 100644 index 0000000..a7aef94 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/ListOption.java @@ -0,0 +1,64 @@ +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; + this.values = values; + this.value = getter.get(); + for (int i = 0; i < values.size(); i++) { + if (values.get(i).toString().equals(value)) { + currentIndex = i; + break; + } + } + } + + @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); + + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + super.mouseClicked(mouseX, mouseY, button); + if (isMouseOver(mouseX, mouseY)) { + if (button == 0) { + currentIndex = (currentIndex + 1) % values.size(); + if (currentIndex > values.size() - 1) { + currentIndex = 0; + } + value = values.get(currentIndex); + } else if (button == 1) { + currentIndex = (currentIndex - 1) % values.size(); + if (currentIndex < 0) { + currentIndex = values.size() - 1; + } + value = values.get(currentIndex); + } + set(value); + } + return true; + } +} 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 new file mode 100644 index 0000000..a1b3497 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/RunnableOption.java @@ -0,0 +1,55 @@ +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 RunnableOption extends Option { + private final Runnable task; + public String name = "Empty"; + + /** + * Runnable option which runs a task when clicked on it. + * + * @param name The name to be displayed + * @param getter Get a default value to run the task by default + * @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 + this.task = task; + } + 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); + } + + @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) { + task.run(); + } + } + 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 new file mode 100644 index 0000000..c917ee8 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/SubMenuOption.java @@ -0,0 +1,80 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.options; + +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.Option; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * SubMenu option displays a sub menu beside a boolean-like button. + *

+ * The {@link #getter} gets a boolean value to display/close the subMenu by default. + *

+ * 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"; + + + public SubMenuOption(String name, @NotNull 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(); + } + + @Override + public void render(DrawContext drawContext, int x, int y,int mouseX,int mouseY) { + this.x = x; + this.y = y; + + 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; + + subMenu.render(drawContext, this.x + parentMenu.finalWidth, this.y, 0,mouseX, mouseY); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) { + subMenu.toggleDisplay(); + set(subMenu.shouldDisplay); + return true; + } + subMenu.mouseClicked(mouseX, mouseY, button); + return false; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + subMenu.mouseReleased(mouseX, mouseY, button); + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button) { + subMenu.mouseDragged(mouseX, mouseY, button); + return super.mouseDragged(mouseX, mouseY, button); + } + + public SubMenuOption getOption() { + return this; + } + + public ContextMenu getSubMenu() { + return subMenu; + } +} 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 new file mode 100644 index 0000000..c7a8794 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/AlphaSlider.java @@ -0,0 +1,87 @@ +package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; + +import com.tanishisherewith.dynamichud.helpers.ColorHelper; +import com.tanishisherewith.dynamichud.helpers.DrawHelper; +import net.minecraft.client.gui.DrawContext; + +import java.awt.*; + +public class AlphaSlider { + private final int width; + private final int height; + private int x; + private int y; + private boolean isDragging = false; + private Color color; + private int alphaHandleY = 0; + private float alpha; + + public AlphaSlider(int x, int y, int width, int height, Color color) { + this.width = width; + this.height = height; + this.color = color; + this.x = x; + this.y = y; + alpha = color.getAlpha() / 255f; + } + + public void render(DrawContext drawContext, int x, int y) { + this.x = x; + this.y = y; + + DrawHelper.drawOutlinedBox(drawContext, x - 2, y - 2, x + width + 2, y + height + 2, Color.WHITE.getRGB()); + DrawHelper.drawGradient(drawContext.getMatrices().peek().getPositionMatrix(), x, y, width, height, color.getRGB(), ColorHelper.changeAlpha(color, 0).getRGB(), DrawHelper.Direction.TOP_BOTTOM); + drawContext.fill(x - 2, y + alphaHandleY - 1, x + width + 2, y + alphaHandleY + 1, Color.WHITE.getRGB()); + } + + public Color getColor() { + return ColorHelper.changeAlpha(color, (int) (alpha * 255f)); + } + + public void setColor(Color color) { + this.color = color; + } + + public void onClick(double mouseX, double mouseY, int button) { + if (button == 0 && isMouseOver(mouseX, mouseY)) { + alphaHandleY = (int) mouseY - y; + alpha = 1.0f - (alphaHandleY / (float) height); + if (alpha < 0.0f) { + alpha = 0.0f; + } else if (alpha > 1.0f) { + alpha = 1.0f; + } + this.isDragging = true; + } + } + + public void setY(int y) { + this.y = y; + } + + public void setX(int x) { + this.x = x; + } + + public boolean isMouseOver(double mouseX, double mouseY) { + return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; + } + + public void onRelease(double mouseX, double mouseY, int button) { + if (button == 0) { + isDragging = false; + } + } + + public void onDrag(double mouseX, double mouseY, int button) { + if (isDragging && mouseY >= y && mouseY <= y + height) { + alphaHandleY = (int) mouseY - y; + alpha = 1.0f - (alphaHandleY / (float) height); + if (alpha < 0.0f) { + alpha = 0.0f; + } else if (alpha > 1.0f) { + alpha = 1.0f; + } + } + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/ColorGradientPicker.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradientPicker.java similarity index 58% rename from src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/ColorGradientPicker.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradientPicker.java index 6086188..66c676d 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/ColorGradientPicker.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorGradientPicker.java @@ -1,6 +1,5 @@ -package com.tanishisherewith.dynamichud.util.colorpicker; +package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; -import com.tanishisherewith.dynamichud.widget.Widget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gl.Framebuffer; import net.minecraft.client.gui.DrawContext; @@ -12,46 +11,65 @@ import java.util.function.Consumer; public class ColorGradientPicker { - private final MinecraftClient client; // The Minecraft client instance - private final Consumer onColorSelected; // The callback to call when a color is selected + 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; - - - public ColorGradientPicker(MinecraftClient client, int x, int y, int initialColor, Consumer onColorSelected, int BoxSize, int Colors, Widget selectedWidget) { - this.client = client; + 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 >> 16) & 0xFF, (initialColor >> 8) & 0xFF, initialColor & 0xFF, hsv); + Color.RGBtoHSB(initialColor.getRed(), initialColor.getGreen(), initialColor.getBlue(), hsv); - // The initial color has an alpha component - hsv[0] = 0.0f; // Set hue to default value - hsv[1] = 1.0f; // Set saturation to default value - hsv[2] = 1.0f; // Set value to default value - this.gradientSlider = new GradientSlider(x, y, Colors, 10, selectedWidget); + this.boxSize = boxSize; this.gradientSlider.setHue(hsv[0]); - - this.gradientBox = new GradientBox(x, y + 20, BoxSize, selectedWidget); this.gradientBox.setHue(hsv[0]); this.gradientBox.setSaturation(hsv[1]); this.gradientBox.setValue(hsv[2]); - this.colorPickerButton = new ColorPickerButton(x + BoxSize + 8, y + 20, 35, 20); + this.colorPickerButton = new ColorPickerButton(x + boxSize + 8, y + 20, 30, 18); } - public void tick() { - gradientSlider.tick(); - gradientBox.tick(); + public void setPos(int x, int y) { + this.x = x; + this.y = y; } - public void render(DrawContext drawContext) { - tick(); - gradientSlider.render(drawContext); - gradientBox.render(drawContext); - colorPickerButton.render(drawContext); + 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()) { - // Draw the cursor + // 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(); @@ -59,18 +77,25 @@ public void render(DrawContext drawContext) { 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 = GlAllocationUtils.allocateByteBuffer(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)) { @@ -97,20 +122,28 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { colorPickerButton.setPicking(false); } - onColorSelected.accept(gradientBox.getColor()); + 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); - onColorSelected.accept(gradientBox.getColor()); + alphaSlider.setColor(new Color(gradientBox.getColor())); + alphaSlider.onDrag(mouseX, mouseY, button); + onColorSelected.accept(alphaSlider.getColor()); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/ColorPickerButton.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorPickerButton.java similarity index 72% rename from src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/ColorPickerButton.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorPickerButton.java index b47c625..fa2b8f1 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/ColorPickerButton.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/ColorPickerButton.java @@ -1,13 +1,13 @@ -package com.tanishisherewith.dynamichud.util.colorpicker; +package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; public class ColorPickerButton { - private final int x; - private final int y; private final int width; private final int height; + private int x; + private int y; private boolean isPicking = false; public ColorPickerButton(int x, int y, int width, int height) { @@ -17,15 +17,21 @@ public ColorPickerButton(int x, int y, int width, int height) { this.height = height; } - public void render(DrawContext drawContext) { + public void render(DrawContext drawContext, int x, int y) { + this.x = x; + this.y = y; drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0,0,404); + drawContext.getMatrices().translate(0, 0, 404); // Draw the button - drawContext.fill(x, y, x + width, y + height, 0xFFAAAAAA); + drawContext.fill(x + 2, y + 2, x + width - 2, y + height - 2, 0xFFAAAAAA); drawContext.drawCenteredTextWithShadow(MinecraftClient.getInstance().textRenderer, "Pick", x + width / 2, y + (height - 8) / 2, 0xFFFFFFFF); drawContext.getMatrices().pop(); } + public int getHeight() { + return height; + } + 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/util/colorpicker/GradientBox.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientBox.java similarity index 63% rename from src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/GradientBox.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientBox.java index 3df903f..49d7699 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/GradientBox.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientBox.java @@ -1,67 +1,45 @@ -package com.tanishisherewith.dynamichud.util.colorpicker; +package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.widget.Widget; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import java.awt.*; public class GradientBox { private final int size; - private final float alphaSpeed = 0.05f; - private final Widget selectedWidget; private int x; private int y; private float hue = 0.0f; private float saturation = 1.0f; private float value = 1.0f; private boolean isDragging = false; - private float alpha = 0.0f; - public GradientBox(int x, int y, int size, Widget selectedWidget) { + public GradientBox(int x, int y, int size) { this.x = x; this.y = y; this.size = size; - this.selectedWidget = selectedWidget; } - public void tick() { - // Update the alpha - alpha += alphaSpeed; - if (alpha > 1.0f) { - alpha = 1.0f; - } - } - - public void render(DrawContext drawContext) { + public void render(DrawContext drawContext, int x, int y) { + setPosition(x, y); drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0,0,401); + drawContext.getMatrices().translate(0, 0, 406); DrawHelper.drawOutlinedBox(drawContext, x - 2, y - 2, x + size + 2, y + size + 2, -1); // Draw the gradient - for (int i = 0; i < size; i++) { - for (int j = 0; j < size; j++) { - float saturation = (float) i / size; - float value = 1.0f - (float) j / size; - int color = Color.HSBtoRGB(hue, saturation, value); - color = (color & 0x00FFFFFF) | ((int) (alpha * 255) << 24); - drawContext.fill(x + i, y + j, x + i + 1, y + j + 1, color); - } - } + 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 handleX = x + saturation * size - handleSize / 2.0f; - float handleY = y + (1.0f - value) * size - handleSize / 2.0f; + float handleX = x + 2 + saturation * size - handleSize / 2.0f; + float handleY = y + 2 + (1.0f - value) * size - handleSize / 2.0f; - DrawHelper.fillRoundedRect(drawContext, (int) handleX, (int) handleY, (int) (handleX + handleSize), (int) (handleY + handleSize), -1); - if (this.selectedWidget != null) - setPosition(selectedWidget.getX() + 30, selectedWidget.getY() + MinecraftClient.getInstance().textRenderer.fontHeight + 4); + DrawHelper.drawFilledCircle(drawContext.getMatrices().peek().getPositionMatrix(), handleX, handleY, 1, -1); drawContext.getMatrices().pop(); } + /** * Sets position. * diff --git a/src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/GradientSlider.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientSlider.java similarity index 61% rename from src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/GradientSlider.java rename to src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientSlider.java index 1e53c6a..ef630a6 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/util/colorpicker/GradientSlider.java +++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/options/coloroption/GradientSlider.java @@ -1,8 +1,6 @@ -package com.tanishisherewith.dynamichud.util.colorpicker; +package com.tanishisherewith.dynamichud.utils.contextmenu.options.coloroption; import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.widget.Widget; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import java.awt.*; @@ -10,37 +8,18 @@ public class GradientSlider { private final int width; private final int height; - private final float progressSpeed = 0.1f; - private final float alphaSpeed = 0.05f; - private final Widget selectedWidget; private int x; private int y; private float hue = 0.0f; - private float progress = 0.0f; private boolean isDragging = false; - private float alpha = 0.0f; - public GradientSlider(int x, int y, int width, int height, Widget selectedWidget) { + public GradientSlider(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; - this.selectedWidget = selectedWidget; } - public void tick() { - // Update the progress - progress += progressSpeed; - if (progress > 1.0f) { - progress = 1.0f; - } - - // Update the alpha - alpha += alphaSpeed; - if (alpha > 1.0f) { - alpha = 1.0f; - } - } /** * Sets position. @@ -53,33 +32,36 @@ public void setPosition(int x, int y) { this.y = y; } - public void render(DrawContext drawContext) { + public void render(DrawContext drawContext, int x, int y) { + setPosition(x, y); drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0,0,401); + drawContext.getMatrices().translate(0, 0, 401); DrawHelper.drawOutlinedBox(drawContext, x - 2, y - 2, x + width + 2, y + height + 2, -1); // Draw the gradient + for (int i = 0; i < width; i++) { float hue = (float) i / width; int color = Color.HSBtoRGB(hue, 1.0f, 1.0f); - color = (color & 0x00FFFFFF) | ((int) (alpha * 255) << 24); + color = (color & 0x00FFFFFF) | (255 << 24); drawContext.fill(x + i, y, x + i + 1, y + height, color); } + // Draw the handle - if (progress >= 1.0f) { - float handleWidth = 3; - float handleHeight = height + 4; - float handleX = x + hue * width - handleWidth / 2.0f; - float handleY = y - (handleHeight - height) / 2.0f; + float handleWidth = 3; + float handleHeight = height + 4; + float handleX = x + hue * width - handleWidth / 2.0f; + float handleY = y - (handleHeight - height) / 2.0f; - DrawHelper.fillRoundedRect(drawContext, (int) handleX, (int) handleY, (int) (handleX + handleWidth), (int) (handleY + handleHeight), -1); - } - if (this.selectedWidget != null) - setPosition(selectedWidget.getX() + 30, selectedWidget.getY() + MinecraftClient.getInstance().textRenderer.fontHeight + 4); + DrawHelper.drawRectangle(drawContext.getMatrices().peek().getPositionMatrix(), handleX, handleY, handleWidth, handleHeight, -1); drawContext.getMatrices().pop(); } + public int getHeight() { + return height; + } + public void onClick(double mouseX, double mouseY, int button) { if (button == 0) { float handleWidth = 3; diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java b/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java index 6633a4e..6b36576 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/Widget.java @@ -1,59 +1,118 @@ package com.tanishisherewith.dynamichud.widget; -import com.tanishisherewith.dynamichud.interfaces.TextGenerator; +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 net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.glfw.GLFW; -import java.awt.*; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.*; +import java.util.Set; -/** - * This class represents a widget that can be displayed on the screen. - */ public abstract class Widget { - protected static Map textGenerators = new HashMap<>(); - protected final MinecraftClient client; // The Minecraft client instance - public boolean enabled = true; // Whether the widget is enabled + public static WidgetData DATA; + /** + * This is the UID of the widget used to identify during loading and saving. + *

+ * It's different from modID because this is unique to each 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 float xPercent; // The x position of the widget as a percentage of the screen width - protected float yPercent; // The y position of the widget as a percentage of the screen height - protected String label; - protected static float scale = 1f; // The scaling factor of the widget + //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; + /** + * An identifier for widgets to group them under one ID. + *

+ * Doesn't necessarily have to be the mod ID of mod, but it's preferred to use mod ID if you are only grouping widgets under one ID. + * Can be any string if wanted. + * + * @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; /** - * Constructs a Widget object. + * Scale of the current widget. * - * @param client The Minecraft client instance + * @see GlobalConfig#getScale() */ - public Widget(MinecraftClient client, String label) { - this.client = client; - this.label = label; + protected float scale = 1.0f; + //Dimensions of the widget + protected WidgetBox widgetBox; + int startX, startY; + + public Widget(WidgetData DATA, String modId) { + Widget.DATA = DATA; + widgetBox = new WidgetBox(0, 0, 0, 0); + this.modId = modId; + init(); } - public static void addTextGenerator(String label, TextGenerator textGenerator) { - textGenerators.put(label, textGenerator); + /** + * This method is called at the end of the {@link Widget#Widget(WidgetData, String)} constructor. + */ + public void init() { + } - public abstract void setTextGeneratorFromLabel(); + /** + * Returns the x position of the widget. + * + * @return The x position of the widget in pixels + */ + public int getX() { + return x; + } /** - * Gets the box around the widget for some purpose + * Returns the y position of the widget. + * + * @return The y position of the widget in pixels */ - public abstract WidgetBox getWidgetBox(); + public int getY() { + return y; + } + + public float getWidth() { + return widgetBox.getWidth(); + } + + public float getHeight() { + return widgetBox.getHeight(); + } + + public void setPosition(int x, int y) { + this.x = x; + this.y = y; + } public void setDraggable(boolean draggable) { isDraggable = draggable; } - public boolean isOverlapping(Set other) { - for (Widget widget : other) { - if ((this.getX() < widget.getX() + widget.getWidgetBox().getWidth() && this.getX() + this.getWidgetBox().getWidth() > widget.getX() && - this.getY() < widget.getY() + widget.getWidgetBox().getHeight() && this.getY() + this.getWidgetBox().getHeight() > widget.getY())) { + 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; } } @@ -68,104 +127,156 @@ public boolean isOverlapping(Widget other) { /** * Renders the widget on the screen. */ - public abstract void render(DrawContext drawContext); + public final void render(DrawContext drawContext, int mouseX, int mouseY) { + if (!shouldDisplay()) return; + + if (shouldScale) { + DrawHelper.scaleAndPosition(drawContext.getMatrices(), getX(), getY(), GlobalConfig.get().getScale()); + } + renderWidget(drawContext, mouseX, mouseY); + + if (shouldScale) { + DrawHelper.stopScaling(drawContext.getMatrices()); + } - public void updatePosition() { - int screenWidth = client.getWindow().getScaledWidth(); - int screenHeight = client.getWindow().getScaledHeight(); } /** - * Returns whether the widget is enabled. - * - * @return True if the widget is enabled, false otherwise + * Renders the widget on the editor screen. */ - public boolean isEnabled() { - return enabled; + public final void renderInEditor(DrawContext drawContext, int mouseX, int mouseY) { + displayBg(drawContext); + + if (shouldScale) { + DrawHelper.scaleAndPosition(drawContext.getMatrices(), getX(), getY(), GlobalConfig.get().getScale()); + } + renderWidgetInEditor(drawContext, mouseX, mouseY); + + if (shouldScale) { + DrawHelper.stopScaling(drawContext.getMatrices()); + } } + /** - * Returns the x position of the widget. + * Renders the widget on the screen + *

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

* - * @return The x position of the widget in pixels + * @param context + * @param mouseX X position of mouse. + * @param mouseY Y position of mouse */ - public int getX() { - return (int) (client.getWindow().getScaledWidth() * xPercent); - } + public abstract void renderWidget(DrawContext context, int mouseX, int mouseY); + + /** - * Returns the scaling factor of the widget. + * 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. * - * @return The scaling factor of the widget + * @param context */ - public static float getScale() { - return scale; + private void renderWidgetInEditor(DrawContext context, int mouseX, int mouseY) { + //displayBg(context); + + renderWidget(context, mouseX, mouseY); } - /** - * Sets the scaling factor of the widget. - * - * @param scale The new scaling factor of the widget - */ - public static void setScale(float scale) { - Widget.scale = scale; + /* Input related methods. Override with super call to add your own input-based code like contextMenu */ + + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (widgetBox.isMouseOver(mouseX, mouseY) && button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + toggle(); + if(isDraggable) { + startX = (int) (mouseX - x); + startY = (int) (mouseY - y); + dragging = true; + } + return true; + } + return false; } - /** - * Sets the x position of the widget. - * - * @param x The new x position of the widget in pixels - */ - public void setX(float x) { - int screenWidth = client.getWindow().getScaledWidth(); - if (x < 0) { - x = (0); - } else if (x + getWidgetBox().getWidth() > screenWidth) { - x = screenWidth - getWidgetBox().getWidth(); + + public boolean mouseDragged(double mouseX, double mouseY, int button, 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) { + // Calculate the size of each snap box + int snapBoxWidth = mc.getWindow().getScaledWidth() / snapSize; + int snapBoxHeight = mc.getWindow().getScaledHeight() / snapSize; + + // Calculate the index of the snap box that the new position would be in and + // snap the new position to the top-left corner of the snap box + newX = (newX / snapBoxWidth) * snapBoxWidth; + newY = (newY / snapBoxHeight) * snapBoxHeight; + } + + this.x = (int) MathHelper.clamp(newX, 0, mc.getWindow().getScaledWidth() - getWidth()); + this.y = (int) MathHelper.clamp(newY, 0, mc.getWindow().getScaledHeight() - getHeight()); + + this.xPercent = (float) this.x / mc.getWindow().getScaledWidth(); + this.yPercent = (float) this.y / mc.getWindow().getScaledHeight(); + + return true; } - this.xPercent = (float) x / screenWidth; + return false; } - /** - * Returns the y position of the widget. - * - * @return The y position of the widget in pixels - */ - public int getY() { - return (int) (client.getWindow().getScaledHeight() * yPercent); + public void mouseReleased(double mouseX, double mouseY, int button) { + dragging = false; } /** - * Sets the y position of the widget. + * MouseScrolled event * - * @param y The new y position of the widget in pixels + * @param vAmount vertical amount of scrolling + * @param hAmount horizontal amount of scrolling */ - public void setY(float y) { - int screenHeight = client.getWindow().getScaledHeight(); - if (y < 0) { - y = 0; - } else if (y + getWidgetBox().getHeight() > screenHeight) { - y = (screenHeight) - getWidgetBox().getHeight(); - } - this.yPercent = (float) y / screenHeight; + public void mouseScrolled(double mouseX, double mouseY, double vAmount, double hAmount) { + } + + + public boolean toggle() { + return this.display = !this.display; + } + + public void onClose() { + this.shiftDown = false; } /** - * Returns the fontheight + * Displays a faint grayish background if enabled or faint reddish background if disabled. + * Drawn with 2 pixel offset to all sides * - * @return fontHeight from TextRenderer + * @param context */ - public int getHeight() { - return client.textRenderer.fontHeight; + protected void displayBg(DrawContext context) { + int backgroundColor = this.shouldDisplay() ? ColorHelper.getColor(0, 0, 0, 128) : ColorHelper.getColor(255, 0, 0, 128); + WidgetBox box = this.getWidgetBox(); + DrawHelper.drawRectangle(context.getMatrices().peek().getPositionMatrix(), + box.x, + box.y, + box.getWidth(), + box.getHeight(), + backgroundColor); } + public void readFromTag(NbtCompound tag) { - xPercent = tag.getFloat("xPercent"); - yPercent = tag.getFloat("yPercent"); - enabled = tag.getBoolean("Enabled"); + modId = tag.getString("modId"); + uid = new UID(tag.getString("UID")); + x = tag.getInt("x"); + y = tag.getInt("y"); + display = tag.getBoolean("Display"); isDraggable = tag.getBoolean("isDraggable"); - label = tag.getString("label"); - scale = tag.getFloat("scale"); - - setTextGeneratorFromLabel(); + shouldScale = tag.getBoolean("shouldScale"); } /** @@ -174,45 +285,118 @@ public void readFromTag(NbtCompound tag) { * @param tag The tag to write to */ public void writeToTag(NbtCompound tag) { - tag.putString("class", getClass().getName()); + tag.putString("name", DATA.name()); + tag.putString("modId", modId); + tag.putString("UID", uid.getUniqueID()); tag.putBoolean("isDraggable", isDraggable); - tag.putFloat("xPercent", xPercent); - tag.putFloat("yPercent", yPercent); - tag.putBoolean("Enabled", enabled); - tag.putString("label", label); - tag.putFloat("scale", scale); - - - for (Field field : getClass().getDeclaredFields()) { - if (Modifier.isStatic(field.getModifiers())) continue; - - field.setAccessible(true); - - try { - Object value = field.get(this); - - if (value instanceof Boolean) { - tag.putBoolean(field.getName(), (Boolean) value); - } else if (value instanceof Byte) { - tag.putByte(field.getName(), (Byte) value); - } else if (value instanceof Short) { - tag.putShort(field.getName(), (Short) value); - } else if (value instanceof Integer) { - tag.putInt(field.getName(), (Integer) value); - } else if (value instanceof Long) { - tag.putLong(field.getName(), (Long) value); - } else if (value instanceof Float) { - tag.putFloat(field.getName(), (Float) value); - } else if (value instanceof Double) { - tag.putDouble(field.getName(), (Double) value); - } else if (value instanceof String) { - tag.putString(field.getName(), (String) value); - } else if (value instanceof Color colorvalue) { - tag.putInt(field.getName(), colorvalue.getRGB()); - }// Add more cases here for other data types - } catch (IllegalAccessException e) { - e.printStackTrace(); - } + tag.putBoolean("shouldScale", shouldScale); + tag.putInt("x", x); + tag.putInt("y", y); + tag.putBoolean("Display", display); + } + + public boolean shouldDisplay() { + return display; + } + + 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; + } + + public void setShouldScale(boolean shouldScale) { + this.shouldScale = shouldScale; + } + + public String getModId() { + return modId; + } + + @Override + public String toString() { + return "Widget{" + + "uniqueId='" + uid.getUniqueID() + '\'' + + ", x=" + x + + ", y=" + y + + ", display=" + display + + ", isDraggable=" + isDraggable + + ", shiftDown=" + shiftDown + + ", shouldScale=" + shouldScale + + '}'; + } + + public abstract static class WidgetBuilder { + protected int x; + protected int y; + protected boolean display = true; + protected boolean isDraggable = true; + protected boolean shouldScale = true; + protected String modID = "unknown"; + + + /** + * X Position of the widget relative to the screen. + * Should be between 0f - 1f + * + * @param x + * @return Builder + */ + public T setX(int x) { + this.x = x; + return self(); + } + + /** + * Y Position of the widget relative to the screen. + * Should be between 0f - 1f + * + * @param y + * @return + */ + public T setY(int y) { + this.y = y; + return self(); + } + + public T setDisplay(boolean display) { + this.display = display; + return self(); + } + + public T setDraggable(boolean isDraggable) { + this.isDraggable = isDraggable; + return self(); + } + + public T shouldScale(boolean shouldScale) { + this.shouldScale = shouldScale; + return self(); } + + public T setModID(String modID) { + this.modID = modID; + return self(); + } + + /** + * Method to be overridden in subclasses to return "this" correctly + */ + protected abstract T self(); + + /** + * Method to construct a Widget object + */ + public abstract S build(); } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java index 067f6aa..cf2670d 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetBox.java @@ -1,40 +1,19 @@ package com.tanishisherewith.dynamichud.widget; public class WidgetBox { - private final float width; - private final float height; - public float x1 = 0, x2 = 0, y1 = 0, y2 = 0; - - public WidgetBox(float x1, float y1, float x2, float y2, float scale) { - this.width = (float) ((x2 - x1) * scale); - this.height = (float) ((y2 - y1) * scale); - this.x1 = x1; - this.x2 = x1 + width; - this.y1 = y1; - this.y2 = y1 + height; - } - - public WidgetBox(float x1, float y1, double width, double height, float scale) { - this.width = (float) (width * scale); - this.height = (float) (height * scale); - this.x1 = x1; - this.x2 = x1 + this.width; - this.y1 = y1; - this.y2 = y1 + this.height; - } - - public boolean contains(Widget widget, double x, double y, float scale) { - if (x1 == 0 || x2 == 0 || y1 == 0 || y2 == 0) { - x1 = widget.getX() - width / 2; - y1 = widget.getY() - height / 2; - x2 = widget.getX() + width / 2; - y2 = widget.getY() + height / 2; - } - return x >= x1 && x <= x2 && y >= y1 && y <= y2; + public float x = 0, y = 0; + private float width; + private float height; + + public WidgetBox(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; } - public boolean intersects(float otherX1, float otherY1, float otherX2, float otherY2, float scale) { - return !(otherX1 > x2 || otherX2 < x1 || otherY1 > y2 || otherY2 < y1); + public boolean isMouseOver(double mouseX, double mouseY) { + return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; } public float getWidth() { @@ -44,4 +23,46 @@ public float getWidth() { public float getHeight() { return height; } + + public boolean intersects(WidgetBox other) { + // Check if this box is to the right of the other box + if (this.x > other.x + other.width) { + return false; + } + + // Check if this box is to the left of the other box + if (this.x + this.width < other.x) { + return false; + } + + // Check if this box is below the other box + if (this.y > other.y + other.height) { + return false; + } + + // Check if this box is above the other box + // If none of the above conditions are met, the boxes must intersect + return !(this.y + this.height < other.y); + } + + public void setSizeAndPosition(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) { + this.x = x; + this.y = y; + this.height = height * (shouldScale ? scale : 1.0f); + this.width = width * (shouldScale ? scale : 1.0f); + } + + public void setSize(double width, double height, boolean shouldScale, float scale) { + if (width >= 0) + this.width = (int) Math.ceil(width * (shouldScale ? scale : 1.0f)); + if (height >= 0) + this.height = (int) Math.ceil(height * (shouldScale ? scale : 1.0f)); + } } diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetData.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetData.java new file mode 100644 index 0000000..0305a78 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetData.java @@ -0,0 +1,20 @@ +package com.tanishisherewith.dynamichud.widget; + +import java.util.function.Supplier; + +public record WidgetData(String name, String description, Supplier widgetFactory) { + @Override + public String name() { + return name; + } + + @Override + public String description() { + return 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 c930768..65a414e 100644 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetManager.java +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetManager.java @@ -1,114 +1,144 @@ package com.tanishisherewith.dynamichud.widget; import com.tanishisherewith.dynamichud.DynamicHUD; -import com.tanishisherewith.dynamichud.interfaces.WidgetLoading; import net.fabricmc.fabric.api.util.NbtType; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtList; +import net.minecraft.util.math.MathHelper; -import java.io.*; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import static com.tanishisherewith.dynamichud.DynamicHUD.printInfo; +import static com.tanishisherewith.dynamichud.DynamicHUD.printWarn; /** - * This class manages a list of widgets that can be added, removed and retrieved. + * Manages a collection of widgets, providing methods to add, remove, save, and load widgets. */ public class WidgetManager { - private final Set widgets = new HashSet<>(); // The list of widgets - private final Set MainMenuWidgets = new HashSet<>(); // The list of MainMenu widgets - private WidgetLoading widgetLoading = new WidgetLoading() { - }; + /** + * The list of widgets managed by this manager. + */ + private static final List widgets = new ArrayList<>(); /** - * Adds a widget to the list. - * - * @param widget The widget to add + * A map from widget names to WidgetData objects, used for creating new widgets. */ - public void addWidget(Widget widget) { - widget.setTextGeneratorFromLabel(); - widgets.add(widget); - } + private static final Map> widgetDataMap = new TreeMap<>(); /** - * Adds a MainMenu widget to the list. + * Adds a WidgetData object to the map. * - * @param widget The widget to add + * @param data The WidgetData object to add. */ - public void addMainMenuWidget(Widget widget) { - widget.setTextGeneratorFromLabel(); - MainMenuWidgets.add(widget); - } - - public void setWidgetLoading(WidgetLoading widgetLoading) { - this.widgetLoading = widgetLoading; + public static void registerCustomWidget(WidgetData data) { + widgetDataMap.put(data.name(), data); } /** - * Removes a widget from the list. + * Adds multiple WidgetData objects to the map. * - * @param widget The widget to remove + * @param widgetDatas The WidgetData objects to add. */ - public void removeWidget(Widget widget) { - widgets.remove(widget); + public static void registerCustomWidgets(WidgetData... widgetDatas) { + for (WidgetData data : widgetDatas) { + widgetDataMap.put(data.name(), data); + } } /** - * Removes a MainMenu widget from the list. + * Adds a widget to the list of managed widgets. * - * @param widget The Main Menu widget to remove + * @param widget The widget to add. */ - public void removeMainMenuWidget(Widget widget) { - MainMenuWidgets.remove(widget); + public static void addWidget(Widget widget) { + widgets.add(widget); } - /** - * Returns list of all widgets. + * Adds multiple widgets to the list of managed widgets. * - * @return list of all widgets. + * @param widget The widgets to add. */ - public Set getWidgets() { - return widgets; + public static void addWidgets(Widget... widget) { + widgets.addAll(Arrays.stream(widget).toList()); } /** - * Returns Set of all MainMenu widgets. + * Removes a widget from the list of managed widgets. * - * @return Set of all MainMenu widgets. + * @param widget The widget to remove. */ - public Set getMainMenuWidgets() { - return MainMenuWidgets; + public static void removeWidget(Widget widget) { + widgets.remove(widget); } - public Set getOtherWidgets(Widget SelectedWidget) { - Set otherWidgets = new HashSet<>(); - for (Widget widget : getWidgets()) { - if (widget != SelectedWidget) { - otherWidgets.add(widget); + /** + * Attempts to restore the widgets back to their place on screen resize. + *

+ * It works by storing the position of widgets as relative to the screen size before the resize + * and then using that percentage to restore the widget to their appropriate place. + *

+ * Widgets will move around on smaller GUI scales. + * Larger the GUI scale, more accurate is the position. + *

+ *

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

+ *

+ * + * @param newWidth Screen width after resize + * @param newHeight Screen height after resize + * @param previousWidth Screen width before resize + * @param previousHeight Screen height before resize + */ + public static void onScreenResized(int newWidth, int newHeight, int previousWidth, int previousHeight) { + for (Widget widget : widgets) { + // To ensure that infinite coords is not returned + if (widget.xPercent <= 0.0f) { + widget.xPercent = (float) widget.getX() / previousWidth; + } + if (widget.yPercent <= 0.0f) { + 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()); + + // Update the widget's position + widget.setPosition((int) newX, (int) newY); + + // Update the stored percentages with the new screen size (after resize). + widget.xPercent = (float) widget.getX() / newWidth; + widget.yPercent = (float) widget.getY() / newHeight; } - return otherWidgets; } + /** * Saves the state of all widgets to the given file. * * @param file The file to save to */ - public void saveWidgets(File file) { + public static void saveWidgets(File file, List widgets) throws IOException { NbtCompound rootTag = new NbtCompound(); NbtList widgetList = new NbtList(); - NbtList MainMenuwidgetList = new NbtList(); printInfo("Saving widgets"); - if (widgets.size() < 1 && MainMenuWidgets.size() < 1) { - printInfo("Widgets are empty.. Saving interrupted to prevent empty file"); + if (widgets.isEmpty()) { + printWarn("Widgets are empty.. Saving interrupted to prevent empty file"); return; } @@ -118,81 +148,79 @@ public void saveWidgets(File file) { widget.writeToTag(widgetTag); // Check for duplicates if (widgetSet.add(widgetTag.toString())) { + printInfo("Saving Widget: " + widget); widgetList.add(widgetTag); } } - rootTag.put("Widgets", widgetList); + rootTag.put("widgets", widgetList); - Set MainMenuWidgetSet = new HashSet<>(); - for (Widget mmwidget : MainMenuWidgets) { - NbtCompound widgetTag = new NbtCompound(); - mmwidget.writeToTag(widgetTag); - // Check for duplicates - if (MainMenuWidgetSet.add(widgetTag.toString())) { - MainMenuwidgetList.add(widgetTag); - } + // Backup the old file + File backupFile = new File(file.getAbsolutePath() + ".backup"); + if (file.exists()) { + Files.copy(file.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } - rootTag.put("MainMenuWidgets", MainMenuwidgetList); - // Use a temporary file to write the data + // Write the data to a temporary file File tempFile = new File(file.getAbsolutePath() + ".tmp"); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(tempFile))) { - NbtIo.writeCompressed(rootTag, out); - // Check if the data has been written successfully - if (tempFile.length() > 0) { - // Check if the temporary file exists and can be renamed - Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); - } else { - throw new IOException("Failed to write data to temporary file OR Empty data passed"); - } + NbtIo.write(rootTag, out); } catch (IOException e) { - // Delete the temporary file if an error occurs - boolean temp = tempFile.delete(); - e.printStackTrace(); + DynamicHUD.logger.warn("Error while saving", e); + // If save operation failed, restore the backup + Files.move(backupFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + throw e; // rethrow the exception } - } + // If save operation was successful, replace the old file with the new one + Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + /** + * Loads the state of all widgets from the given file. + * + * @param file The file to load from + */ + public static void loadWidgets(File file) throws IOException { + widgets.clear(); - public Set loadWigdets(File file) { - Set widgets = new HashSet<>(); if (file.exists()) { - printInfo("Widgets File exists"); - try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { - NbtCompound rootTag = NbtIo.readCompressed(in); - NbtList widgetList = rootTag.getList("Widgets", NbtType.COMPOUND); - for (int i = 0; i < widgetList.size(); i++) { - NbtCompound widgetTag = widgetList.getCompound(i); - String className = widgetTag.getString("class"); - widgets.add(widgetLoading.loadWidgetsFromTag(className, widgetTag)); - printInfo("Wigdet " + i + ": " + widgets.stream().toList().get(i).toString()); - } - } catch (IOException e) { - e.printStackTrace(); + 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); } - } else - DynamicHUD.printWarn("Widgets File does not exist"); + } else { + printWarn("Widget File does not exist. Try saving one first"); + } + } + + /** + * Returns the list of managed widgets. + * + * @return The list of managed widgets. + */ + public static List getWidgets() { return widgets; } - public Set loadMainMenuWigdets(File file) { - Set MainMenuwidgets = new HashSet<>(); - if (file.exists()) { - try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { - NbtCompound rootTag = NbtIo.readCompressed(in); - NbtList MainMenuwidgetList = rootTag.getList("MainMenuWidgets", NbtType.COMPOUND); - for (int i = 0; i < MainMenuwidgetList.size(); i++) { - NbtCompound widgetTag = MainMenuwidgetList.getCompound(i); - String className = widgetTag.getString("class"); - MainMenuwidgets.add(widgetLoading.loadWidgetsFromTag(className, widgetTag)); - printInfo("MainMenu Wigdet " + i + ": " + MainMenuwidgets.stream().toList().get(i).toString()); - } - } catch (IOException e) { - e.printStackTrace(); - } - } else - DynamicHUD.printWarn("Widgets File does not exist"); - return MainMenuwidgets; + /** + * Returns the list of managed widgets with the same modID. + * + * @return The list of managed widgets with the same modID. + */ + public static List getWidgetsForMod(String modID) { + return getWidgets().stream() + .filter(widget -> modID.equalsIgnoreCase(widget.getModId())) + .toList(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java new file mode 100644 index 0000000..db0c9c5 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/widget/WidgetRenderer.java @@ -0,0 +1,158 @@ +package com.tanishisherewith.dynamichud.widget; + +import com.tanishisherewith.dynamichud.DynamicHUD; +import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen; +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; + +public class WidgetRenderer { + public final List> allowedScreens = new CopyOnWriteArrayList<>(); + public boolean isInEditor = false; + public Widget selectedWidget = null; + List widgets; + private boolean renderInGameHud = true; + + /** + * Add the list of widgets the widgetRenderer should render + *

+ * By default, it adds the {@link GameMenuScreen} to allow rendering of the widgets in the pause/main menu screen. + * + * @param widgets List of widgets to render + */ + public WidgetRenderer(List widgets) { + this.widgets = widgets; + addScreen(GameMenuScreen.class); + } + + public void addWidget(Widget widget) { + widgets.add(widget); + } + + public void addScreen(Class screen) { + allowedScreens.add(screen); + } + + public void shouldRenderInGameHud(boolean renderInGameHud) { + this.renderInGameHud = renderInGameHud; + } + + public void renderWidgets(DrawContext context, int mouseX, int mouseY) { + if (WidgetManager.getWidgets().isEmpty() || DynamicHUD.MC.getDebugHud().shouldShowDebugHud()) return; + + Screen currentScreen = DynamicHUD.MC.currentScreen; + + if (currentScreen == null && renderInGameHud) { + for (Widget widget : widgets) { + widget.isInEditor = false; + widget.render(context, 0, 0); + } + return; + } + if (currentScreen instanceof AbstractMoveableScreen) { + for (Widget widget : widgets) { + widget.isInEditor = true; + widget.renderInEditor(context, mouseX, mouseY); + } + return; + } + if (currentScreen != null && allowedScreens.contains(DynamicHUD.MC.currentScreen.getClass()) && !this.isInEditor) { + for (Widget widget : widgets) { + widget.isInEditor = false; + widget.render(context, 0, 0); + } + } + } + + public void mouseClicked(double mouseX, double mouseY, int button) { + 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.mouseClicked(mouseX, mouseY, button)) { + selectedWidget = widget; + return; + } + } + } + } + + public void mouseDragged(double mouseX, double mouseY, int button, int snapSize) { + 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; + } + } + } + } + + 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) { + widget.mouseScrolled(mouseX, mouseY, vAmount, hAmount); + } + } + } + + public void keyPressed(int keyCode) { + Screen currentScreen = DynamicHUD.MC.currentScreen; + if (currentScreen instanceof AbstractMoveableScreen && (keyCode == GLFW.GLFW_KEY_LEFT_SHIFT || keyCode == GLFW.GLFW_KEY_RIGHT_SHIFT)) { + for (Widget widget : widgets) { + widget.shiftDown = true; + } + } + } + + public void keyReleased(int keyCode) { + Screen currentScreen = DynamicHUD.MC.currentScreen; + if (currentScreen instanceof AbstractMoveableScreen && (keyCode == GLFW.GLFW_KEY_LEFT_SHIFT || keyCode == GLFW.GLFW_KEY_RIGHT_SHIFT)) { + for (Widget widget : widgets) { + widget.shiftDown = false; + } + } + } + + public void onCloseScreen() { + if (DynamicHUD.MC.currentScreen instanceof AbstractMoveableScreen) { + for (Widget widget : widgets) { + widget.onClose(); + } + } + } + + public List getWidgets() { + return widgets; + } + + public void mouseReleased(double mouseX, double mouseY, int button) { + Screen currentScreen = DynamicHUD.MC.currentScreen; + if (currentScreen == null) { + return; + } + if (currentScreen instanceof AbstractMoveableScreen) { + for (Widget widget : widgets) { + widget.mouseReleased(mouseX, mouseY, button); + } + } + } +} diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/armor/ArmorWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widget/armor/ArmorWidget.java deleted file mode 100644 index f433a5b..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/armor/ArmorWidget.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.tanishisherewith.dynamichud.widget.armor; - -import com.tanishisherewith.dynamichud.helpers.ColorHelper; -import com.tanishisherewith.dynamichud.helpers.TextureHelper; -import com.tanishisherewith.dynamichud.interfaces.TextGenerator; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.WidgetBox; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.item.ItemRenderer; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.nbt.NbtCompound; - -import java.awt.*; -import java.util.function.Supplier; - -/** - * This class represents a widget that displays the armor item in a specified equipment slot. - */ -public class ArmorWidget extends Widget { - public final TextureHelper.Position[] currentTextPosition = TextureHelper.Position.values(); - protected EquipmentSlot slot; // The equipment slot to display the armor item from - protected TextGenerator textGenerator; - protected Supplier color; - protected boolean TextBackground; - - /** - * Constructs an ArmorWidget object. - * - * @param client The Minecraft client instance - * @param slot The equipment slot to display the armor item from - * @param xPercent The x position of the widget as a percentage of the screen width - * @param yPercent The y position of the widget as a percentage of the screen height - */ - public ArmorWidget(MinecraftClient client, EquipmentSlot slot, float xPercent, float yPercent, boolean enabled, TextureHelper.Position currentTextPosition, TextGenerator textGenerator, Supplier color, boolean Textbackground, String label) { - super(client, label); - this.slot = slot; - this.xPercent = xPercent; - this.yPercent = yPercent; - this.enabled = enabled; - this.currentTextPosition[0] = currentTextPosition; - this.textGenerator = textGenerator; - this.color = color; - this.TextBackground = Textbackground; - } - - /** - * Renders the widget on the screen. - */ - @Override - public void render(DrawContext drawContext) { - ItemRenderer itemRenderer = client.getItemRenderer(); - ItemStack armorItem; - TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; - if (client.player == null) { - armorItem = Items.DIAMOND_CHESTPLATE.getDefaultStack(); - } else { - armorItem = client.player.getEquippedStack(slot); - } - TextureHelper.drawItemTextureWithTextAndScale(drawContext, scale, textRenderer, armorItem, getX(), getY(), getText(), ColorHelper.ColorToInt(getColor()), currentTextPosition[0], scale*0.5f, TextBackground); - } - - @Override - public void setTextGeneratorFromLabel() { - TextGenerator textGenerator = textGenerators.get(label); - if (textGenerator != null) { - setTextGenerator(textGenerator); - } - } - - @Override - public WidgetBox getWidgetBox() { - return new WidgetBox(this.getX(), this.getY(), this.getX() + this.getWidth(), this.getY() + this.getHeight(),scale); - } - - /** - * Returns the width of the widget. - * - * @return The width of the widget in pixels - */ - public int getWidth() { - return 16; // The width of an item texture is 16 pixels - } - - /** - * Returns the height of the widget. - * - * @return The height of the widget in pixels - */ - public int getHeight() { - return 16; // The height of an item texture is 16 pixels - } - - /** - * Returns the text displayed by this widget. - * - * @return The text displayed by this widget - */ - public String getText() { - return textGenerator.generateText(); - } - - public void setTextGenerator(TextGenerator textGenerator) { - this.textGenerator = textGenerator; - } - - public Color getColor() { - return color.get(); - } - - public void setColor(Supplier color) { - this.color = color; - } - - @Override - public void writeToTag(NbtCompound tag) { - super.writeToTag(tag); - tag.putString("slot", slot.getName()); - tag.putString("Position", String.valueOf(this.currentTextPosition[0])); - if (this.getText() != null) tag.putString("text", this.getText()); - tag.putInt("Color", this.getColor().getRGB()); - tag.putBoolean("TextBackground", this.TextBackground); - } - - @Override - public void readFromTag(NbtCompound tag) { - super.readFromTag(tag); - slot = EquipmentSlot.byName(tag.getString("slot")); - String Position = tag.getString("Position"); - color = () -> ColorHelper.getColorFromInt(tag.getInt("Color")); - if (TextureHelper.Position.getByUpperCaseName(Position) != null && !(tag.getString("Position") == null) && !tag.getString("Position").isEmpty()) - currentTextPosition[0] = TextureHelper.Position.getByUpperCaseName(Position); - else - currentTextPosition[0] = TextureHelper.Position.ABOVE; - TextBackground = tag.getBoolean("TextBackground"); - label = tag.getString("label"); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/item/ItemWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widget/item/ItemWidget.java deleted file mode 100644 index 6c5ac60..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/item/ItemWidget.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.tanishisherewith.dynamichud.widget.item; - -import com.tanishisherewith.dynamichud.helpers.ColorHelper; -import com.tanishisherewith.dynamichud.helpers.TextureHelper; -import com.tanishisherewith.dynamichud.interfaces.TextGenerator; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.WidgetBox; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.item.ItemRenderer; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; - -import java.awt.*; -import java.util.function.Supplier; - -public class ItemWidget extends Widget { - public final TextureHelper.Position[] currentTextPosition = TextureHelper.Position.values(); - protected Supplier color; - protected TextGenerator textGenerator; - protected Supplier itemStack; - protected boolean TextBackground; - - /** - * Constructs a Widget object. - * - * @param client The Minecraft client instance - */ - public ItemWidget(MinecraftClient client, Supplier itemStack, float xPercent, float yPercent, boolean enabled, TextureHelper.Position currentTextPosition, TextGenerator textGenerator, Supplier color, boolean Textbackground, String label) { - super(client, label); - this.xPercent = xPercent; - this.yPercent = yPercent; - this.enabled = enabled; - this.itemStack = itemStack; - this.currentTextPosition[0] = currentTextPosition; - this.textGenerator = textGenerator; - this.color = color; - this.TextBackground = Textbackground; - } - - @Override - public WidgetBox getWidgetBox() { - return new WidgetBox(this.getX(), this.getY(), this.getX() + this.getWidth(), this.getY() + this.getHeight(),scale); - } - - /** - * Returns the width of the widget. - * - * @return The width of the widget in pixels - */ - public int getWidth() { - return 16; // The width of an item texture is 16 pixels - } - - /** - * Returns the height of the widget. - * - * @return The height of the widget in pixels - */ - public int getHeight() { - return 16; // The height of an item texture is 16 pixels - } - - public ItemStack getItemStack() { - return itemStack.get(); - } - - public void setItemStack(Supplier itemStack) { - this.itemStack = itemStack; - } - - /** - * Returns the text displayed by this widget. - * - * @return The text displayed by this widget - */ - public String getText() { - return textGenerator.generateText(); - } - - public void setTextGenerator(TextGenerator textGenerator) { - this.textGenerator = textGenerator; - } - - public Supplier getColor() { - return color; - } - - public void setColor(Supplier color) { - this.color = color; - } - - @Override - public void setTextGeneratorFromLabel() { - TextGenerator textGenerator = textGenerators.get(label); - if (textGenerator != null) { - setTextGenerator(textGenerator); - } - } - - @Override - public void writeToTag(NbtCompound tag) { - super.writeToTag(tag); - tag.putString("Position", String.valueOf(this.currentTextPosition[0])); - tag.putInt("ItemID", Item.getRawId(getItemStack().getItem())); - tag.putInt("ItemCount", getItemStack().getMaxCount()); - tag.putString("text", getText()); - tag.putBoolean("TextBackground", this.TextBackground); - } - - @Override - public void readFromTag(NbtCompound tag) { - super.readFromTag(tag); - String Position = tag.getString("Position"); - - int itemID = tag.getInt("ItemID"); - int itemCount = tag.getInt("ItemCount"); - itemStack = () -> getItemStack(itemID, itemCount); - - if (TextureHelper.Position.getByUpperCaseName(Position) != null && !tag.getString("Position").isEmpty()) - currentTextPosition[0] = TextureHelper.Position.getByUpperCaseName(Position); - else - currentTextPosition[0] = TextureHelper.Position.ABOVE; - TextBackground = tag.getBoolean("TextBackground"); - label = tag.getString("label"); - } - - public ItemStack getItemStack(int itemID, int itemCount) { - Item item = Item.byRawId(itemID); - return new ItemStack(item, itemCount); - } - - @Override - public void render(DrawContext drawContext) { - ItemRenderer itemRenderer = client.getItemRenderer(); - TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; - TextureHelper.drawItemTextureWithTextAndScale(drawContext, scale, textRenderer, getItemStack(), getX(), getY(), getText(), ColorHelper.ColorToInt(color.get()), currentTextPosition[0], scale*0.5f, TextBackground); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/slider/ScaleSliderWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widget/slider/ScaleSliderWidget.java deleted file mode 100644 index 0e60528..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/slider/ScaleSliderWidget.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.tanishisherewith.dynamichud.widget.slider; - -import com.tanishisherewith.dynamichud.widget.Widget; -import net.minecraft.client.gui.widget.SliderWidget; -import net.minecraft.text.Text; -import java.text.DecimalFormat; - -public class ScaleSliderWidget extends SliderWidget { - private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##"); - private final float minValue; - private final float maxValue; - - public ScaleSliderWidget(int x, int y, int width, int height, Text message, double value, float minValue, float maxValue) { - super(x, y, width, height, message, value); - this.minValue = minValue; - this.maxValue = maxValue; - } - - public float getValue(){ - return (float) (minValue + (maxValue - minValue) * value); - } - - @Override - protected void updateMessage() { - String formattedValue = DECIMAL_FORMAT.format(getValue()); - setMessage(Text.of("Widgets Scale: " + formattedValue)); - } - - @Override - protected void applyValue() { - Widget.setScale(getValue()); - } - -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/slider/SliderWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widget/slider/SliderWidget.java deleted file mode 100644 index 84b70ad..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/slider/SliderWidget.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.tanishisherewith.dynamichud.widget.slider; - -import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.widget.Widget; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; - -import java.util.function.Consumer; - -/** - * This class represents a slider widget that allows the user to select a value within a specified range. - */ -public class SliderWidget { - private final MinecraftClient client; - private final int width; // The width of the widget - private final String label; // The label displayed above the slider - private final float minValue; // The minimum value of the slider - private final float maxValue; // The maximum value of the slider - private final int height; // The height of the widget - private int x; // The x position of the widget - private int y; // The y position of the widget - private float value; // The current value of the slider - private Widget selectedWidget = null; - private Consumer getValue; - private float progress = 0.0f; - private float progressSpeed = 0.1f; - private float textProgress = 0.0f; - private float textProgressSpeed = 0.05f; - private boolean MouseClicked = false; - - /** - * Constructs a SliderWidget object. - * - * @param client The Minecraft client instance - * @param x The x position of the widget - * @param y The y position of the widget - * @param width The width of the widget - * @param height The height of the widget - * @param label The label displayed above the slider - * @param value The initial value of the slider - * @param minValue The minimum value of the slider - * @param maxValue The maximum value of the slider - * @param selectedWidget The widget which was selected to display this slider - */ - public SliderWidget(MinecraftClient client, int x, int y, int width, int height, String label, float value, float minValue, float maxValue, Consumer getValue, Widget selectedWidget) { - this.client = client; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.label = label; - this.value = value; - this.minValue = minValue; - this.maxValue = maxValue; - this.getValue=getValue; - this.selectedWidget = selectedWidget; - } - - public void tick() { - // Update the progress - progress += progressSpeed; - if (progress > 1.0f) { - progress = 1.0f; - } - // Update the text progress - textProgress += textProgressSpeed; - if (textProgress > 1.0f) { - textProgress = 1.0f; - } - } - - /** - * Updates the position of this Slider to avoid getting out of the screen. - */ - public void updatePosition() { - // Check if the Slider is outside the bounds of the screen - int screenWidth = client.getWindow().getScaledWidth(); - int screenHeight = client.getWindow().getScaledHeight(); - if (x + width > screenWidth) { - x = screenWidth - width + 1; - } - if (y + 2 < 0) { - y += 2; - } - if (y + height + 2 > screenHeight) { - y = screenHeight - height - 2; - } - } - - /** - * Renders the widget on the screen. - * - * @param drawContext The matrix stack used for rendering - */ - public void render(DrawContext drawContext) { - tick(); - // Draw the label - TextRenderer textRenderer = client.textRenderer; - String labelText = label + ": " + String.format("%.1f", value); - int labelWidth = textRenderer.getWidth(labelText); - int labelX = (int) (x + (width - labelWidth) / 2.0f * textProgress) - 1; - int labelY = y + height - textRenderer.fontHeight - 6; - drawContext.drawTextWithShadow(textRenderer, labelText, labelX, labelY, 0xFFFFFFFF); - - // Draw the slider - int sliderWidth = width - 8; - int sliderHeight = 2; - int sliderX = x; - int sliderY = y + height - sliderHeight; - - drawSlider(drawContext, sliderX, sliderY, sliderWidth, sliderHeight); - - // Draw the handle - float handleWidth = 4; - float handleHeight = 10; - float handleX = sliderX + (value - minValue) / (maxValue - minValue) * (sliderWidth - handleWidth); - float handleY = sliderY + ((sliderHeight - handleHeight) / 2); - - if (progress >= 1.0f) { - DrawHelper.fillRoundedRect(drawContext, (int) handleX, (int) handleY, (int) (handleX + handleWidth), (int) (handleY + handleHeight), 0xFFFFFFFF); - } - - if (selectedWidget != null) - setPosition(selectedWidget.getX(), selectedWidget.getY() + textRenderer.fontHeight + 67); - updatePosition(); - } - - private void drawSlider(DrawContext drawContext, int sliderX, int sliderY, int sliderWidth, int sliderHeight) { - int visibleSliderWidth = (int) (sliderWidth * progress); - DrawHelper.fill(drawContext, sliderX, sliderY, sliderX + visibleSliderWidth, sliderY + sliderHeight, 0xFFFFFFFF); - } - - /** - * Returns whether the given point is within the bounds of this widget. - * - * @param x - X position of the point. - * @param y - Y position of the point. - * @return true if the point is within the bounds of this context menu, false otherwise. - */ - public boolean contains(double x, double y) { - return x >= this.x + 2 && x <= this.x - 2 + width && y >= this.y + 2 && y <= this.y - 2 + height; - } - - - /** - * Sets the position of the widget. - * - * @param x The new x position of the widget - * @param y The new y position of the widget - */ - public void setPosition(int x, int y) { - this.x = x; - this.y = y; - } - - /** - * Handles mouse clicks on the widget. - * - * @param mouseX The x position of the mouse cursor - * @param mouseY The y position of the mouse cursor - * @param button The mouse button that was clicked - * @return True if the mouse click was handled by the widget, false otherwise - */ - public boolean mouseClicked(double mouseX, double mouseY, int button) { - // Check if the mouse is over the slider - if (mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height) { - // Update the value based on the mouse position - MouseClicked = !MouseClicked; - setValue(minValue + (float) (mouseX - x) / width * (maxValue - minValue) - 0.001f); - getValue.accept(value); - return true; - } - MouseClicked = false; - return false; - } - - /** - * Handles mouse dragging on the widget. - * - * @param mouseX The current x position of the mouse cursor - * @param mouseY The current y position of the mouse cursor - * @param button The mouse button that is being dragged - * @param deltaX The change in x position of the mouse cursor since the last call to this method - * @param deltaY The change in y position of the mouse cursor since the last call to this method - * @return True if the mouse dragging was handled by the widget, false otherwise - */ - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - // Check if the mouse is over the slider - if (mouseX >= x && mouseX <= x + width && MouseClicked) { - // Update the value based on the mouse position - setValue(minValue + (float) (mouseX - x) / width * (maxValue - minValue)); - getValue.accept(value); - return true; - } - return false; - } - - /** - * Returns the current value of the slider. - * - * @return The current value of the slider - */ - public float getValue() { - return value; - } - - /** - * Sets the value of the slider. - * - * @param value The new value of the slider - */ - public void setValue(float value) { - this.value = Math.min(Math.max(value, minValue), maxValue); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/slider/SliderWidgetBuilder.java b/src/main/java/com/tanishisherewith/dynamichud/widget/slider/SliderWidgetBuilder.java deleted file mode 100644 index e79cd96..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/slider/SliderWidgetBuilder.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.tanishisherewith.dynamichud.widget.slider; - -import com.tanishisherewith.dynamichud.widget.Widget; -import net.minecraft.client.MinecraftClient; - -import java.util.function.Consumer; - -public class SliderWidgetBuilder { - private final MinecraftClient client; - private int x; - private int y; - private int width; - private int height; - private String label; - private float value; - private float minValue; - private float maxValue; - private Widget selectedWidget; - private Consumer getValue; - - public SliderWidgetBuilder(MinecraftClient client) { - this.client = client; - } - - public SliderWidgetBuilder setX(int x) { - this.x = x; - return this; - } - - public SliderWidgetBuilder setY(int y) { - this.y = y; - return this; - } - - public SliderWidgetBuilder setWidth(int width) { - this.width = width; - return this; - } - - public SliderWidgetBuilder setHeight(int height) { - this.height = height; - return this; - } - - public SliderWidgetBuilder setLabel(String label) { - this.label = label; - return this; - } - - public SliderWidgetBuilder setValue(float value) { - this.value = value; - return this; - } - - public SliderWidgetBuilder setMinValue(float minValue) { - this.minValue = minValue; - return this; - } - - public SliderWidgetBuilder setMaxValue(float maxValue) { - this.maxValue = maxValue; - return this; - } - public SliderWidgetBuilder getValue(Consumer getValue) { - this.getValue = getValue; - return this; - } - - public SliderWidgetBuilder setSelectedWidget(Widget selectedWidget) { - this.selectedWidget = selectedWidget; - return this; - } - - public SliderWidget build() { - return new SliderWidget(client, x, y, width, height, label, value, minValue, maxValue,getValue, selectedWidget); - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/widget/text/TextWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widget/text/TextWidget.java deleted file mode 100644 index d8a6fcd..0000000 --- a/src/main/java/com/tanishisherewith/dynamichud/widget/text/TextWidget.java +++ /dev/null @@ -1,277 +0,0 @@ -package com.tanishisherewith.dynamichud.widget.text; - -import com.tanishisherewith.dynamichud.helpers.ColorHelper; -import com.tanishisherewith.dynamichud.helpers.DrawHelper; -import com.tanishisherewith.dynamichud.interfaces.TextGenerator; -import com.tanishisherewith.dynamichud.util.contextmenu.ContextMenuOptionsProvider; -import com.tanishisherewith.dynamichud.widget.Widget; -import com.tanishisherewith.dynamichud.widget.WidgetBox; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.text.Text; - - -/** - * This class represents a text widget that displays a specified text on the screen. - */ -public class TextWidget extends Widget implements ContextMenuOptionsProvider { - protected static float rainbowSpeed = 15f; // The speed of the rainbow effect - protected String text; - protected TextGenerator dataText; - protected boolean shadow; // Whether to draw a shadow behind the text - protected boolean rainbow; // Whether to apply a rainbow effect to the text - protected int Textcolor; // The color of the text - protected int Datacolor; // The color of the Data - protected boolean TextcolorOptionEnabled = false; - protected boolean DatacolorOptionEnabled = false; - - - - /** - * Constructs a TextWidget object. - * - * @param client The Minecraft client instance - * @param text The text to display - * @param xPercent The x position of the widget as a percentage of the screen width - * @param yPercent The y position of the widget as a percentage of the screen height - */ - public TextWidget(MinecraftClient client, String text, TextGenerator dataText, float xPercent, float yPercent, boolean Shadow, boolean Rainbow, int Textcolor, int Datacolor, boolean enabled) { - super(client, text); - this.text = text; - this.dataText = dataText; - this.xPercent = xPercent; - this.yPercent = yPercent; - this.shadow = Shadow; - this.rainbow = Rainbow; - this.Textcolor = Textcolor; - this.Datacolor = Datacolor; - this.enabled = enabled; - } - - /** - * Toggles whether the Data color option is enabled. - */ - public void toggleTextColorOption() { - TextcolorOptionEnabled = !TextcolorOptionEnabled; - } - - /** - * Toggles whether the Text color option is enabled. - */ - public void toggleDataColorOption() { - DatacolorOptionEnabled = !DatacolorOptionEnabled; - } - - /** - * Sets whether the rainbow effect is enabled. - * - * @param rainbow True if the rainbow effect should be enabled, false otherwise - */ - public void setRainbow(boolean rainbow) { - this.rainbow = rainbow; - } - - /** - * Sets whether the shadow is enabled. - * - * @param shadow True if the shadow should be enabled, false otherwise - */ - public void setShadow(boolean shadow) { - this.shadow = shadow; - } - - /** - * Returns the speed of the rainbow effect. - * - * @return The speed of the rainbow effect - */ - public float getRainbowSpeed() { - return rainbowSpeed; - } - - /** - * Sets the speed of the rainbow effect. - * - * @param rainbowSpeed The new speed of the rainbow effect - */ - public static void setRainbowSpeed(float rainbowSpeed) { - TextWidget.rainbowSpeed = rainbowSpeed; - } - - /** - * Returns whether the rainbow effect is enabled. - * - * @return True if the rainbow effect is enabled, false otherwise - */ - public boolean hasRainbow() { - return rainbow; - } - - /** - * Returns whether the shadow is enabled. - * - * @return True if the shadow is enabled, false otherwise - */ - public boolean hasShadow() { - return shadow; - } - - - /** - * Returns the text displayed by this widget. - * - * @return The text displayed by this widget - */ - public String getText() { - return text; - } - - /** - * Returns the text displayed by this widget. - * - * @return The text displayed by this widget - */ - public String getDataText() { - return dataText.generateText(); - } - - - public void setDataTextGenerator(TextGenerator textGenerator) { - this.dataText = textGenerator; - } - - /** - * Returns the color of the text. - * - * @return The color of the text - */ - public int getTextcolor() { - return Textcolor; - } - - /** - * Returns the color of the Data. - * - * @return The color of the Data - */ - public int getDatacolor() { - return Datacolor; - } - - @Override - public void setTextGeneratorFromLabel() { - TextGenerator textGenerator = textGenerators.get(getText()); - if (textGenerator != null) { - setDataTextGenerator(textGenerator); - } - } - - /** - * Sets the color of the text. - * - * @param color The new color of the text - */ - public void setTextColor(int color) { - this.Textcolor = color; - } - - /** - * Sets the color of the text. - * - * @param color The new color of the text - */ - public void setDataColor(int color) { - this.Datacolor = color; - } - - - /** - * Returns whether color options are enabled for this widget. - * - * @return true if color options are enabled for this widget, false otherwise. - */ - public boolean isTextcolorOptionEnabled() { - return TextcolorOptionEnabled; - } - - /** - * Returns whether color options are enabled for this widget. - * - * @return true if color options are enabled for this widget, false otherwise. - */ - public boolean isDatacolorOptionEnabled() { - return DatacolorOptionEnabled; - } - - @Override - public WidgetBox getWidgetBox() { - TextRenderer textRenderer = client.textRenderer; - String cmtxt=getDataText()+getText() + "."; - float x1 = getX(); //- client.textRenderer.getWidth(textWidget.getText()); - float x2 = getX() + textRenderer.getWidth(cmtxt); - float y1 = getY() - 1 ; - float y2 = getY() + textRenderer.fontHeight - 1; - return new WidgetBox(x1, y1, x2, y2, scale); - } - - - /** - * Renders this widget on screen. - */ - @Override - public void render(DrawContext drawContext) { - drawContext.getMatrices().push(); - drawContext.getMatrices().translate(0, 0, 300); - int Textcolour = rainbow ? ColorHelper.getColorFromHue((System.currentTimeMillis() % 10000) / (rainbowSpeed * 400f)) : this.Textcolor; - int Datacolour = rainbow ? ColorHelper.getColorFromHue((System.currentTimeMillis() % 10000) / (rainbowSpeed * 400f)) : this.Datacolor; - drawTwoTexts(drawContext, getText(), getDataText(), ((getX() + 1)), (getY()), Textcolour, Datacolour, scale); - drawContext.getMatrices().pop(); - } - - - - - @Override - public void writeToTag(NbtCompound tag) { - super.writeToTag(tag); - tag.putBoolean("Rainbow", hasRainbow()); - tag.putBoolean("Shadow", hasShadow()); - tag.putInt("TextColor", Textcolor); - tag.putInt("DataColor", Datacolor); - tag.putString("Text", text); - } - - @Override - public void readFromTag(NbtCompound tag) { - super.readFromTag(tag); - shadow = tag.getBoolean("Shadow"); - rainbow = tag.getBoolean("Rainbow"); - Textcolor = tag.getInt("TextColor"); - Datacolor = tag.getInt("DataColor"); - text = tag.getString("Text"); - } - public void drawTwoTexts(DrawContext drawContext, String text1, String text2, int x, int y, int color1, int color2, float scale) { - drawText(drawContext, text1, x, y, color1); - int x2 = Math.round(x + client.textRenderer.getWidth(text1) * scale); - drawText(drawContext, text2, x2, y, color2); - } - - private void drawText(DrawContext drawContext, String text, int x, int y, int color) { - DrawHelper.drawTextWithScale(drawContext, text, x, y, color, shadow, scale); - //client.textRenderer.draw(text, getX(), getY(), color, scale); - - } - - @Override - public boolean isOptionEnabled(String label) { - return switch (label) { - case "Shadow" -> hasShadow(); - case "Rainbow" -> hasRainbow(); - case "TextColor" -> isTextcolorOptionEnabled(); - case "DataColor" -> isDatacolorOptionEnabled(); - default -> false; - }; - } -} diff --git a/src/main/java/com/tanishisherewith/dynamichud/widgets/TextWidget.java b/src/main/java/com/tanishisherewith/dynamichud/widgets/TextWidget.java new file mode 100644 index 0000000..efdf1a4 --- /dev/null +++ b/src/main/java/com/tanishisherewith/dynamichud/widgets/TextWidget.java @@ -0,0 +1,233 @@ +package com.tanishisherewith.dynamichud.widgets; + +import com.tanishisherewith.dynamichud.config.GlobalConfig; +import com.tanishisherewith.dynamichud.helpers.ColorHelper; +import com.tanishisherewith.dynamichud.utils.DynamicValueRegistry; +import com.tanishisherewith.dynamichud.utils.contextmenu.ContextMenu; +import com.tanishisherewith.dynamichud.utils.contextmenu.options.*; +import com.tanishisherewith.dynamichud.widget.Widget; +import com.tanishisherewith.dynamichud.widget.WidgetData; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.nbt.NbtCompound; +import org.lwjgl.glfw.GLFW; + +import java.awt.*; +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 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; + public TextWidget() { + this(null, null, 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(); + } + + /** + * 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; + this.shadow = shadow; + this.rainbow = rainbow; + createMenu(); + } + + 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); + */ + } + + @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()); + } + menu.render(drawContext, getX(), getY(), (int) Math.ceil(getHeight()),mouseX,mouseY); + } + + @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); + 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(); + menu.close(); + } + + @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); + } + + @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; + } + + 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(); + } + + public enum Enum { + Enum1, + Enum2, + Enum3 + } + + 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 Builder shadow(boolean shadow) { + this.shadow = shadow; + return self(); + } + + 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(); + } + + public Builder setTextColor(Color textColor) { + this.textColor = textColor; + return self(); + } + + @Override + protected Builder self() { + return this; + } + + @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); + } + widget.setPosition(x, y); + widget.setDraggable(isDraggable); + widget.setShouldScale(shouldScale); + return widget; + } + } + + + + +} diff --git a/src/main/resources/assets/DynamicHUD/DynamicHud-logo.png b/src/main/resources/assets/DynamicHUD/DynamicHud-logo.png deleted file mode 100644 index e2848ae..0000000 Binary files a/src/main/resources/assets/DynamicHUD/DynamicHud-logo.png and /dev/null differ diff --git a/src/main/resources/assets/DynamicHUD/logo.png b/src/main/resources/assets/DynamicHUD/logo.png new file mode 100644 index 0000000..bb4962c Binary files /dev/null and b/src/main/resources/assets/DynamicHUD/logo.png differ diff --git a/src/main/resources/dynamichud.mixins.json b/src/main/resources/dynamichud.mixins.json index f8d9096..2cff290 100644 --- a/src/main/resources/dynamichud.mixins.json +++ b/src/main/resources/dynamichud.mixins.json @@ -9,7 +9,6 @@ "defaultRequire": 1 }, "client": [ - "OptionsScreenMixin", - "TitleScreenMixin" + "ScreenMixin" ] } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 005e1b6..6b684b8 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -10,7 +10,7 @@ "contributors": [ "LumaaDev" ], - "icon": "assets/DynamicHUD/DynamicHud-logo.png", + "icon": "assets/dynamichud/logo.png", "contact": { "repo": "https://github.com/V-Fast/DynamicHUD", "issues": "https://github.com/V-Fast/DynamicHUD/issues" @@ -18,7 +18,7 @@ "license": "All-Rights-Reserved", "environment": "client", "entrypoints": { - "main": [ + "client": [ "com.tanishisherewith.dynamichud.DynamicHUD" ], "modmenu": [