diff --git a/common/src/main/java/org/saturnclient/ui/ElementRenderer.java b/common/src/main/java/org/saturnclient/ui/ElementRenderer.java new file mode 100644 index 0000000..fb85aca --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/ElementRenderer.java @@ -0,0 +1,27 @@ +package org.saturnclient.ui; + +import java.util.List; + +public abstract class ElementRenderer { + public static ElementRenderer INSTANCE; + + public abstract void draw(List elements, Element element); + + public abstract void render(List elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY); + + public abstract void mouseClicked(List elements, double mouseX, double mouseY, int button); + + public abstract void mouseScrolled( + List elements, + double mouseX, + double mouseY, + double horizontalAmount, + double verticalAmount); + + public abstract void keyPressed(List elements, int keyCode, int scanCode, int modifiers); + + public abstract void mouseDragged(List elements, double mouseX, double mouseY, int button, double deltaX, + double deltaY); + + public abstract void mouseReleased(List elements, double mouseX, double mouseY, int button); +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java b/common/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java new file mode 100644 index 0000000..3226e0f --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java @@ -0,0 +1,66 @@ +package org.saturnclient.ui.elements; + +import java.util.ArrayList; +import java.util.List; + +import org.saturnclient.config.AnimationConfig; +import org.saturnclient.config.Config; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.ElementRenderer; +import org.saturnclient.ui.RenderScope; + +public class AnimationStagger extends Element { + protected List children = new ArrayList<>(); + public int delay; + + public AnimationStagger(int delay) { + this.delay = delay; + } + + public AnimationStagger(AnimationConfig config) { + this.delay = config.stagger.value; + } + + public void draw(Element element) { + if (!Config.stagger.value) { + element.animation = null; + } + + if (element.animation != null) { + element.animation.delay = delay * children.size(); + } + + ElementRenderer.INSTANCE.draw(children, element); + + this.height = Math.max(this.height, element.y + element.height); + this.width = Math.max(this.width, element.x + element.width); + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + ElementRenderer.INSTANCE.render(children, ctx.elapsed, renderScope, + ctx.mouseX, + ctx.mouseY); + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + ElementRenderer.INSTANCE.mouseClicked(children, mouseX, mouseY, button); + } + + @Override + public void keyPressed(int keyCode, int scanCode, int modifiers) { + ElementRenderer.INSTANCE.keyPressed(children, keyCode, scanCode, modifiers); + } + + @Override + public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + ElementRenderer.INSTANCE.mouseDragged(children, mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) { + ElementRenderer.INSTANCE.mouseReleased(children, mouseX, mouseY, button); + } +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/Button.java b/common/src/main/java/org/saturnclient/ui/elements/Button.java new file mode 100644 index 0000000..3c0d5ed --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/Button.java @@ -0,0 +1,35 @@ +package org.saturnclient.ui.elements; + +import org.saturnclient.config.Theme; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; + +public class Button extends Element { + private String text; + private Runnable onClick; + + public Button(String text, Runnable onClick) { + this.text = text; + this.onClick = onClick; + this.dimensions(Fonts.getWidth(text, Theme.FONT.value) + 50, 38); + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, + ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value); + + renderScope.drawText(text, Fonts.centerX(width, text, Theme.FONT.value), Fonts.centerY(height), + Theme.FONT.value, + ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value); + } + + @Override + public void click(int mouseX, int mouseY) { + if (onClick != null) { + onClick.run(); + } + } +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/ClickableTexture.java b/common/src/main/java/org/saturnclient/ui/elements/ClickableTexture.java new file mode 100644 index 0000000..1347b6d --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/ClickableTexture.java @@ -0,0 +1,30 @@ +package org.saturnclient.ui.elements; + +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 ClickableTexture extends Element { + SaturnIdentifier sprite; + private Runnable onClick; + + public ClickableTexture(SaturnIdentifier sprite, Runnable onClick) { + this.sprite = sprite; + this.onClick = onClick; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height, + ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value); + } + + @Override + public void click(int mouseX, int mouseY) { + if (onClick != null) { + onClick.run(); + } + } +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/ImageTexture.java b/common/src/main/java/org/saturnclient/ui/elements/ImageTexture.java new file mode 100644 index 0000000..573dc4f --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/ImageTexture.java @@ -0,0 +1,36 @@ +package org.saturnclient.ui.elements; + +import org.saturnclient.common.minecraft.bindings.SaturnIdentifier; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.Identifier; + +public class ImageTexture extends Element { + Identifier sprite; + + public ImageTexture(Identifier sprite) { + this.sprite = sprite; + } + + public ImageTexture(SaturnIdentifier sprite) { + this.sprite = (Identifier) sprite.inner; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + if (sprite.toString().endsWith(".svg")) { + MinecraftClient client = MinecraftClient.getInstance(); + + // Get the actual window pixel dimensions for the image + int renderWidth = (int) (width * client.getWindow().getFramebufferWidth() / client.getWindow().getWidth()); + int renderHeight = (int) (height * client.getWindow().getFramebufferHeight() / client.getWindow().getHeight()); + + sprite = SvgTexture.getSvg(client, sprite, renderWidth * 2, renderHeight * 2); + } + + renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height); + } +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/Notification.java b/common/src/main/java/org/saturnclient/ui/elements/Notification.java new file mode 100644 index 0000000..110ff3c --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/Notification.java @@ -0,0 +1,58 @@ +package org.saturnclient.ui.elements; + +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.anim.SlideX; +import org.saturnclient.ui.resources.Fonts; +import org.saturnclient.ui.resources.Textures; + +public class Notification extends Element { + private static Property error = Property.color(0xFFbf212f); + private static Property info = Property.color(0xFF264b96); + private static Property success = Property.color(0xFF27b376); + + public static enum NotificationKind { + Error, + Info, + Success, + } + + NotificationKind kind; + String text; + String title; + + public Notification(int width, int height, NotificationKind kind, String title, String text) { + this.kind = kind; + this.text = text; + this.title = title; + this.width = (int) Math.max(Fonts.getWidth(text, 1) * 0.6f, Fonts.getWidth(title, 1)) + 20; + this.height = 50; + this.x = width - this.width - 15; + this.y = height - this.height - 15; + this.animation = new SlideX(700, -width); + this.duration = 3000; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawRoundedRectangle(0, 0, width, height, 10, Theme.PRIMARY.value); + switch (kind) { + case Error: + renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value); + break; + + case Info: + renderScope.drawTexture(Textures.INFO, 9, 9, 0, 0, 15, 15, info.value); + break; + + case Success: + renderScope.drawTexture(Textures.CHECK, 9, 9, 0, 0, 15, 15, success.value); + break; + } + renderScope.drawText(0.7f, title, 32, 10, 1, -1); + renderScope.drawText(0.5f, text, 32, 28, 1, -1); + } +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/Scroll.java b/common/src/main/java/org/saturnclient/ui/elements/Scroll.java new file mode 100644 index 0000000..47e89a9 --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/Scroll.java @@ -0,0 +1,131 @@ +package org.saturnclient.ui.elements; + +import java.util.ArrayList; +import java.util.List; + +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.components.ElementRenderer; + +public class Scroll extends Element { + private static Property scrollBarRadius = Property.integer(10); + private static Property scrollBarWidth = Property.integer(5); + private static Property scrollBarPadding = Property.integer(5); + + int padding = 0; + + protected List children = new ArrayList<>(); + int scroll = 0; + int maxScroll = 0; + + public Scroll(int padding) { + this.padding = padding; + } + + public void draw(Element element) { + ElementRenderer.draw(children, element); + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + calculateMaxScroll(); + + renderScope.drawRoundedRectangle(0, 0, width, height, + Theme.BG_RADIUS.value, Theme.BACKGROUND.value); + + renderScope.enableScissor(padding, padding, width - padding, height - padding); + renderScope.matrices.push(); + renderScope.matrices.translate(padding, -scroll + padding, 0); + + ElementRenderer.render(children, ctx.elapsed, renderScope, + ctx.mouseX - padding, + ctx.mouseY - padding + scroll); + + renderScope.matrices.pop(); + renderScope.disableScissor(); + + if (maxScroll > 0) { + renderScope.drawRoundedRectangle( + width - scrollBarWidth.value - scrollBarPadding.value, + calculateScrollBarY(), + scrollBarWidth.value, + calculateScrollBarHeight(), + scrollBarRadius.value, + Theme.SCROLL.value); + } + } + + @Override + public void scroll(int mouseX, int mouseY, double horizontalAmount, double verticalAmount) { + scroll -= verticalAmount; + if (scroll < 0) { + scroll = 0; + } else if (scroll > maxScroll) { + scroll = maxScroll; + } + } + + @Override + public Element dimensions(int width, int height) { + super.dimensions(width, height); + + maxScroll = 0; + for (Element element : children) { + maxScroll = Math.max(maxScroll, (element.y + element.height + padding) - (height - padding * 2)); + } + + return this; + } + + int calculateScrollBarHeight() { + if (maxScroll <= 0) + return height - (scrollBarPadding.value * 2); + return Math.max(20, (height * height) / (height + maxScroll + 10)) - (scrollBarPadding.value * 2); + } + + int calculateScrollBarY() { + int scrollBarHeight = calculateScrollBarHeight(); + if (maxScroll <= 0) + return scrollBarPadding.value; + int y = (scroll * (height - scrollBarHeight - scrollBarPadding.value * 2)) / maxScroll + scrollBarPadding.value; + return Math.min(y, height - scrollBarHeight - scrollBarPadding.value); + } + + private void calculateMaxScroll() { + maxScroll = 0; + + for (Element element : children) { + int bottom = element.y + element.height; + maxScroll = Math.max(maxScroll, bottom - height + padding); + } + + if (maxScroll > 0) { + maxScroll = maxScroll + 10; + } else { + maxScroll = 0; + } + } + + @Override + public void keyPressed(int keyCode, int scanCode, int modifiers) { + ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers); + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + scroll, button); + } + + @Override + public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + ElementRenderer.mouseDragged(children, mouseX - padding, mouseY - padding + scroll, button, deltaX, deltaY); + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) { + ElementRenderer.mouseReleased(children, mouseX - padding, mouseY - padding + scroll, button); + } +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/TabMenu.java b/common/src/main/java/org/saturnclient/ui/elements/TabMenu.java new file mode 100644 index 0000000..a8daf13 --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/TabMenu.java @@ -0,0 +1,65 @@ +package org.saturnclient.ui.elements; + +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; +import org.saturnclient.ui.Utils; + +import net.minecraft.util.Identifier; + +public class TabMenu extends Element { + public static class TabMenuComponent { + Runnable onClick; + Identifier sprite; + + public TabMenuComponent(SaturnIdentifier sprite, Runnable onClick) { + this.onClick = onClick; + this.sprite = (Identifier) sprite.inner; + } + } + + TabMenuComponent[] components; + public int selectedScreen = 0; + + public TabMenu(int active, TabMenuComponent... components) { + this.components = components; + width = 25 * components.length + 6; + height = 30; + this.selectedScreen = active; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value); + + int col = 0; + for (TabMenuComponent component : components) { + int x = 25 * col + 5; + + boolean hovering = ctx.isHovering(x, 5, 20, 20) || selectedScreen == col; + + if (hovering) { + renderScope.drawRoundedRectangle(x, 5, 20, 20, Theme.WIDGET_RADIUS.value, Theme.ACCENT.value); + } + + renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10, + hovering ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value); + + col++; + } + } + + @Override + public void click(int mouseX, int mouseY) { + int col = 0; + for (TabMenuComponent component : components) { + int x = 25 * col + 5; + if (Utils.isHovering(mouseX - x, mouseY - 5, 20, 20, 1.0f)) { + component.onClick.run(); + } + col++; + } + } +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/Text.java b/common/src/main/java/org/saturnclient/ui/elements/Text.java new file mode 100644 index 0000000..7de1737 --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/Text.java @@ -0,0 +1,22 @@ +package org.saturnclient.ui.elements; + +import org.saturnclient.config.Theme; +import org.saturnclient.ui.Element; +import org.saturnclient.ui.ElementContext; +import org.saturnclient.ui.RenderScope; +import org.saturnclient.ui.resources.Fonts; + +public class Text extends Element { + private String text; + + public Text(String text) { + this.text = text; + width = Fonts.getWidth(text, Theme.FONT.value); + height = Fonts.getHeight(); + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawText(this.text, 0, 0, Theme.FONT.value, Theme.FOREGROUND.value); + } +} diff --git a/common/src/main/java/org/saturnclient/ui/elements/TextureButton.java b/common/src/main/java/org/saturnclient/ui/elements/TextureButton.java new file mode 100644 index 0000000..23464fd --- /dev/null +++ b/common/src/main/java/org/saturnclient/ui/elements/TextureButton.java @@ -0,0 +1,42 @@ +package org.saturnclient.ui.elements; + +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; + +import net.minecraft.client.render.RenderLayer; +import net.minecraft.util.Identifier; + +public class TextureButton extends Element { + Identifier sprite; + private Runnable onClick; + static int padding = 14; + + public TextureButton(SaturnIdentifier sprite, Runnable onClick) { + this.sprite = (Identifier) sprite.inner; + this.onClick = onClick; + } + + @Override + public void render(RenderScope renderScope, ElementContext ctx) { + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, + ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value); + + renderScope.setRenderLayer(RenderLayer::getGuiTextured); + + int texWidth = width - padding; + int texHeight = height - padding; + + renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight, + ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value); + } + + @Override + public void click(int mouseX, int mouseY) { + if (onClick != null) { + onClick.run(); + } + } +} diff --git a/versions/1.21.4/src/main/java/org/saturnclient/ui/resources/SvgTexture.java b/common/src/main/java/org/saturnclient/ui/resources/SvgTexture.java similarity index 100% rename from versions/1.21.4/src/main/java/org/saturnclient/ui/resources/SvgTexture.java rename to common/src/main/java/org/saturnclient/ui/resources/SvgTexture.java