From 01dd79c5cef3af927323b5049ee93151966e08d7 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 7 Mar 2026 00:49:38 +0100 Subject: [PATCH] Implementing components --- .../common/minecraft/MinecraftProvider.java | 2 + .../ui/components/CosmeticPreview.java | 43 ++++ .../ui/components/ElementRendererImpl.java | 98 ++++++++++ .../ui/components/SaturnModule.java | 161 +++++++++++++++ .../saturnclient/ui/components/Sidebar.java | 121 ++++++++++++ .../ui/components/SkinPreview.java | 91 +++++++++ .../ui/components/inputs/FloatInput.java | 59 ++++++ .../ui/components/inputs/HexInput.java | 183 ++++++++++++++++++ .../ui/components/inputs/Input.java | 112 +++++++++++ .../ui/components/inputs/IntInput.java | 59 ++++++ .../components/inputs/KeybindingSelector.java | 42 ++++ .../ui/components/inputs/Select.java | 40 ++++ .../ui/components/inputs/Toggle.java | 30 +++ 13 files changed, 1041 insertions(+) create mode 100644 src/main/java/org/saturnclient/ui/components/CosmeticPreview.java create mode 100644 src/main/java/org/saturnclient/ui/components/ElementRendererImpl.java create mode 100644 src/main/java/org/saturnclient/ui/components/SaturnModule.java create mode 100644 src/main/java/org/saturnclient/ui/components/Sidebar.java create mode 100644 src/main/java/org/saturnclient/ui/components/SkinPreview.java create mode 100644 src/main/java/org/saturnclient/ui/components/inputs/FloatInput.java create mode 100644 src/main/java/org/saturnclient/ui/components/inputs/HexInput.java create mode 100644 src/main/java/org/saturnclient/ui/components/inputs/Input.java create mode 100644 src/main/java/org/saturnclient/ui/components/inputs/IntInput.java create mode 100644 src/main/java/org/saturnclient/ui/components/inputs/KeybindingSelector.java create mode 100644 src/main/java/org/saturnclient/ui/components/inputs/Select.java create mode 100644 src/main/java/org/saturnclient/ui/components/inputs/Toggle.java diff --git a/src/main/java/org/saturnclient/common/minecraft/MinecraftProvider.java b/src/main/java/org/saturnclient/common/minecraft/MinecraftProvider.java index d2465f4..b8d0998 100644 --- a/src/main/java/org/saturnclient/common/minecraft/MinecraftProvider.java +++ b/src/main/java/org/saturnclient/common/minecraft/MinecraftProvider.java @@ -15,4 +15,6 @@ public abstract class MinecraftProvider { public abstract int getWidth(String text, int font); public abstract void registerBufferedImageTexture(SaturnIdentifier i, BufferedImage bi); + + public abstract String getKeyName(int key); } diff --git a/src/main/java/org/saturnclient/ui/components/CosmeticPreview.java b/src/main/java/org/saturnclient/ui/components/CosmeticPreview.java new file mode 100644 index 0000000..2ae9d49 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/CosmeticPreview.java @@ -0,0 +1,43 @@ +package org.saturnclient.ui.components; + +import org.saturnclient.common.minecraft.bindings.SaturnIdentifier; +import org.saturnclient.config.Theme; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; + +public class CosmeticPreview extends Element { + static int padding = 14; + + SaturnIdentifier sprite; + private Runnable onClick; + private boolean isSelected; + + public CosmeticPreview(boolean isSelected, SaturnIdentifier sprite, Runnable onClick) { + this.sprite = sprite; + this.onClick = onClick; + this.isSelected = isSelected; + + this.width = 50; + this.height = 111; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, + isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value); + + int texWidth = width - padding; + int texHeight = height - padding; + + renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight, + Theme.FOREGROUND.value); + } + + @Override + public void click(int mouseX, int mouseY) { + if (onClick != null) { + onClick.run(); + } + } +} diff --git a/src/main/java/org/saturnclient/ui/components/ElementRendererImpl.java b/src/main/java/org/saturnclient/ui/components/ElementRendererImpl.java new file mode 100644 index 0000000..ad664d8 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/ElementRendererImpl.java @@ -0,0 +1,98 @@ +package org.saturnclient.ui.components; + +import java.util.ArrayList; +import java.util.List; + +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.ElementRenderer; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.Utils; + +public class ElementRendererImpl extends ElementRenderer { + public void draw(List elements, Element element) { + synchronized (elements) { + elements.add(element); + } + + if (element.animation != null) { + element.animation.init(element); + } + } + + public void render(List elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY) { + for (Element element : elements) { + element.playAnimationFrame(elapsed); + + renderScope.getMatrixStack().push(); + renderScope.setOpacity(element.opacity); + renderScope.getMatrixStack().translate(element.x, element.y, 0); + renderScope.getMatrixStack().scale(element.scale, element.scale, 1.0f); + element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, element)); + renderScope.getMatrixStack().pop(); + } + } + + public void mouseClicked(List elements, double mouseX, double mouseY, int button) { + if (button == 0) { + for (Element element : new ArrayList<>(elements)) { + element.focused = false; + + int adjustedMouseX = (int) mouseX - element.x; + int adjustedMouseY = (int) mouseY - element.y; + + element.mouseClicked(adjustedMouseX * element.scale, adjustedMouseY * element.scale, button); + + if (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width, + element.height, element.scale)) { + element.focused = true; + element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale)); + } + } + } + } + + public void mouseScrolled( + List elements, + double mouseX, + double mouseY, + double horizontalAmount, + double verticalAmount) { + for (Element element : new ArrayList<>(elements)) { + if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height, + element.scale)) { + element.scroll( + (int) mouseX - element.x, + (int) mouseY - element.y, + horizontalAmount, + verticalAmount); + } + } + } + + public void keyPressed(List elements, int keyCode, int scanCode, int modifiers) { + for (Element element : new ArrayList<>(elements)) { + if (element.focused && element.opacity > 0) { + element.keyPressed(keyCode, scanCode, modifiers); + + char typedChar = Utils.getCharFromKey(keyCode, modifiers); + if (typedChar != '\0') { + element.charTyped(typedChar); + } + } + } + } + + public void mouseDragged(List elements, double mouseX, double mouseY, int button, double deltaX, + double deltaY) { + for (Element element : new ArrayList<>(elements)) { + element.mouseDragged(mouseX - element.x, mouseY - element.y, button, deltaX, deltaY); + } + } + + public void mouseReleased(List elements, double mouseX, double mouseY, int button) { + for (Element element : new ArrayList<>(elements)) { + element.mouseReleased(mouseX - element.x, mouseY - element.y, button); + } + } +} diff --git a/src/main/java/org/saturnclient/ui/components/SaturnModule.java b/src/main/java/org/saturnclient/ui/components/SaturnModule.java new file mode 100644 index 0000000..140b57b --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/SaturnModule.java @@ -0,0 +1,161 @@ +package org.saturnclient.ui.components; + +import org.saturnclient.modules.Module; +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.config.Theme; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.Utils; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.ui.resources.Textures; +import org.saturnclient.ui.screens.ConfigEditor; + +public class SaturnModule extends Element { + + private final Module mod; + + static int p = 10; // padding + static int s = 14; // settings icon size + static int expand = 5; // settings icon click area + + public SaturnModule(Module mod) { + this.mod = mod; + this.width = 160; + this.height = 50; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + boolean hovered = ctx.isHovering(); + boolean enabled = mod.isEnabled(); + + int iconX = width - s - p; + int iconY = height - s - p; + + boolean settingsHover = ctx.isHovering( + getSettingsX(), + getSettingsY(), + getSettingsHitBox(), + getSettingsHitBox()); + + // Resolve theme colors + int bgColor = Theme.PRIMARY.value; + int fgColor = hovered && !settingsHover ? Theme.ACCENT.value : Theme.PRIMARY_FG.value; + + int iconBg = enabled ? Theme.ACCENT.value : Theme.PRIMARY.value; + int iconFg = enabled ? Theme.ACCENT_FG.value : fgColor; + + int settingsColor = settingsHover + ? Theme.ACCENT.value + : Theme.withAlpha(160, fgColor); + + int h = height - (p * 2); + + // Main background + renderScope.drawRoundedRectangle( + 0, 0, + width, height, + Theme.WIDGET_RADIUS.value, + bgColor); + + // Icon background + renderScope.drawRoundedRectangle( + p, p, + h, h, + Theme.WIDGET_RADIUS.value, + iconBg); + + // Icon + renderScope.drawTexture( + mod.getIconTexture(), + p + (p / 2), + p + (p / 2), + 0, 0, + h - p, + h - p, + iconFg); + + // Module name + renderScope.drawText( + 0.6f, + mod.getName(), + p + h + 4, + p + 1, + Theme.FONT.value, + fgColor); + + // Version (right aligned) + String version = mod.getVersion(); + int versionWidth = (int) (Fonts.getWidth(version, Theme.FONT.value) * 0.35f); + + renderScope.drawText( + 0.35f, + version, + width - p - versionWidth, + p + 1, + Theme.FONT.value, + fgColor); + + // Tags + int xt = p + h + 4; + for (String tag : mod.getTags()) { + int tagWidth = (int) (Fonts.getWidth(tag, Theme.FONT.value) * 0.4f); + + renderScope.drawRoundedRectangle( + xt, + p + (h / 2), + tagWidth + 6, + 12, + 5, + Theme.ACCENT.value); + + renderScope.drawText( + 0.4f, + tag, + xt + 2, + p + (h / 2) + 3, + Theme.FONT.value, + Theme.ACCENT_FG.value); + + xt += tagWidth + 9; + } + + // Settings icon + renderScope.drawTexture( + Textures.SETTINGS, + iconX, + iconY, + 0, 0, + s, s, + settingsColor); + } + + @Override + public void click(int mouseX, int mouseY) { + boolean settingsHover = Utils.isHovering( + mouseX - getSettingsX(), + mouseY - getSettingsY(), + getSettingsHitBox(), + getSettingsHitBox(), + 1.0f); + + if (settingsHover) { + SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig())); + } else { + mod.setEnabled(!mod.isEnabled()); + } + } + + private int getSettingsX() { + return width - s - p - expand; + } + + private int getSettingsY() { + return height - s - p - expand; + } + + private int getSettingsHitBox() { + return s + expand * 2; + } +} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/ui/components/Sidebar.java b/src/main/java/org/saturnclient/ui/components/Sidebar.java new file mode 100644 index 0000000..8c4ef45 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/Sidebar.java @@ -0,0 +1,121 @@ +package org.saturnclient.ui.components; + +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.common.minecraft.bindings.SaturnIdentifier; +import org.saturnclient.config.Config; +import org.saturnclient.config.Theme; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.Utils; +import org.saturnclient.ui.resources.Textures; +import org.saturnclient.ui.screens.ConfigEditor; +import org.saturnclient.ui.screens.ModMenu; +import org.saturnclient.ui.screens.cosmetics.CloakMenu; +import org.saturnclient.ui.screens.store.CloakStore; + +public class Sidebar extends Element { + public static class SidebarComponent { + Runnable onClick; + boolean end; + SaturnIdentifier sprite; + + public SidebarComponent(SaturnIdentifier sprite, Runnable onClick, boolean end) { + this.onClick = onClick; + this.end = end; + this.sprite = sprite; + } + } + + Runnable onClose; + SidebarComponent[] components = { + new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> { + SaturnClient.client.setScreen(new ModMenu()); + }, false), + new Sidebar.SidebarComponent(Textures.SETTINGS, () -> { + SaturnClient.client.setScreen(new ConfigEditor(Config.config)); + }, false), + + new Sidebar.SidebarComponent(Textures.SHIRT, () -> { + SaturnClient.client.setScreen(new CloakMenu()); + }, false), + + new Sidebar.SidebarComponent(Textures.CLOSE, () -> { + onClose.run(); + }, true), + + new Sidebar.SidebarComponent(Textures.STORE, () -> { + SaturnClient.client.setScreen(new CloakStore()); + }, true), + }; + int active = 0; + + public Sidebar(int active, Runnable onClose) { + this.width = 30; + this.height = 350; + this.active = active; + this.onClose = onClose; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + int topRow = 0; + int bottomRow = 0; + + int gap = 5; // 5px gap between icons + int padding = 10; + int sp = 30 - padding + gap; + int s = 30 - (padding * 2); + int s2 = s + padding; + + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Theme.BACKGROUND.value); + + int i = 0; + + for (SidebarComponent component : components) { + int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow)); + boolean selected = ctx.isHovering(0, y - (padding / 2), s2, s2) || active == i; + + renderScope.drawRoundedRectangle(padding / 2, y - (padding / 2), s + padding, + s + padding, Theme.WIDGET_RADIUS.value, Theme.getBg(selected)); + + renderScope.drawTexture(component.sprite, padding, y, 0, 0, s, s, Theme.getFg(selected)); + + if (component.end) { + bottomRow += 1; + } else { + topRow += 1; + } + + i++; + } + } + + @Override + public void click(int mouseX, int mouseY) { + int topRow = 0; + int bottomRow = 0; + + int gap = 5; + int padding = 10; + int sp = 30 - padding + gap; + int s = 30 - (padding * 2); + int s2 = s + padding; + + for (SidebarComponent component : components) { + int y = component.end ? ((height - (sp * bottomRow)) - (s * 2)) : (padding + (sp * topRow)); + boolean hovering = Utils.isHovering(mouseX - (padding / 2), mouseY - (y - (padding / 2)), s2, s2, scale); + + if (hovering) { + component.onClick.run(); + break; + } + + if (component.end) { + bottomRow += 1; + } else { + topRow += 1; + } + } + } +} diff --git a/src/main/java/org/saturnclient/ui/components/SkinPreview.java b/src/main/java/org/saturnclient/ui/components/SkinPreview.java new file mode 100644 index 0000000..58b82ab --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/SkinPreview.java @@ -0,0 +1,91 @@ +package org.saturnclient.ui.components; + +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.saturnclient.common.minecraft.bindings.SaturnQuaternionf; +import org.saturnclient.saturnclient.SaturnClient; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; + +import net.minecraft.client.render.DiffuseLighting; +import net.minecraft.client.render.entity.EntityRenderDispatcher; +import net.minecraft.entity.LivingEntity; + +public class SkinPreview extends Element { + private boolean negativeAngle = false; + private float angle = 0.0f; + + public SkinPreview(float angle, boolean negativeAngle) { + this.angle = angle; + this.negativeAngle = negativeAngle; + } + + public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float f, + float mouseX, + float mouseY, LivingEntity entity, float angle, boolean negativeAngle) { + float g = (float) (x1 + x2) / 2.0F; + float h = (float) (y1 + y2) / 2.0F; + renderScope.enableScissor(0, 6, x2, y2); + float i = (float) Math.atan((double) ((g - mouseX) / 40.0F)); + float j = (float) Math.atan((double) ((h - mouseY) / 40.0F)); + Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F); + float k = entity.bodyYaw; + float l = entity.getYaw(); + float m = entity.getPitch(); + float n = entity.prevHeadYaw; + float o = entity.headYaw; + entity.setYaw(angle); + entity.bodyYaw = angle; + entity.setPitch(-(j * 20.0F)); + entity.headYaw = negativeAngle ? (i * 40.0F) + 180f : -(i * 40.0F); + entity.prevHeadYaw = entity.getHeadYaw(); + entity.setCustomNameVisible(false); + + float p = entity.getScale(); + Vector3f vector3f = new Vector3f(0.0F, entity.getHeight() / 2.0F + f * p, 0.0F); + float q = (float) size / p; + drawEntity(renderScope, g, h, q, vector3f, quaternionf, null, entity); + + entity.setCustomNameVisible(true); + entity.bodyYaw = k; + entity.setYaw(l); + entity.setPitch(m); + entity.prevHeadYaw = n; + entity.headYaw = o; + renderScope.disableScissor(); + } + + public static void drawEntity(RenderScope renderScope, float x, float y, float size, Vector3f vector3f, + Quaternionf quaternionf, @Nullable Quaternionf quaternionf2, LivingEntity entity) { + renderScope.getMatrixStack().push(); + renderScope.getMatrixStack().translate((double) x, (double) y, 50.0); + renderScope.getMatrixStack().scale(size, size, -size); + renderScope.getMatrixStack().translate(vector3f.x, vector3f.y, vector3f.z); + renderScope.getMatrixStack().multiply(new SaturnQuaternionf(quaternionf)); + renderScope.draw(); + DiffuseLighting.method_34742(); + EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher(); + + entityRenderDispatcher.setRenderShadows(false); + // TODO add the method draw with vertex consumers + // renderScope.draw((vertexConsumers) -> { + // entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 1.0F, renderScope.getMatrixStack(), vertexConsumers, + // 15728880); + // }); + renderScope.draw(); + entityRenderDispatcher.setRenderShadows(true); + renderScope.getMatrixStack().pop(); + DiffuseLighting.enableGuiDepthLighting(); + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + LivingEntity entity = SaturnClient.client.player; + if (entity != null) { + drawEntity(renderScope, 0, 0, 75, 78, 30, 0.0625F, ctx.mouseX / scale, ctx.mouseY / scale, entity, angle, + negativeAngle); + } + } +} diff --git a/src/main/java/org/saturnclient/ui/components/inputs/FloatInput.java b/src/main/java/org/saturnclient/ui/components/inputs/FloatInput.java new file mode 100644 index 0000000..234d189 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/inputs/FloatInput.java @@ -0,0 +1,59 @@ +package org.saturnclient.ui.components.inputs; + +import org.saturnclient.config.manager.ConfigManager; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.resources.Fonts; + +public class FloatInput extends Input { + public Property prop; + + public FloatInput(Property prop) { + this.width = 120; + this.height = Fonts.getHeight(); + this.prop = prop; + this.text = String.valueOf(prop.value); + } + + @Override + public void charTyped(char chr) { + if ((Character.isDigit(chr) || chr == '.' || (chr == '-' && cursorPosition == 0)) && text.length() < 10) { + if (text.equals("0") && cursorPosition == 1) { + text = "" + chr; + } else { + text = text.substring(0, cursorPosition) + + chr + + text.substring(cursorPosition); + + try { + prop.value = text.isEmpty() ? 0 : Float.parseFloat(text); + cursorPosition++; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void backspace() { + try { + if (text.length() == 0) { + text = "0"; + cursorPosition = 1; + } + prop.value = Float.parseFloat(text); + ConfigManager.save(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void checkReset() { + if (prop.isReset) { + this.text = String.valueOf(prop.value); + this.cursorPosition = 0; + prop.isReset = false; + } + } +} diff --git a/src/main/java/org/saturnclient/ui/components/inputs/HexInput.java b/src/main/java/org/saturnclient/ui/components/inputs/HexInput.java new file mode 100644 index 0000000..e72d1a5 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/inputs/HexInput.java @@ -0,0 +1,183 @@ +package org.saturnclient.ui.components.inputs; + +import java.awt.Color; + +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; + +public class HexInput extends Element { + public Property prop; + + // Picker state + private boolean open = false; + private float hue = 0f; + private float opacity = 1f; + private int selectedX = 0; + private int selectedY = 0; + + private boolean draggingOpacity = false; + + // Dimensions + private static final int PICKER_WIDTH = 100; + private static final int PICKER_HEIGHT = 100; + private static final int SLIDER_WIDTH = 100; + private static final int SLIDER_HEIGHT = 10; + private static final int HUE_SLIDER_HEIGHT = 10; + + public HexInput(Property prop) { + this.prop = prop; + this.width = 58; + this.height = 20; + syncFromColor(); + } + + @Override + public void click(int mouseX, int mouseY) { + open = !open; + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + if (open) { + handlePickerClick(mouseX - width - 8, mouseY); + } + } + + @Override + public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (open) { + handlePickerDrag(mouseX - width - 8, mouseY); + } + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) { + draggingOpacity = false; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + // Draw the main color rectangle + renderScope.drawRoundedRectangle(0, 0, width, height, 6, prop.value); + + if (!open) + return; + + // Draw the picker relative to (width + 8, 0) + int pickerX = width + 8; + int pickerY = 0; + + renderColorSquare(renderScope, pickerX, pickerY); + renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_HEIGHT - 10); + renderHueSlider(renderScope, pickerX, pickerY + PICKER_HEIGHT + 10); + } + + private void renderColorSquare(RenderScope renderScope, int xStart, int yStart) { + for (int x = 0; x < PICKER_WIDTH; x++) { + for (int y = 0; y < PICKER_HEIGHT; y++) { + float saturation = x / (float) PICKER_WIDTH; + float value = 1f - y / (float) PICKER_HEIGHT; + int color = Color.HSBtoRGB(hue, saturation, value); + int alpha = (int) (opacity * 255) & 0xFF; + color = (alpha << 24) | (color & 0xFFFFFF); + renderScope.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, color); + } + } + + // Draw selection square + int selX = xStart + selectedX - 4; + int selY = yStart + selectedY - 4; + renderScope.fill(selX, selY, selX + 8, selY + 8, 0xFF000000 | prop.value); + renderScope.drawBorder(selX, selY, 8, 8, 0xFFFFFFFF); + } + + private void renderOpacitySlider(RenderScope renderScope, int xStart, int yStart) { + for (int x = 0; x < SLIDER_WIDTH; x++) { + float localOpacity = x / (float) SLIDER_WIDTH; + int alpha = (int) (localOpacity * 255) & 0xFF; + int color = (alpha << 24) | (prop.value & 0xFFFFFF); + renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color); + } + + int knobX = xStart + (int) (opacity * SLIDER_WIDTH); + renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000); + } + + private void renderHueSlider(RenderScope renderScope, int xStart, int yStart) { + for (int x = 0; x < SLIDER_WIDTH; x++) { + float localHue = x / (float) SLIDER_WIDTH; + int color = Color.HSBtoRGB(localHue, 1f, 1f); + renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color); + } + + int knobX = xStart + (int) (hue * SLIDER_WIDTH); + renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000); + } + + private void handlePickerClick(double mouseX, double mouseY) { + // Color square + if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) { + selectedX = (int) mouseX; + selectedY = (int) mouseY; + updateColorFromSelection(); + } + + // Opacity slider + int opY = -SLIDER_HEIGHT - 10; + if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= opY && mouseY <= opY + SLIDER_HEIGHT) { + updateOpacity(mouseX); + draggingOpacity = true; + } + + // Hue slider + int hueY = PICKER_HEIGHT + 10; + if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= hueY && mouseY <= hueY + HUE_SLIDER_HEIGHT) { + updateHue(mouseX); + } + } + + private void handlePickerDrag(double mouseX, double mouseY) { + // Opacity + if (draggingOpacity) + updateOpacity(mouseX); + + // Color square + if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) { + selectedX = (int) mouseX; + selectedY = (int) mouseY; + updateColorFromSelection(); + } + } + + private void updateColorFromSelection() { + float saturation = selectedX / (float) PICKER_WIDTH; + float value = 1f - selectedY / (float) PICKER_HEIGHT; + int color = Color.HSBtoRGB(hue, saturation, value); + int alpha = (int) (opacity * 255) & 0xFF; + prop.value = (alpha << 24) | (color & 0xFFFFFF); + } + + private void updateHue(double mouseX) { + hue = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH)); + updateColorFromSelection(); + } + + private void updateOpacity(double mouseX) { + opacity = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH)); + updateColorFromSelection(); + } + + private void syncFromColor() { + int rgb = prop.value & 0xFFFFFF; + float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null); + hue = hsb[0]; + selectedX = (int) (hsb[1] * PICKER_WIDTH); + selectedY = (int) ((1f - hsb[2]) * PICKER_HEIGHT); + int alpha = (prop.value >> 24) & 0xFF; + if (alpha == 0) + alpha = 255; + opacity = alpha / 255f; + } +} \ No newline at end of file diff --git a/src/main/java/org/saturnclient/ui/components/inputs/Input.java b/src/main/java/org/saturnclient/ui/components/inputs/Input.java new file mode 100644 index 0000000..c1cd845 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/inputs/Input.java @@ -0,0 +1,112 @@ +package org.saturnclient.ui.components.inputs; + +import org.saturnclient.config.Theme; +import org.saturnclient.config.manager.Key; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; + +public abstract class Input extends Element { + String text = ""; + public int cursorPosition = 0; + + public abstract void checkReset(); + + public abstract void backspace(); + + public boolean validateBackspace() { + return true; + } + + @Override + public void keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode == Key.GLFW_KEY_BACKSPACE) { + if (cursorPosition > 0 && validateBackspace()) { + text = text.substring(0, cursorPosition - 1) + + text.substring(cursorPosition); + cursorPosition--; + backspace(); + } + } else if (keyCode == Key.GLFW_KEY_LEFT) { + if (cursorPosition > 0) { + cursorPosition--; + } + } else if (keyCode == Key.GLFW_KEY_RIGHT) { + if (cursorPosition < text.length()) { + cursorPosition++; + } + } + } + + @Override + public void click(int mouseX, int mouseY) { + int scrollOffset = getScrollOffset(); + String visibleText = text.substring(scrollOffset); + + int newCursorPos = scrollOffset; + float textWidth = 0; + + for (int i = 0; i < visibleText.length(); i++) { + textWidth = Fonts.getWidth(visibleText.substring(0, i + 1), Theme.FONT.value) * 0.6f; + + if (textWidth > mouseX) { + break; + } + + newCursorPos++; + } + + cursorPosition = newCursorPos; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + checkReset(); + + renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000); + + int scrollOffset = getScrollOffset(); + String visibleText = getVisibleText(scrollOffset); + int textColor = focused ? 0xFFFFFF : 0xAAAAAA; + renderScope.drawText(0.6f, visibleText, 4, 4, Theme.FONT.value, textColor); + + if (focused) { + int cursorX = 4 + + (int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), Theme.FONT.value) + * 0.6f); + + renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF); + } + } + + protected int getScrollOffset() { + if (text.isEmpty()) { + return 0; + } + + int offset = 0; + int maxWidth = width - 4; // Available width for text + while (offset < cursorPosition) { // Ensure cursor remains visible + String visibleText = text.substring(offset, cursorPosition); + if (Fonts.getWidth(visibleText, Theme.FONT.value) > maxWidth) { + offset++; + } else { + break; + } + } + return offset; + } + + protected String getVisibleText(int scrollOffset) { + String visibleText = text.substring(scrollOffset); + + // Ensure text does not overflow past the available width + for (int i = 1; i <= visibleText.length(); i++) { + if (Fonts.getWidth(visibleText.substring(0, i), Theme.FONT.value) > width - 4) { + return visibleText.substring(0, i - 1); // Trim overflow + } + } + return visibleText; + } +} diff --git a/src/main/java/org/saturnclient/ui/components/inputs/IntInput.java b/src/main/java/org/saturnclient/ui/components/inputs/IntInput.java new file mode 100644 index 0000000..4b8bea9 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/inputs/IntInput.java @@ -0,0 +1,59 @@ +package org.saturnclient.ui.components.inputs; + +import org.saturnclient.config.manager.ConfigManager; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.resources.Fonts; + +public class IntInput extends Input { + public Property prop; + + public IntInput(Property prop) { + this.width = 120; + this.height = Fonts.getHeight(); + this.prop = prop; + this.text = String.valueOf(prop.value); + } + + @Override + public void charTyped(char chr) { + if ((Character.isDigit(chr) || chr == '.' || (chr == '-' && cursorPosition == 0)) && text.length() < 10) { + if (text.equals("0") && cursorPosition == 1) { + text = "" + chr; + } else { + text = text.substring(0, cursorPosition) + + chr + + text.substring(cursorPosition); + + try { + prop.value = text.isEmpty() ? 0 : Integer.parseInt(text); + cursorPosition++; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void backspace() { + try { + if (text.length() == 0) { + text = "0"; + cursorPosition = 1; + } + prop.value = Integer.parseInt(text); + ConfigManager.save(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void checkReset() { + if (prop.isReset) { + this.text = String.valueOf(prop.value); + this.cursorPosition = 0; + prop.isReset = false; + } + } +} diff --git a/src/main/java/org/saturnclient/ui/components/inputs/KeybindingSelector.java b/src/main/java/org/saturnclient/ui/components/inputs/KeybindingSelector.java new file mode 100644 index 0000000..f3e9910 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/inputs/KeybindingSelector.java @@ -0,0 +1,42 @@ +package org.saturnclient.ui.components.inputs; + +import org.saturnclient.common.minecraft.MinecraftProvider; +import org.saturnclient.config.Theme; +import org.saturnclient.config.manager.Key; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; + +public class KeybindingSelector extends Element { + Property prop; + + public KeybindingSelector(Property prop) { + this.prop = prop; + this.width = 120; + this.height = Fonts.getHeight(); + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + int textColor = focused ? 0xFFFFFF : 0xAAAAAA; + renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000); + String name = MinecraftProvider.PROVIDER.getKeyName(prop.value); + + renderScope.drawText(0.6f, + prop.value == -1 ? "" + : name != null ? name.toUpperCase() : "", + 4, 4, Theme.FONT.value, textColor); + } + + @Override + public void keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode == Key.GLFW_KEY_BACKSPACE) { + prop.value = -1; + } else { + prop.value = keyCode; + } + focused = false; + } +} diff --git a/src/main/java/org/saturnclient/ui/components/inputs/Select.java b/src/main/java/org/saturnclient/ui/components/inputs/Select.java new file mode 100644 index 0000000..ef62975 --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/inputs/Select.java @@ -0,0 +1,40 @@ +package org.saturnclient.ui.components.inputs; + +import org.saturnclient.config.Theme; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.Utils; +import org.saturnclient.ui.resources.Fonts; + +public class Select extends Element { + private Property prop; + + public Select(Property prop) { + this.prop = prop; + this.width = 150; + this.height = 15; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawText(0.8f, "<", 0, 0, Theme.FONT.value, Theme.FOREGROUND.value); + + String text = prop.getSelection(); + + renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, Theme.FONT.value) * 0.6f)) / 2, 1, + Theme.FONT.value, Theme.FOREGROUND.value); + + renderScope.drawText(0.8f, ">", width - 10, 0, Theme.FONT.value, Theme.FOREGROUND.value); + } + + @Override + public void click(int mouseX, int mouseY) { + if (Utils.isHovering(mouseX - (width - 10), mouseY, 10, height, 1.0f)) { + prop.next(); + } else if (Utils.isHovering(mouseX, mouseY, 10, height, 1.0f)) { + prop.prev(); + } + } +} diff --git a/src/main/java/org/saturnclient/ui/components/inputs/Toggle.java b/src/main/java/org/saturnclient/ui/components/inputs/Toggle.java new file mode 100644 index 0000000..6c4f3ec --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/inputs/Toggle.java @@ -0,0 +1,30 @@ +package org.saturnclient.ui.components.inputs; + +import org.saturnclient.config.Theme; +import org.saturnclient.config.manager.Property; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; + +public class Toggle extends Element { + private Property prop; + + public Toggle(Property prop) { + this.prop = prop; + this.width = 60; + this.height = 30; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawRoundedRectangle(0, 0, width, height, height, + Theme.withAlpha(0.5f, prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value)); + renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30, + prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value); + } + + @Override + public void click(int mouseX, int mouseY) { + prop.value = !prop.value; + } +}