From a7e3e537b180638beadc0adcdc642935311e99ee Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 18:59:47 +0200 Subject: [PATCH 01/10] Add GUI scale sliders for Saturn's screens The title screen, the menus and the HUD menus (emote wheel, shift menu) each get a scale slider, relative to Minecraft's GUI scale. The HUD editor stays in GUI pixels. Co-Authored-By: Claude Opus 5.5 --- .../java/org/saturnclient/config/Config.java | 12 ++++ .../org/saturnclient/ui/SaturnScreen.java | 60 +++++++++++++++---- .../saturnclient/ui/screens/EmoteWheel.java | 6 ++ .../saturnclient/ui/screens/HudEditor.java | 23 ++++--- .../saturnclient/ui/screens/ShiftMenu.java | 6 ++ .../saturnclient/ui/screens/TitleMenu.java | 6 ++ 6 files changed, 90 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/saturnclient/config/Config.java b/src/main/java/org/saturnclient/config/Config.java index 01a0ed2..4012c9e 100644 --- a/src/main/java/org/saturnclient/config/Config.java +++ b/src/main/java/org/saturnclient/config/Config.java @@ -6,6 +6,7 @@ import org.saturnclient.common.provider.GLFWProvider; import org.saturnclient.common.provider.Providers; import org.saturnclient.common.ref.asset.IdentifierRef; import org.saturnclient.config.property.BoolProperty; +import org.saturnclient.config.property.FloatProperty; import org.saturnclient.config.property.IntProperty; import org.saturnclient.config.property.KeybindingProperty; import org.saturnclient.config.property.Property; @@ -19,8 +20,16 @@ public class Config { public static BoolProperty cloakPhysics = Property.bool(true); public static BoolProperty stagger = Property.bool(true); public static IntProperty backgroundBlur = Property.integer(5).range(0, 10); + // How big Saturn's screens are, relative to Minecraft's GUI scale + public static FloatProperty titleScale = guiScale(); + public static FloatProperty menuScale = guiScale(); + public static FloatProperty hudMenuScale = guiScale(); public static KeybindingProperty openEmoteWheel = Property.keybinding(GLFWProvider.GLFW_KEY_B); + private static FloatProperty guiScale() { + return Property.floatProp(1f).range(0.5f, 2f, 0.05f).unit("×"); + } + public static IdentifierRef getLogo() { return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO; } @@ -35,6 +44,9 @@ public class Config { config.property("Cloak Physics", cloakPhysics, "Let cloaks swing as you move"); config.property("Stagger Animations", stagger, "Animate list items one after another"); config.property("Background blur", backgroundBlur, "How much the game behind Saturn's screens is blurred"); + config.property("Title screen scale", titleScale, "How big the title screen is, relative to Minecraft's GUI scale"); + config.property("Menu scale", menuScale, "How big the mods, settings and cosmetics menus are, relative to Minecraft's GUI scale"); + config.property("HUD menu scale", hudMenuScale, "How big the emote wheel and shift menu are, relative to Minecraft's GUI scale"); // Initialize a sub namespace for theme Theme.init(config); diff --git a/src/main/java/org/saturnclient/ui/SaturnScreen.java b/src/main/java/org/saturnclient/ui/SaturnScreen.java index aea1cfe..14e6b46 100644 --- a/src/main/java/org/saturnclient/ui/SaturnScreen.java +++ b/src/main/java/org/saturnclient/ui/SaturnScreen.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; import org.saturnclient.config.Config; +import org.saturnclient.config.property.FloatProperty; public abstract class SaturnScreen { /** How long the background blur takes to fade in, in ms */ @@ -14,8 +15,10 @@ public abstract class SaturnScreen { public static interface ScreenProvider { public void close(); + /** The screen's width in GUI pixels */ public int getWidth(); + /** The screen's height in GUI pixels */ public int getHeight(); } @@ -23,6 +26,10 @@ public abstract class SaturnScreen { public int width; public int height; public String title; + /** GUI pixels per Saturn unit, from {@link #getScale()} when the screen was built */ + public float scale = 0.5f; + /** Whether a mouse button is held, so the screen isn't rebuilt under a drag */ + private boolean mouseDown = false; // Saturn Screen public List elements = new ArrayList<>(); @@ -51,10 +58,39 @@ public abstract class SaturnScreen { return Math.round(Config.backgroundBlur.value * Math.min(elapsed / (float) BLUR_FADE_IN, 1f)); } + /** + * The GUI scale setting this screen follows; the menu scale unless the + * screen is another kind. + */ + protected FloatProperty getScaleSetting() { + return Config.menuScale; + } + + /** + * GUI pixels per Saturn unit. At 1× a Saturn unit is half a GUI pixel. + * Enlarging stops where the screen would be smaller than 320x240 GUI + * pixels, the least Minecraft's own GUI scale leaves. + */ + public float getScale() { + float fit = Math.min(provider.getWidth() / 320f, provider.getHeight() / 240f); + return 0.5f * Math.min(getScaleSetting().value, Math.max(1f, fit)); + } + + /** + * Rebuilds the screen if its scale setting changed, once no drag is going + * on (the slider being dragged would be replaced). + */ + public void updateScale() { + if (!mouseDown && getScale() != scale) { + init(); + } + } + public void init() { elements.clear(); - width = provider.getWidth(); - height = provider.getHeight(); + scale = getScale(); + width = (int) (provider.getWidth() / scale); + height = (int) (provider.getHeight() / scale); Popup.reset(width, height); ui(); // abstraction to render the saturn ui and also render extra stuff here } @@ -72,7 +108,7 @@ public abstract class SaturnScreen { public void render(RenderScope renderScope, int mouseX, int mouseY, float delta, long elapsed) { renderScope.getMatrixStack().push(); - renderScope.getMatrixStack().scale(0.5f, 0.5f); + renderScope.getMatrixStack().scale(scale, scale); ElementRenderer.render(new ArrayList<>(elements), elapsed, renderScope, mouseX, mouseY); Popup.render(renderScope, elapsed, mouseX, mouseY); @@ -85,8 +121,9 @@ public abstract class SaturnScreen { // -------------- public boolean mouseClicked(double mouseX, double mouseY, int button) { - mouseX *= 2; - mouseY *= 2; + mouseDown = true; + mouseX /= scale; + mouseY /= scale; if (!Popup.mouseClicked(mouseX, mouseY, button)) { ElementRenderer.mouseClicked(elements, mouseX, mouseY, button); @@ -96,8 +133,8 @@ public abstract class SaturnScreen { } public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - mouseX *= 2; - mouseY *= 2; + mouseX /= scale; + mouseY /= scale; if (!Popup.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) { ElementRenderer.mouseDragged(elements, mouseX, mouseY, button, deltaX, deltaY); @@ -107,8 +144,9 @@ public abstract class SaturnScreen { } public boolean mouseReleased(double mouseX, double mouseY, int button) { - mouseX *= 2; - mouseY *= 2; + mouseDown = false; + mouseX /= scale; + mouseY /= scale; // Everything hears the release, so no element is left thinking it's being dragged. Popup.mouseReleased(mouseX, mouseY, button); @@ -122,8 +160,8 @@ public abstract class SaturnScreen { double mouseY, double horizontalAmount, double verticalAmount) { - mouseX *= 2; - mouseY *= 2; + mouseX /= scale; + mouseY /= scale; if (!Popup.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)) { ElementRenderer.mouseScrolled(elements, mouseX, mouseY, horizontalAmount, verticalAmount); diff --git a/src/main/java/org/saturnclient/ui/screens/EmoteWheel.java b/src/main/java/org/saturnclient/ui/screens/EmoteWheel.java index 771fd40..3f6b9c3 100644 --- a/src/main/java/org/saturnclient/ui/screens/EmoteWheel.java +++ b/src/main/java/org/saturnclient/ui/screens/EmoteWheel.java @@ -7,6 +7,7 @@ import org.saturnclient.common.provider.Providers; import org.saturnclient.common.ref.asset.IdentifierRef; import org.saturnclient.config.AnimationConfig; import org.saturnclient.config.Config; +import org.saturnclient.config.property.FloatProperty; import org.saturnclient.config.Theme; import org.saturnclient.cosmetics.Emotes; import org.saturnclient.ui.Element; @@ -43,6 +44,11 @@ public class EmoteWheel extends SaturnScreen { super("Emote Wheel"); } + @Override + protected FloatProperty getScaleSetting() { + return Config.hudMenuScale; + } + @Override public void ui() { List emotes = Emotes.availableEmotes; diff --git a/src/main/java/org/saturnclient/ui/screens/HudEditor.java b/src/main/java/org/saturnclient/ui/screens/HudEditor.java index 8f10f80..363cec8 100644 --- a/src/main/java/org/saturnclient/ui/screens/HudEditor.java +++ b/src/main/java/org/saturnclient/ui/screens/HudEditor.java @@ -18,8 +18,8 @@ import org.saturnclient.ui.resources.Fonts; * Moves and resizes the enabled HUD mods, drawn as they are in game. Drag one * to move it, or drag its corner handle to resize it. * - * Unlike other Saturn screens this works in GUI pixels, like the HUD itself: - * it draws without the half scale and takes the mouse unscaled. + * Unlike other Saturn screens this works in GUI pixels, like the HUD itself, + * whatever Saturn's GUI scale. */ public class HudEditor extends SaturnScreen { private static final String HINT = "Drag to move · Drag the corner to resize · Esc when done"; @@ -45,6 +45,11 @@ public class HudEditor extends SaturnScreen { } } + @Override + public float getScale() { + return 1f; + } + @Override public void ui() { // Placing HUDs needs a clear view of the game. @@ -102,10 +107,7 @@ public class HudEditor extends SaturnScreen { @Override public void render(RenderScope renderScope, int mouseX, int mouseY, float delta, long elapsed) { - // The screen hands over the mouse in Saturn units, twice GUI pixels. - int guiMouseX = mouseX / 2; - int guiMouseY = mouseY / 2; - HudMod hovered = draggingMod != null ? draggingMod : getModAt(guiMouseX, guiMouseY); + HudMod hovered = draggingMod != null ? draggingMod : getModAt(mouseX, mouseY); for (HudMod mod : hudMods) { ModLayout dim = mod.getDimensions(); @@ -149,12 +151,11 @@ public class HudEditor extends SaturnScreen { } private void drawHint(RenderScope renderScope) { - int guiWidth = width / 2; int textWidth = (int) (Fonts.getWidth(HINT, Theme.getBodyFont()) * HINT_SCALE); int textHeight = (int) (Fonts.getHeight() * HINT_SCALE); int pillWidth = textWidth + 20; int pillHeight = textHeight + 10; - int x = (guiWidth - pillWidth) / 2; + int x = (width - pillWidth) / 2; int y = 8; renderScope.drawRoundedRectangle(x, y, pillWidth, pillHeight, pillHeight / 2, 1, Theme.getPanelBg(), @@ -176,13 +177,11 @@ public class HudEditor extends SaturnScreen { } private void keepOnScreen(ModLayout dim) { - int guiWidth = width / 2; - int guiHeight = height / 2; int w = right(dim) - dim.x.value; int h = bottom(dim) - dim.y.value; - dim.x.value = Math.max(0, Math.min(dim.x.value, guiWidth - w)); - dim.y.value = Math.max(0, Math.min(dim.y.value, guiHeight - h)); + dim.x.value = Math.max(0, Math.min(dim.x.value, width - w)); + dim.y.value = Math.max(0, Math.min(dim.y.value, height - h)); } private static int right(ModLayout dim) { diff --git a/src/main/java/org/saturnclient/ui/screens/ShiftMenu.java b/src/main/java/org/saturnclient/ui/screens/ShiftMenu.java index 3f2d512..58b932f 100644 --- a/src/main/java/org/saturnclient/ui/screens/ShiftMenu.java +++ b/src/main/java/org/saturnclient/ui/screens/ShiftMenu.java @@ -3,6 +3,7 @@ package org.saturnclient.ui.screens; import org.saturnclient.common.provider.Providers; import org.saturnclient.config.AnimationConfig; import org.saturnclient.config.Config; +import org.saturnclient.config.property.FloatProperty; import org.saturnclient.ui.SaturnScreen; import org.saturnclient.ui.anim.Fade; import org.saturnclient.ui.anim.SlideY; @@ -24,6 +25,11 @@ public class ShiftMenu extends SaturnScreen { super("Shift Menu"); } + @Override + protected FloatProperty getScaleSetting() { + return Config.hudMenuScale; + } + @Override public void ui() { draw(new ImageTexture(Textures.LOGO_TEXT) diff --git a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java index 3821d69..cc1b450 100644 --- a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java @@ -6,6 +6,7 @@ import org.saturnclient.common.provider.Providers; import org.saturnclient.common.ref.game.MinecraftClientRef.MinecraftScreen; import org.saturnclient.config.AnimationConfig; import org.saturnclient.config.Config; +import org.saturnclient.config.property.FloatProperty; import org.saturnclient.config.Theme; import org.saturnclient.ui.Element; import org.saturnclient.ui.SaturnScreen; @@ -39,6 +40,11 @@ public class TitleMenu extends SaturnScreen { super("Title Menu"); } + @Override + protected FloatProperty getScaleSetting() { + return Config.titleScale; + } + @Override public void ui() { blurBackground = false; From 320b2fd9a56d2b1d8bb011250862bd81f5c5ce58 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 19:09:10 +0200 Subject: [PATCH 02/10] Tighten the title screen around the logo Give the title screen the shift menu's logo and wordmark, bring the menus up under it, and make the dock smaller with a settings item. Co-Authored-By: Claude Opus 5.5 --- .../org/saturnclient/ui/components/Dock.java | 8 +++---- .../saturnclient/ui/screens/TitleMenu.java | 22 ++++++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/saturnclient/ui/components/Dock.java b/src/main/java/org/saturnclient/ui/components/Dock.java index e5d174e..0959a44 100644 --- a/src/main/java/org/saturnclient/ui/components/Dock.java +++ b/src/main/java/org/saturnclient/ui/components/Dock.java @@ -15,12 +15,12 @@ public class Dock extends Element { public record Item(IdentifierRef icon, Runnable onClick) { } - public static final int HEIGHT = 52; + public static final int HEIGHT = 40; - private static final int PADDING = 6; + private static final int PADDING = 5; private static final int ITEM = HEIGHT - PADDING * 2; - private static final int ICON = 18; - private static final int GAP = 4; + private static final int ICON = 15; + private static final int GAP = 3; private final List items; diff --git a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java index cc1b450..6d7e040 100644 --- a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java @@ -27,9 +27,13 @@ import org.saturnclient.ui.resources.Textures; * along the bottom the player, a dock of Saturn's screens and the version. */ public class TitleMenu extends SaturnScreen { - private static final int LOGO = 88; - private static final int WORDMARK_WIDTH = 200; - private static final int WORDMARK_HEIGHT = 20; + // The logo and wordmark as the shift menu has them: the wordmark over the logo's foot + private static final int LOGO = 98; + private static final int WORDMARK_WIDTH = 98; + private static final int WORDMARK_HEIGHT = 10; + private static final int WORDMARK_Y = 88; + /** From the logo's foot to the menus, close so they read as one block */ + private static final int MENUS_GAP = 14; private static final int COLUMN_WIDTH = 280; private static final int BUTTON_HEIGHT = 36; private static final int GAP = 8; @@ -51,16 +55,16 @@ public class TitleMenu extends SaturnScreen { // Logo and menus, centered together a little above the middle int menusHeight = BUTTON_HEIGHT * 3 + GAP * 2; - int blockHeight = LOGO + 16 + WORDMARK_HEIGHT + 36 + menusHeight; + int blockHeight = LOGO + MENUS_GAP + menusHeight; int top = (height - blockHeight) / 2 - 20; draw(new ImageTexture(Config.getLogo()) .dimensions(LOGO, LOGO) .position((width - LOGO) / 2, top) .animation(new SlideY(AnimationConfig.logo, -20))); - draw(new ImageTexture(Textures.LOGO_TEXT_BIG) + draw(new ImageTexture(Textures.LOGO_TEXT) .dimensions(WORDMARK_WIDTH, WORDMARK_HEIGHT) - .position((width - WORDMARK_WIDTH) / 2, top + LOGO + 16) + .position((width - WORDMARK_WIDTH) / 2, top + WORDMARK_Y) .animation(new Fade(AnimationConfig.logo.duration.value))); int rowHeight = BUTTON_HEIGHT + GAP; @@ -80,7 +84,7 @@ public class TitleMenu extends SaturnScreen { () -> Providers.saturn.getClient().scheduleStop()) .dimensions(halfWidth, BUTTON_HEIGHT).position(COLUMN_WIDTH - halfWidth, rowHeight * 2)); - draw(menus.position((width - COLUMN_WIDTH) / 2, top + LOGO + 16 + WORDMARK_HEIGHT + 36)); + draw(menus.position((width - COLUMN_WIDTH) / 2, top + LOGO + MENUS_GAP)); // Along the bottom: the player, Saturn's screens, the version draw(new AccountChip() @@ -92,7 +96,9 @@ public class TitleMenu extends SaturnScreen { new Dock.Item(Textures.ICON_COSMETICS, () -> Providers.saturn.getClient().setScreen(new CosmeticsMenu())), new Dock.Item(Textures.ICON_HUD_EDITOR, - () -> Providers.saturn.getClient().setScreen(new HudEditor())))); + () -> Providers.saturn.getClient().setScreen(new HudEditor())), + new Dock.Item(Textures.ICON_SETTINGS, + () -> Providers.saturn.getClient().setScreen(new ClientSettings())))); draw(dock.position((width - dock.width) / 2, height - EDGE - Dock.HEIGHT) .animation(new SlideFade(AnimationConfig.mainMenu, 12))); From a7ad4586274f34354d025e77aca7b7d24972a1ba Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 19:20:33 +0200 Subject: [PATCH 03/10] Move the title screen's extras to its corners and dock The account chip and a quit button go in the top corners and the version in the bottom left, now smaller and fainter. Minecraft's options and Saturn's settings take the HUD editor's place in the dock, and the intro brings the menus and edges in after the logo. Faded elements now start hidden instead of at 10% opacity. Co-Authored-By: Claude Opus 5.5 --- .../org/saturnclient/ui/ElementRenderer.java | 5 ++ .../java/org/saturnclient/ui/anim/Fade.java | 4 +- .../ui/elements/AnimationStagger.java | 12 +++- .../saturnclient/ui/screens/TitleMenu.java | 71 +++++++++++-------- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/saturnclient/ui/ElementRenderer.java b/src/main/java/org/saturnclient/ui/ElementRenderer.java index ec2cd96..c863d6c 100644 --- a/src/main/java/org/saturnclient/ui/ElementRenderer.java +++ b/src/main/java/org/saturnclient/ui/ElementRenderer.java @@ -26,6 +26,11 @@ public class ElementRenderer { for (Element element : elements) { element.playAnimationFrame(elapsed); + // Not faded in yet + if (element.opacity <= 0) { + continue; + } + renderScope.getMatrixStack().push(); renderScope.setOpacity(element.opacity); renderScope.getMatrixStack().translate(element.x, element.y); diff --git a/src/main/java/org/saturnclient/ui/anim/Fade.java b/src/main/java/org/saturnclient/ui/anim/Fade.java index 9a5ecef..9663e42 100644 --- a/src/main/java/org/saturnclient/ui/anim/Fade.java +++ b/src/main/java/org/saturnclient/ui/anim/Fade.java @@ -16,12 +16,12 @@ public class Fade extends Animation { @Override public void tick(double progress, Element element) { - element.opacity = Math.max(0.1f, targetOpacity * (float) progress); + element.opacity = targetOpacity * (float) progress; } @Override public void init(Element element) { targetOpacity = element.opacity; - element.opacity = 0.1f; + element.opacity = 0; } } diff --git a/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java b/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java index 80dba99..c27c897 100644 --- a/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java +++ b/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java @@ -13,6 +13,8 @@ import org.saturnclient.ui.RenderScope; public class AnimationStagger extends Element { protected List children = new ArrayList<>(); public int delay; + /** When the first child starts, in ms */ + public int start = 0; public AnimationStagger(int delay) { this.delay = delay; @@ -22,13 +24,21 @@ public class AnimationStagger extends Element { this.delay = config.stagger.value; } + /** + * Holds the children back until {@code start} ms after the screen opens. + */ + public AnimationStagger after(int start) { + this.start = start; + return this; + } + public void draw(Element element) { if (!Config.stagger.value) { element.animation = null; } if (element.animation != null) { - element.animation.delay = delay * children.size(); + element.animation.delay = start + delay * children.size(); } ElementRenderer.draw(children, element); diff --git a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java index 6d7e040..86b4caf 100644 --- a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java @@ -10,6 +10,7 @@ import org.saturnclient.config.property.FloatProperty; import org.saturnclient.config.Theme; import org.saturnclient.ui.Element; import org.saturnclient.ui.SaturnScreen; +import org.saturnclient.ui.anim.Animation; import org.saturnclient.ui.anim.Fade; import org.saturnclient.ui.anim.SlideFade; import org.saturnclient.ui.anim.SlideY; @@ -17,14 +18,16 @@ import org.saturnclient.ui.components.AccountChip; import org.saturnclient.ui.components.Dock; import org.saturnclient.ui.elements.AnimationStagger; import org.saturnclient.ui.elements.Button; +import org.saturnclient.ui.elements.IconButton; import org.saturnclient.ui.elements.ImageTexture; import org.saturnclient.ui.elements.Label; import org.saturnclient.ui.resources.Fonts; import org.saturnclient.ui.resources.Textures; /** - * Saturn's title screen: the logo over the game's menus in the middle, and - * along the bottom the player, a dock of Saturn's screens and the version. + * Saturn's title screen: the logo over the game's menus in the middle, the + * player and a quit button in the top corners, and along the bottom a dock of + * Saturn's screens and Minecraft's options, and the version. */ public class TitleMenu extends SaturnScreen { // The logo and wordmark as the shift menu has them: the wordmark over the logo's foot @@ -37,8 +40,16 @@ public class TitleMenu extends SaturnScreen { private static final int COLUMN_WIDTH = 280; private static final int BUTTON_HEIGHT = 36; private static final int GAP = 8; - private static final int EDGE = 28; - private static final float VERSION_SCALE = 0.62f; + /** Space around the account chip, quit button, dock and version, from the screen's edges */ + private static final int EDGE = 14; + private static final int QUIT_SIZE = 32; + + // The intro, in ms: the logo, then the menus, then what's around the edges + private static final int MENUS_DELAY = 250; + private static final int EDGES_DELAY = 450; + private static final int EDGES_STAGGER = 60; + private static final float VERSION_SCALE = 0.5f; + private static final float VERSION_ALPHA = 0.6f; public TitleMenu() { super("Title Menu"); @@ -54,7 +65,7 @@ public class TitleMenu extends SaturnScreen { blurBackground = false; // Logo and menus, centered together a little above the middle - int menusHeight = BUTTON_HEIGHT * 3 + GAP * 2; + int menusHeight = BUTTON_HEIGHT * 2 + GAP; int blockHeight = LOGO + MENUS_GAP + menusHeight; int top = (height - blockHeight) / 2 - 20; @@ -67,57 +78,57 @@ public class TitleMenu extends SaturnScreen { .position((width - WORDMARK_WIDTH) / 2, top + WORDMARK_Y) .animation(new Fade(AnimationConfig.logo.duration.value))); - int rowHeight = BUTTON_HEIGHT + GAP; - int halfWidth = (COLUMN_WIDTH - GAP) / 2; - AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu); + AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu).after(MENUS_DELAY); addMenuButton(menus, new Button("Singleplayer", () -> Providers.saturn.getClient().setScreen(MinecraftScreen.SelectWorld)) .dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, 0)); addMenuButton(menus, new Button("Multiplayer", () -> Providers.saturn.getClient().setScreen(MinecraftScreen.Multiplayer)) - .dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, rowHeight)); - addMenuButton(menus, new Button("Options", - () -> Providers.saturn.getClient().setScreen(MinecraftScreen.Options)) - .dimensions(halfWidth, BUTTON_HEIGHT).position(0, rowHeight * 2)); - addMenuButton(menus, new Button("Quit", - () -> Providers.saturn.getClient().scheduleStop()) - .dimensions(halfWidth, BUTTON_HEIGHT).position(COLUMN_WIDTH - halfWidth, rowHeight * 2)); + .dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, BUTTON_HEIGHT + GAP)); draw(menus.position((width - COLUMN_WIDTH) / 2, top + LOGO + MENUS_GAP)); - // Along the bottom: the player, Saturn's screens, the version + // Top corners: the player, and quitting the game draw(new AccountChip() - .position(EDGE, height - EDGE - AccountChip.HEIGHT) - .animation(new Fade(AnimationConfig.mainMenu))); + .position(EDGE, EDGE) + .animation(delayed(new SlideFade(AnimationConfig.mainMenu, 8), EDGES_DELAY))); + draw(new IconButton(Textures.ICON_CLOSE, () -> Providers.saturn.getClient().scheduleStop()) + .dimensions(QUIT_SIZE, QUIT_SIZE) + .position(width - EDGE - QUIT_SIZE, EDGE + (AccountChip.HEIGHT - QUIT_SIZE) / 2) + .animation(delayed(new SlideFade(AnimationConfig.mainMenu, 8), EDGES_DELAY))); + // Along the bottom: the dock of Saturn's screens and Minecraft's options, and the version Dock dock = new Dock(List.of( new Dock.Item(Textures.ICON_MODS, () -> Providers.saturn.getClient().setScreen(new ModMenu())), new Dock.Item(Textures.ICON_COSMETICS, () -> Providers.saturn.getClient().setScreen(new CosmeticsMenu())), - new Dock.Item(Textures.ICON_HUD_EDITOR, - () -> Providers.saturn.getClient().setScreen(new HudEditor())), + new Dock.Item(Textures.LOGO, () -> Providers.saturn.getClient().setScreen(new ClientSettings())), new Dock.Item(Textures.ICON_SETTINGS, - () -> Providers.saturn.getClient().setScreen(new ClientSettings())))); + () -> Providers.saturn.getClient().setScreen(MinecraftScreen.Options)))); draw(dock.position((width - dock.width) / 2, height - EDGE - Dock.HEIGHT) - .animation(new SlideFade(AnimationConfig.mainMenu, 12))); + .animation(delayed(new SlideFade(AnimationConfig.mainMenu, -12), EDGES_DELAY + EDGES_STAGGER))); String version = getVersionText(); - int versionWidth = (int) (Fonts.getWidth(version, Theme.getBodyFont()) * VERSION_SCALE); int versionHeight = (int) (Fonts.getHeight() * VERSION_SCALE); - draw(new Label(() -> version, VERSION_SCALE, Theme::getBodyFont, Theme::getMutedFg) - .position(width - EDGE - versionWidth, height - EDGE - (AccountChip.HEIGHT + versionHeight) / 2) - .animation(new Fade(AnimationConfig.mainMenu))); + draw(new Label(() -> version, VERSION_SCALE, Theme::getBodyFont, + () -> Theme.withAlpha(VERSION_ALPHA, Theme.getMutedFg())) + .position(EDGE, height - EDGE - versionHeight) + .animation(delayed(new Fade(AnimationConfig.mainMenu), EDGES_DELAY + EDGES_STAGGER * 2))); + } + + private static Animation delayed(Animation animation, int delay) { + animation.delay = delay; + return animation; } /** - * "Saturn Client 0.1.0 · Minecraft 1.21.11", from the mod version - * "0.1.0+1.21.11". + * "Saturn 0.1.0 · 1.21.11", from the mod version "0.1.0+1.21.11". */ private static String getVersionText() { String[] version = Providers.saturn.getModVersion().split("\\+", 2); - String text = "Saturn Client " + version[0]; - return version.length > 1 ? text + " · Minecraft " + version[1] : text; + String text = "Saturn " + version[0]; + return version.length > 1 ? text + " · " + version[1] : text; } private static void addMenuButton(AnimationStagger stagger, Element button) { From adedd688a798d7afe1e0c248e9163399eb9ca524 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 19:39:39 +0200 Subject: [PATCH 04/10] Bring the title screen's intro and corners into line The intro now comes in in reading order, one Main Menu stagger apart. The account chip is as tall as the quit button, with a smaller head and name, both sitting a little tighter in the top corners, and the Saturn dock item opens the shift menu. Co-Authored-By: Claude Opus 5.5 --- .../ui/components/AccountChip.java | 18 ++++--- .../saturnclient/ui/screens/TitleMenu.java | 54 ++++++++++++------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/saturnclient/ui/components/AccountChip.java b/src/main/java/org/saturnclient/ui/components/AccountChip.java index 554f9c6..fa5a49d 100644 --- a/src/main/java/org/saturnclient/ui/components/AccountChip.java +++ b/src/main/java/org/saturnclient/ui/components/AccountChip.java @@ -15,12 +15,13 @@ import org.saturnclient.ui.resources.Fonts; * while connected to Saturn. */ public class AccountChip extends Element { - public static final int HEIGHT = 40; + public static final int HEIGHT = 32; - private static final int HEAD = 28; - private static final int PADDING = 6; - private static final int DOT = 7; - private static final float NAME_SCALE = 0.7f; + private static final int HEAD = 18; + private static final int PADDING = 7; + private static final int GAP = 8; + private static final int DOT = 6; + private static final float NAME_SCALE = 0.55f; private static final int CONNECTED = 0xFF7FD1A8; private final String name; @@ -29,7 +30,8 @@ public class AccountChip extends Element { String username = Providers.saturn.getClient().getUsername(); this.name = username != null ? username : "Player"; this.height = HEIGHT; - this.width = PADDING + HEAD + 10 + (int) (Fonts.getWidth(name, Theme.FONT.value) * NAME_SCALE) + 10 + DOT + 12; + this.width = PADDING + HEAD + GAP + (int) (Fonts.getWidth(name, Theme.FONT.value) * NAME_SCALE) + GAP + DOT + + GAP + 2; } @Override @@ -43,11 +45,11 @@ public class AccountChip extends Element { renderScope.drawTexture(skin, PADDING, headY, 8, 8, HEAD, HEAD, 8, 8, 64, 64); renderScope.drawTexture(skin, PADDING, headY, 40, 8, HEAD, HEAD, 8, 8, 64, 64); - int nameX = PADDING + HEAD + 10; + int nameX = PADDING + HEAD + GAP; renderScope.drawText(NAME_SCALE, name, nameX, Field.textY(height, NAME_SCALE), Theme.FONT.value, Theme.FOREGROUND.value); - int dotX = nameX + (int) (Fonts.getWidth(name, Theme.FONT.value) * NAME_SCALE) + 10; + int dotX = nameX + (int) (Fonts.getWidth(name, Theme.FONT.value) * NAME_SCALE) + GAP; renderScope.drawRoundedRectangle(dotX, (height - DOT) / 2, DOT, DOT, DOT / 2, ServiceClient.isConnected() ? CONNECTED : Theme.withAlpha(0.3f, Theme.FOREGROUND.value)); } diff --git a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java index 86b4caf..7143339 100644 --- a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java @@ -27,7 +27,7 @@ import org.saturnclient.ui.resources.Textures; /** * Saturn's title screen: the logo over the game's menus in the middle, the * player and a quit button in the top corners, and along the bottom a dock of - * Saturn's screens and Minecraft's options, and the version. + * Saturn's screens, the shift menu and Minecraft's options, and the version. */ public class TitleMenu extends SaturnScreen { // The logo and wordmark as the shift menu has them: the wordmark over the logo's foot @@ -42,12 +42,19 @@ public class TitleMenu extends SaturnScreen { private static final int GAP = 8; /** Space around the account chip, quit button, dock and version, from the screen's edges */ private static final int EDGE = 14; - private static final int QUIT_SIZE = 32; + /** The top corners sit a little tighter */ + private static final int TOP_EDGE = 10; + /** As tall as the account chip beside it */ + private static final int QUIT_SIZE = AccountChip.HEIGHT; - // The intro, in ms: the logo, then the menus, then what's around the edges - private static final int MENUS_DELAY = 250; - private static final int EDGES_DELAY = 450; - private static final int EDGES_STAGGER = 60; + // The intro comes in in reading order, left to right and top to bottom, one step apart + private static final int STEP_ACCOUNT = 0; + private static final int STEP_QUIT = 1; + private static final int STEP_LOGO = 2; + private static final int STEP_MENUS = 3; + /** After the two menus */ + private static final int STEP_VERSION = 5; + private static final int STEP_DOCK = 6; private static final float VERSION_SCALE = 0.5f; private static final float VERSION_ALPHA = 0.6f; @@ -72,13 +79,14 @@ public class TitleMenu extends SaturnScreen { draw(new ImageTexture(Config.getLogo()) .dimensions(LOGO, LOGO) .position((width - LOGO) / 2, top) - .animation(new SlideY(AnimationConfig.logo, -20))); + .animation(step(new SlideY(AnimationConfig.logo, -20), STEP_LOGO))); draw(new ImageTexture(Textures.LOGO_TEXT) .dimensions(WORDMARK_WIDTH, WORDMARK_HEIGHT) .position((width - WORDMARK_WIDTH) / 2, top + WORDMARK_Y) - .animation(new Fade(AnimationConfig.logo.duration.value))); + .animation(step(new Fade(AnimationConfig.logo.duration.value), STEP_LOGO))); - AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu).after(MENUS_DELAY); + AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu) + .after(stepDelay(STEP_MENUS)); addMenuButton(menus, new Button("Singleplayer", () -> Providers.saturn.getClient().setScreen(MinecraftScreen.SelectWorld)) @@ -91,34 +99,42 @@ public class TitleMenu extends SaturnScreen { // Top corners: the player, and quitting the game draw(new AccountChip() - .position(EDGE, EDGE) - .animation(delayed(new SlideFade(AnimationConfig.mainMenu, 8), EDGES_DELAY))); + .position(TOP_EDGE, TOP_EDGE) + .animation(step(new SlideFade(AnimationConfig.mainMenu, 8), STEP_ACCOUNT))); draw(new IconButton(Textures.ICON_CLOSE, () -> Providers.saturn.getClient().scheduleStop()) .dimensions(QUIT_SIZE, QUIT_SIZE) - .position(width - EDGE - QUIT_SIZE, EDGE + (AccountChip.HEIGHT - QUIT_SIZE) / 2) - .animation(delayed(new SlideFade(AnimationConfig.mainMenu, 8), EDGES_DELAY))); + .position(width - TOP_EDGE - QUIT_SIZE, TOP_EDGE) + .animation(step(new SlideFade(AnimationConfig.mainMenu, 8), STEP_QUIT))); - // Along the bottom: the dock of Saturn's screens and Minecraft's options, and the version + // Along the bottom: the dock of Saturn's screens, the shift menu and Minecraft's options, and the version Dock dock = new Dock(List.of( new Dock.Item(Textures.ICON_MODS, () -> Providers.saturn.getClient().setScreen(new ModMenu())), new Dock.Item(Textures.ICON_COSMETICS, () -> Providers.saturn.getClient().setScreen(new CosmeticsMenu())), - new Dock.Item(Textures.LOGO, () -> Providers.saturn.getClient().setScreen(new ClientSettings())), + new Dock.Item(Textures.LOGO, () -> Providers.saturn.getClient().setScreen(new ShiftMenu())), new Dock.Item(Textures.ICON_SETTINGS, () -> Providers.saturn.getClient().setScreen(MinecraftScreen.Options)))); draw(dock.position((width - dock.width) / 2, height - EDGE - Dock.HEIGHT) - .animation(delayed(new SlideFade(AnimationConfig.mainMenu, -12), EDGES_DELAY + EDGES_STAGGER))); + .animation(step(new SlideFade(AnimationConfig.mainMenu, -12), STEP_DOCK))); String version = getVersionText(); int versionHeight = (int) (Fonts.getHeight() * VERSION_SCALE); draw(new Label(() -> version, VERSION_SCALE, Theme::getBodyFont, () -> Theme.withAlpha(VERSION_ALPHA, Theme.getMutedFg())) .position(EDGE, height - EDGE - versionHeight) - .animation(delayed(new Fade(AnimationConfig.mainMenu), EDGES_DELAY + EDGES_STAGGER * 2))); + .animation(step(new Fade(AnimationConfig.mainMenu), STEP_VERSION))); } - private static Animation delayed(Animation animation, int delay) { - animation.delay = delay; + /** + * When the intro's {@code step} starts, in ms: the Main Menu stagger apart, + * or all at once when staggering is off. + */ + private static int stepDelay(int step) { + return Config.stagger.value ? step * AnimationConfig.mainMenu.stagger.value : 0; + } + + private static Animation step(Animation animation, int step) { + animation.delay = stepDelay(step); return animation; } From 3436054112a3c71b840f0257dfa4424aa5b4d131 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 19:45:04 +0200 Subject: [PATCH 05/10] Draw the color picker from gradient textures The picker drew its square and bars one pixel at a time, about 10,000 fills a frame. It now stretches four gradient strips, made once, over them instead. Co-Authored-By: Claude Opus 5.5 --- .../ui/components/inputs/ColorPicker.java | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/saturnclient/ui/components/inputs/ColorPicker.java b/src/main/java/org/saturnclient/ui/components/inputs/ColorPicker.java index 8c7f701..84e4fa0 100644 --- a/src/main/java/org/saturnclient/ui/components/inputs/ColorPicker.java +++ b/src/main/java/org/saturnclient/ui/components/inputs/ColorPicker.java @@ -1,7 +1,10 @@ package org.saturnclient.ui.components.inputs; import java.awt.Color; +import java.awt.image.BufferedImage; +import org.saturnclient.common.provider.Providers; +import org.saturnclient.common.ref.asset.IdentifierRef; import org.saturnclient.config.Theme; import org.saturnclient.config.property.ColorProperty; import org.saturnclient.ui.Element; @@ -27,6 +30,18 @@ public class ColorPicker extends Element { private static final int SQUARE_Y = OPACITY_Y + SLIDER_HEIGHT + GAP; private static final int HUE_Y = SQUARE_Y + SIZE + GAP; + // Gradient strips, made once and stretched over each part, so a frame is a + // few draws instead of one per pixel. Tinting the white ones colors them. + private static final int STRIP = 256; + /** White fading out to the right: saturation, over the hue */ + private static final IdentifierRef SATURATION = IdentifierRef.ofSaturn("textures/gui/color_picker/saturation"); + /** Black fading in downwards: brightness, over the saturation */ + private static final IdentifierRef BRIGHTNESS = IdentifierRef.ofSaturn("textures/gui/color_picker/brightness"); + /** White fading in to the right, tinted with the color */ + private static final IdentifierRef OPACITY = IdentifierRef.ofSaturn("textures/gui/color_picker/opacity"); + private static final IdentifierRef HUE = IdentifierRef.ofSaturn("textures/gui/color_picker/hue"); + private static boolean stripsRegistered = false; + private final ColorProperty prop; private float hue; @@ -46,6 +61,8 @@ public class ColorPicker extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { + registerStrips(); + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, Theme.withAlpha(0.97f, Theme.BACKGROUND.value), Theme.withAlpha(0.14f, Theme.FOREGROUND.value)); @@ -55,22 +72,16 @@ public class ColorPicker extends Element { } private void renderOpacity(RenderScope renderScope) { - int rgb = prop.value & 0xFFFFFF; - for (int x = 0; x < SIZE; x++) { - int alpha = (int) (x / (float) SIZE * 255) & 0xFF; - renderScope.fill(PADDING + x, OPACITY_Y, PADDING + x + 1, OPACITY_Y + SLIDER_HEIGHT, (alpha << 24) | rgb); - } + renderScope.drawTexture(OPACITY, PADDING, OPACITY_Y, 0, 0, SIZE, SLIDER_HEIGHT, + 0xFF000000 | prop.value); drawMarker(renderScope, PADDING + (int) (opacity * SIZE), OPACITY_Y); } private void renderSquare(RenderScope renderScope) { - for (int x = 0; x < SIZE; x++) { - for (int y = 0; y < SIZE; y++) { - int color = Color.HSBtoRGB(hue, x / (float) SIZE, 1f - y / (float) SIZE); - renderScope.fill(PADDING + x, SQUARE_Y + y, PADDING + x + 1, SQUARE_Y + y + 1, 0xFF000000 | color); - } - } + renderScope.fill(PADDING, SQUARE_Y, PADDING + SIZE, SQUARE_Y + SIZE, 0xFF000000 | Color.HSBtoRGB(hue, 1f, 1f)); + renderScope.drawTexture(SATURATION, PADDING, SQUARE_Y, 0, 0, SIZE, SIZE, 0xFFFFFFFF); + renderScope.drawTexture(BRIGHTNESS, PADDING, SQUARE_Y, 0, 0, SIZE, SIZE, 0xFFFFFFFF); int selX = PADDING + (int) (saturation * SIZE) - 4; int selY = SQUARE_Y + (int) ((1f - brightness) * SIZE) - 4; @@ -79,14 +90,36 @@ public class ColorPicker extends Element { } private void renderHue(RenderScope renderScope) { - for (int x = 0; x < SIZE; x++) { - int color = Color.HSBtoRGB(x / (float) SIZE, 1f, 1f); - renderScope.fill(PADDING + x, HUE_Y, PADDING + x + 1, HUE_Y + SLIDER_HEIGHT, 0xFF000000 | color); - } + renderScope.drawTexture(HUE, PADDING, HUE_Y, 0, 0, SIZE, SLIDER_HEIGHT, 0xFFFFFFFF); drawMarker(renderScope, PADDING + (int) (hue * SIZE), HUE_Y); } + private static void registerStrips() { + if (stripsRegistered) { + return; + } + + BufferedImage saturation = new BufferedImage(STRIP, 1, BufferedImage.TYPE_INT_ARGB); + BufferedImage brightness = new BufferedImage(1, STRIP, BufferedImage.TYPE_INT_ARGB); + BufferedImage opacity = new BufferedImage(STRIP, 1, BufferedImage.TYPE_INT_ARGB); + BufferedImage hue = new BufferedImage(STRIP, 1, BufferedImage.TYPE_INT_ARGB); + + for (int i = 0; i < STRIP; i++) { + int alpha = i * 255 / (STRIP - 1); + saturation.setRGB(i, 0, ((255 - alpha) << 24) | 0xFFFFFF); + brightness.setRGB(0, i, alpha << 24); + opacity.setRGB(i, 0, (alpha << 24) | 0xFFFFFF); + hue.setRGB(i, 0, 0xFF000000 | Color.HSBtoRGB(i / (float) STRIP, 1f, 1f)); + } + + Providers.saturn.registerBufferedImageTexture(SATURATION, saturation); + Providers.saturn.registerBufferedImageTexture(BRIGHTNESS, brightness); + Providers.saturn.registerBufferedImageTexture(OPACITY, opacity); + Providers.saturn.registerBufferedImageTexture(HUE, hue); + stripsRegistered = true; + } + private static void drawMarker(RenderScope renderScope, int x, int sliderY) { renderScope.fill(x - 2, sliderY - 2, x + 2, sliderY + SLIDER_HEIGHT + 2, 0xFFFFFFFF); renderScope.fill(x - 1, sliderY - 1, x + 1, sliderY + SLIDER_HEIGHT + 1, 0xFF000000); From 5c0912f0cf6f09e5776f330e559ddd8e38370f47 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 19:56:24 +0200 Subject: [PATCH 06/10] Use the background and primary colors' own alpha Panels, buttons, cards and tiles multiplied the theme's background and primary colors by a fixed alpha, so they were translucent however the colors were set. They now use the colors as they are, and the defaults and presets carry the old translucency in their alpha instead. Popups stay nearly solid so they're readable over panels. Co-Authored-By: Claude Opus 5.5 --- .../java/org/saturnclient/config/Theme.java | 29 ++++++++++++------- .../ui/components/CosmeticTile.java | 2 +- .../saturnclient/ui/components/EmoteTile.java | 2 +- .../ui/components/SaturnModule.java | 2 +- .../ui/components/ThemePresetCard.java | 2 +- .../ui/components/inputs/ColorPicker.java | 2 +- .../ui/components/inputs/CurvePreview.java | 2 +- .../ui/components/inputs/OptionList.java | 2 +- .../org/saturnclient/ui/elements/Button.java | 2 +- 9 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/config/Theme.java index b3f0d41..a0b0eaf 100644 --- a/src/main/java/org/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/config/Theme.java @@ -10,14 +10,14 @@ import org.saturnclient.config.property.SelectProperty; public class Theme { public static ConfigManager config; - // Main UI background (slightly warm dark to match Saturn tone) - public static final ColorProperty BACKGROUND = Property.color(0xFF12100D); + // Main UI background (slightly warm dark to match Saturn tone), translucent over the world + public static final ColorProperty BACKGROUND = Property.color(0xD112100D); // Default text color public static final ColorProperty FOREGROUND = Property.color(0xFFF2E6D2); - // Widget default background (normal state) - public static final ColorProperty PRIMARY = Property.color(0xFF1C1915); + // Widget default background (normal state), translucent over the world + public static final ColorProperty PRIMARY = Property.color(0xB81C1915); // Widget text color public static final ColorProperty PRIMARY_FG = Property.color(0xFFEAD9B8); @@ -62,10 +62,10 @@ public class Theme { } public static final List PRESETS = List.of( - new Preset("Saturn", 0xFF12100D, 0xFFF2E6D2, 0xFF1C1915, 0xFFEAD9B8, 0xFFE2BF7D, 0xFF1A140A, 0xFFE2BF7D), - new Preset("Midnight", 0xFF0E1117, 0xFFE4EAF5, 0xFF171C26, 0xFFC9D4E8, 0xFF8FB3FF, 0xFF0A1224, 0xFF8FB3FF), - new Preset("Amethyst", 0xFF120F17, 0xFFEDE4F7, 0xFF1C1724, 0xFFD8C9EB, 0xFFC79BFF, 0xFF1A0F2A, 0xFFC79BFF), - new Preset("Emerald", 0xFF0D1210, 0xFFE2F2EA, 0xFF16201B, 0xFFC4E0D2, 0xFF7FD1A8, 0xFF0A1F15, 0xFF7FD1A8)); + new Preset("Saturn", 0xD112100D, 0xFFF2E6D2, 0xB81C1915, 0xFFEAD9B8, 0xFFE2BF7D, 0xFF1A140A, 0xFFE2BF7D), + new Preset("Midnight", 0xD10E1117, 0xFFE4EAF5, 0xB8171C26, 0xFFC9D4E8, 0xFF8FB3FF, 0xFF0A1224, 0xFF8FB3FF), + new Preset("Amethyst", 0xD1120F17, 0xFFEDE4F7, 0xB81C1724, 0xFFD8C9EB, 0xFFC79BFF, 0xFF1A0F2A, 0xFFC79BFF), + new Preset("Emerald", 0xD10D1210, 0xFFE2F2EA, 0xB816201B, 0xFFC4E0D2, 0xFF7FD1A8, 0xFF0A1F15, 0xFF7FD1A8)); public static void init(ConfigManager parent) { config = new ConfigManager(parent, "UI Style"); @@ -98,10 +98,19 @@ public class Theme { } /** - * Translucent background for panels drawn over the world + * Background for panels drawn over the world, as translucent as the + * background color is */ public static int getPanelBg() { - return withAlpha(0.82f, BACKGROUND.value); + return BACKGROUND.value; + } + + /** + * Background for popups, which sit over other panels: the background color, + * kept nearly solid so what's under it doesn't show through + */ + public static int getPopupBg() { + return withAlpha(0.97f, BACKGROUND.value | 0xFF000000); } /** diff --git a/src/main/java/org/saturnclient/ui/components/CosmeticTile.java b/src/main/java/org/saturnclient/ui/components/CosmeticTile.java index f0cbfe5..51979ff 100644 --- a/src/main/java/org/saturnclient/ui/components/CosmeticTile.java +++ b/src/main/java/org/saturnclient/ui/components/CosmeticTile.java @@ -60,7 +60,7 @@ public class CosmeticTile extends Element { int border = isEquipped ? Theme.ACCENT.value : Theme.withAlpha(hovered ? 0.22f : 0.06f, Theme.FOREGROUND.value); renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, isEquipped ? 2 : 1, - Theme.withAlpha(0.72f, Theme.PRIMARY.value), border); + Theme.PRIMARY.value, border); // Preview, fitted into its area int areaWidth = width - INSET * 2; diff --git a/src/main/java/org/saturnclient/ui/components/EmoteTile.java b/src/main/java/org/saturnclient/ui/components/EmoteTile.java index ede8058..b0a0170 100644 --- a/src/main/java/org/saturnclient/ui/components/EmoteTile.java +++ b/src/main/java/org/saturnclient/ui/components/EmoteTile.java @@ -41,7 +41,7 @@ public class EmoteTile extends Element { boolean hovered = ctx.isHovering(); renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, hovered ? 2 : 1, - Theme.withAlpha(0.72f, Theme.PRIMARY.value), + Theme.PRIMARY.value, hovered ? Theme.ACCENT.value : Theme.withAlpha(0.06f, Theme.FOREGROUND.value)); int imageSize = height - INSET * 2 - NAME_AREA; diff --git a/src/main/java/org/saturnclient/ui/components/SaturnModule.java b/src/main/java/org/saturnclient/ui/components/SaturnModule.java index 280df8e..3ef694c 100644 --- a/src/main/java/org/saturnclient/ui/components/SaturnModule.java +++ b/src/main/java/org/saturnclient/ui/components/SaturnModule.java @@ -46,7 +46,7 @@ public class SaturnModule extends Element { // Card renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, - Theme.withAlpha(0.72f, Theme.PRIMARY.value), border); + Theme.PRIMARY.value, border); if (enabled) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, diff --git a/src/main/java/org/saturnclient/ui/components/ThemePresetCard.java b/src/main/java/org/saturnclient/ui/components/ThemePresetCard.java index 108f7ec..0da52e6 100644 --- a/src/main/java/org/saturnclient/ui/components/ThemePresetCard.java +++ b/src/main/java/org/saturnclient/ui/components/ThemePresetCard.java @@ -38,7 +38,7 @@ public class ThemePresetCard extends Element { : Theme.withAlpha(ctx.isHovering() ? 0.22f : 0.06f, Theme.FOREGROUND.value); renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, active ? 2 : 1, - Theme.withAlpha(0.72f, Theme.PRIMARY.value), border); + Theme.PRIMARY.value, border); // A panel in the preset's colors: a line of text over an accent and a widget int previewWidth = width - PADDING * 2; diff --git a/src/main/java/org/saturnclient/ui/components/inputs/ColorPicker.java b/src/main/java/org/saturnclient/ui/components/inputs/ColorPicker.java index 84e4fa0..2247f5b 100644 --- a/src/main/java/org/saturnclient/ui/components/inputs/ColorPicker.java +++ b/src/main/java/org/saturnclient/ui/components/inputs/ColorPicker.java @@ -64,7 +64,7 @@ public class ColorPicker extends Element { registerStrips(); renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, - Theme.withAlpha(0.97f, Theme.BACKGROUND.value), Theme.withAlpha(0.14f, Theme.FOREGROUND.value)); + Theme.getPopupBg(), Theme.withAlpha(0.14f, Theme.FOREGROUND.value)); renderOpacity(renderScope); renderSquare(renderScope); diff --git a/src/main/java/org/saturnclient/ui/components/inputs/CurvePreview.java b/src/main/java/org/saturnclient/ui/components/inputs/CurvePreview.java index a5f2b45..8f8933b 100644 --- a/src/main/java/org/saturnclient/ui/components/inputs/CurvePreview.java +++ b/src/main/java/org/saturnclient/ui/components/inputs/CurvePreview.java @@ -38,7 +38,7 @@ public class CurvePreview extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, - Theme.withAlpha(0.97f, Theme.BACKGROUND.value), Theme.withAlpha(0.14f, Theme.FOREGROUND.value)); + Theme.getPopupBg(), Theme.withAlpha(0.14f, Theme.FOREGROUND.value)); renderScope.drawText(TITLE_SCALE, "PREVIEW", PADDING, PADDING - 2, Theme.FONT.value, Theme.getMutedFg()); diff --git a/src/main/java/org/saturnclient/ui/components/inputs/OptionList.java b/src/main/java/org/saturnclient/ui/components/inputs/OptionList.java index 83a73d7..9fd6004 100644 --- a/src/main/java/org/saturnclient/ui/components/inputs/OptionList.java +++ b/src/main/java/org/saturnclient/ui/components/inputs/OptionList.java @@ -45,7 +45,7 @@ public class OptionList extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, - Theme.withAlpha(0.97f, Theme.BACKGROUND.value), Theme.withAlpha(0.14f, Theme.FOREGROUND.value)); + Theme.getPopupBg(), Theme.withAlpha(0.14f, Theme.FOREGROUND.value)); int visible = Math.min(options.length, MAX_VISIBLE); hovered = getIndexAt(ctx.mouseX, ctx.mouseY); diff --git a/src/main/java/org/saturnclient/ui/elements/Button.java b/src/main/java/org/saturnclient/ui/elements/Button.java index e884242..30615fd 100644 --- a/src/main/java/org/saturnclient/ui/elements/Button.java +++ b/src/main/java/org/saturnclient/ui/elements/Button.java @@ -39,7 +39,7 @@ public class Button extends Element { */ static void drawSurface(RenderScope renderScope, int width, int height, boolean hovered) { renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, - hovered ? Theme.withAlpha(0.9f, Theme.PRIMARY.value) : Theme.getPanelBg(), + hovered ? Theme.PRIMARY.value : Theme.getPanelBg(), Theme.withAlpha(hovered ? 0.25f : 0.1f, Theme.FOREGROUND.value)); } From 4d9c629eaf4f5d315d772d7e07336ff237369390 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 20:43:44 +0200 Subject: [PATCH 07/10] Frost translucent panels with glass Panels whose background isn't solid draw the blurred game behind them, cut to the same rounded rectangle texture as the background, where the version supports glass. Panel screens then leave the rest of the game unblurred. Co-Authored-By: Claude Opus 5.5 --- .../common/provider/SaturnProvider.java | 6 +++ .../java/org/saturnclient/ui/PanelScreen.java | 4 ++ .../java/org/saturnclient/ui/RenderScope.java | 40 +++++++++++++++++-- .../org/saturnclient/ui/SaturnScreen.java | 16 ++++++-- .../org/saturnclient/ui/elements/Panel.java | 8 +++- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/saturnclient/common/provider/SaturnProvider.java b/src/main/java/org/saturnclient/common/provider/SaturnProvider.java index 4d63eec..998f539 100644 --- a/src/main/java/org/saturnclient/common/provider/SaturnProvider.java +++ b/src/main/java/org/saturnclient/common/provider/SaturnProvider.java @@ -28,6 +28,12 @@ public interface SaturnProvider { */ public IdentifierRef getPlayerSkin(); + /** + * Whether this version draws glass (the blurred game behind translucent + * panels), so screens needn't blur the whole background. + */ + public boolean supportsGlass(); + /** Shows a warning toast. Safe to call from any thread. */ public void showWarning(String title, String message); } diff --git a/src/main/java/org/saturnclient/ui/PanelScreen.java b/src/main/java/org/saturnclient/ui/PanelScreen.java index 2bf84a2..e8c8380 100644 --- a/src/main/java/org/saturnclient/ui/PanelScreen.java +++ b/src/main/java/org/saturnclient/ui/PanelScreen.java @@ -1,5 +1,6 @@ package org.saturnclient.ui; +import org.saturnclient.common.provider.Providers; import org.saturnclient.ui.components.Sidebar; import org.saturnclient.ui.elements.ImageTexture; import org.saturnclient.ui.elements.Panel; @@ -38,6 +39,9 @@ public abstract class PanelScreen extends SaturnScreen { @Override public final void ui() { + // The panels are frosted themselves, so the rest of the game stays clear. + blurBackground = !Providers.saturn.supportsGlass(); + panelWidth = Math.min(MAX_WIDTH, width - MARGIN * 2 - Sidebar.WIDTH - GAP); panelHeight = Math.min(MAX_HEIGHT, height - MARGIN * 2); diff --git a/src/main/java/org/saturnclient/ui/RenderScope.java b/src/main/java/org/saturnclient/ui/RenderScope.java index 0bbc677..808da81 100644 --- a/src/main/java/org/saturnclient/ui/RenderScope.java +++ b/src/main/java/org/saturnclient/ui/RenderScope.java @@ -34,6 +34,14 @@ public interface RenderScope { if (color == 0) return; + drawTexture(getRoundedRectangle(width, height, radius), x, y, 0, 0, width, height, color); + } + + /** + * The white rounded rectangle that {@link #drawRoundedRectangle} tints, as a + * cached texture. + */ + default IdentifierRef getRoundedRectangle(int width, int height, int radius) { radius = Math.min(radius, width / 2); radius = Math.min(radius, height / 2); @@ -42,7 +50,24 @@ public interface RenderScope { width, height, radius, radius); - drawSvgShape(rect, "rounded_" + width + "_" + height + "_" + radius, x, y, width, height, color); + return getSvgShape(rect, "rounded_" + width + "_" + height + "_" + radius, width, height); + } + + /** + * Frosts a rounded rectangle for a translucent background to be drawn over: + * the game behind the screen, blurred, cut to exactly the shape + * {@link #drawRoundedRectangle} draws. Draws nothing on versions without + * glass. + */ + default void drawGlass(int x, int y, int width, int height, int radius) { + drawGlass(getRoundedRectangle(width, height, radius), x, y, width, height); + } + + /** + * Draws the blurred game behind the screen where {@code mask}, stretched + * over the area, is opaque. + */ + default void drawGlass(IdentifierRef mask, int x, int y, int width, int height) { } /** @@ -89,15 +114,22 @@ public interface RenderScope { * {@code color}. */ private void drawSvgShape(String shape, String cacheKey, int x, int y, int width, int height, int color) { + drawTexture(getSvgShape(shape, cacheKey, width, height), x, y, 0, 0, width, height, color); + } + + /** + * {@code shape} (SVG elements in a {@code width} x {@code height} canvas, + * drawn in white) rendered to a cached texture. + */ + private IdentifierRef getSvgShape(String shape, String cacheKey, int width, int height) { String svg = String.format( "%s", width, height, shape); InputStream svgStream = new ByteArrayInputStream(svg.getBytes(StandardCharsets.UTF_8)); - drawTexture(SvgTexture.getSvg(svgStream, - IdentifierRef.ofSaturn("textures/gui/tmp_rect/" + cacheKey), - width * 4, height * 4), x, y, 0, 0, width, height, color); + return SvgTexture.getSvg(svgStream, IdentifierRef.ofSaturn("textures/gui/tmp_rect/" + cacheKey), + width * 4, height * 4); } void drawBorder(int x, int y, int width, int height, int color); diff --git a/src/main/java/org/saturnclient/ui/SaturnScreen.java b/src/main/java/org/saturnclient/ui/SaturnScreen.java index 14e6b46..a730b3d 100644 --- a/src/main/java/org/saturnclient/ui/SaturnScreen.java +++ b/src/main/java/org/saturnclient/ui/SaturnScreen.java @@ -46,11 +46,11 @@ public abstract class SaturnScreen { } /** - * How much to blur the game behind this screen right now: the client's - * setting, faded in over the screen's first moments. + * How much to blur the game behind this screen and its glass right now: the + * client's setting, faded in over the screen's first moments. */ - public int getBackgroundBlur() { - if (!blurBackground || start == null) { + public int getBlurRadius() { + if (start == null) { return 0; } @@ -58,6 +58,14 @@ public abstract class SaturnScreen { return Math.round(Config.backgroundBlur.value * Math.min(elapsed / (float) BLUR_FADE_IN, 1f)); } + /** + * How much to blur the whole game behind this screen right now, or 0 when it + * isn't. + */ + public int getBackgroundBlur() { + return blurBackground ? getBlurRadius() : 0; + } + /** * The GUI scale setting this screen follows; the menu scale unless the * screen is another kind. diff --git a/src/main/java/org/saturnclient/ui/elements/Panel.java b/src/main/java/org/saturnclient/ui/elements/Panel.java index 2b3369e..5392842 100644 --- a/src/main/java/org/saturnclient/ui/elements/Panel.java +++ b/src/main/java/org/saturnclient/ui/elements/Panel.java @@ -6,7 +6,8 @@ import org.saturnclient.ui.ElementContext; import org.saturnclient.ui.RenderScope; /** - * The translucent, outlined surface that screens lay their content on. + * The translucent, outlined surface that screens lay their content on, + * frosted over the game where glass is supported. */ public class Panel extends Element { @@ -16,6 +17,11 @@ public class Panel extends Element { } public static void drawBackground(RenderScope renderScope, int width, int height) { + // Frosted behind a background that isn't solid + if (Theme.getPanelBg() >>> 24 < 0xFF) { + renderScope.drawGlass(0, 0, width, height, Theme.BG_RADIUS.value); + } + renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, 1, Theme.getPanelBg(), Theme.getBorder()); } From 7bc308155e882743badc71e8465ec8e913022c0a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 21:35:23 +0200 Subject: [PATCH 08/10] Split the blur into world, menu and widget blurs The world or panorama keeps its animated background blur, while glass behind menus and behind widgets (buttons, the dock, the account chip) each get a fixed blur of their own. Glass is left out when its blur is 0. Co-Authored-By: Claude Opus 5.5 --- .../common/provider/SaturnProvider.java | 6 ---- .../java/org/saturnclient/config/Config.java | 8 ++++- .../java/org/saturnclient/ui/PanelScreen.java | 4 --- .../java/org/saturnclient/ui/RenderScope.java | 30 ++++++++++++++----- .../org/saturnclient/ui/SaturnScreen.java | 16 +++------- .../ui/components/AccountChip.java | 5 ++-- .../org/saturnclient/ui/components/Dock.java | 4 ++- .../org/saturnclient/ui/elements/Button.java | 3 +- .../org/saturnclient/ui/elements/Panel.java | 8 ++--- .../saturnclient/ui/screens/EmoteWheel.java | 4 +-- 10 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/saturnclient/common/provider/SaturnProvider.java b/src/main/java/org/saturnclient/common/provider/SaturnProvider.java index 998f539..4d63eec 100644 --- a/src/main/java/org/saturnclient/common/provider/SaturnProvider.java +++ b/src/main/java/org/saturnclient/common/provider/SaturnProvider.java @@ -28,12 +28,6 @@ public interface SaturnProvider { */ public IdentifierRef getPlayerSkin(); - /** - * Whether this version draws glass (the blurred game behind translucent - * panels), so screens needn't blur the whole background. - */ - public boolean supportsGlass(); - /** Shows a warning toast. Safe to call from any thread. */ public void showWarning(String title, String message); } diff --git a/src/main/java/org/saturnclient/config/Config.java b/src/main/java/org/saturnclient/config/Config.java index 4012c9e..7d89c6d 100644 --- a/src/main/java/org/saturnclient/config/Config.java +++ b/src/main/java/org/saturnclient/config/Config.java @@ -19,7 +19,11 @@ public class Config { public static BoolProperty saturnTitleScreen = Property.bool(true); public static BoolProperty cloakPhysics = Property.bool(true); public static BoolProperty stagger = Property.bool(true); + /** The world or panorama behind Saturn's screens, faded in as they open */ public static IntProperty backgroundBlur = Property.integer(5).range(0, 10); + /** Glass behind translucent menus (panels), and widgets (buttons, the dock) */ + public static IntProperty menuBlur = Property.integer(8).range(0, 20); + public static IntProperty widgetBlur = Property.integer(6).range(0, 20); // How big Saturn's screens are, relative to Minecraft's GUI scale public static FloatProperty titleScale = guiScale(); public static FloatProperty menuScale = guiScale(); @@ -43,7 +47,9 @@ public class Config { config.property("Open Emote Wheel", openEmoteWheel, "The key that opens the emote wheel"); config.property("Cloak Physics", cloakPhysics, "Let cloaks swing as you move"); config.property("Stagger Animations", stagger, "Animate list items one after another"); - config.property("Background blur", backgroundBlur, "How much the game behind Saturn's screens is blurred"); + config.property("World blur", backgroundBlur, "How much the world or panorama behind Saturn's screens is blurred"); + config.property("Menu blur", menuBlur, "How much the game is blurred through translucent menus"); + config.property("Widget blur", widgetBlur, "How much the game is blurred through translucent buttons and the dock"); config.property("Title screen scale", titleScale, "How big the title screen is, relative to Minecraft's GUI scale"); config.property("Menu scale", menuScale, "How big the mods, settings and cosmetics menus are, relative to Minecraft's GUI scale"); config.property("HUD menu scale", hudMenuScale, "How big the emote wheel and shift menu are, relative to Minecraft's GUI scale"); diff --git a/src/main/java/org/saturnclient/ui/PanelScreen.java b/src/main/java/org/saturnclient/ui/PanelScreen.java index e8c8380..2bf84a2 100644 --- a/src/main/java/org/saturnclient/ui/PanelScreen.java +++ b/src/main/java/org/saturnclient/ui/PanelScreen.java @@ -1,6 +1,5 @@ package org.saturnclient.ui; -import org.saturnclient.common.provider.Providers; import org.saturnclient.ui.components.Sidebar; import org.saturnclient.ui.elements.ImageTexture; import org.saturnclient.ui.elements.Panel; @@ -39,9 +38,6 @@ public abstract class PanelScreen extends SaturnScreen { @Override public final void ui() { - // The panels are frosted themselves, so the rest of the game stays clear. - blurBackground = !Providers.saturn.supportsGlass(); - panelWidth = Math.min(MAX_WIDTH, width - MARGIN * 2 - Sidebar.WIDTH - GAP); panelHeight = Math.min(MAX_HEIGHT, height - MARGIN * 2); diff --git a/src/main/java/org/saturnclient/ui/RenderScope.java b/src/main/java/org/saturnclient/ui/RenderScope.java index 808da81..85c0607 100644 --- a/src/main/java/org/saturnclient/ui/RenderScope.java +++ b/src/main/java/org/saturnclient/ui/RenderScope.java @@ -55,19 +55,33 @@ public interface RenderScope { /** * Frosts a rounded rectangle for a translucent background to be drawn over: - * the game behind the screen, blurred, cut to exactly the shape - * {@link #drawRoundedRectangle} draws. Draws nothing on versions without - * glass. + * the game behind the screen, blurred by {@code blur}, cut to exactly the + * shape {@link #drawRoundedRectangle} draws. Draws nothing on versions + * without glass. */ - default void drawGlass(int x, int y, int width, int height, int radius) { - drawGlass(getRoundedRectangle(width, height, radius), x, y, width, height); + default void drawGlass(int x, int y, int width, int height, int radius, int blur) { + drawGlass(getRoundedRectangle(width, height, radius), x, y, width, height, blur); } /** - * Draws the blurred game behind the screen where {@code mask}, stretched - * over the area, is opaque. + * Draws the game behind the screen, blurred by {@code blur}, where + * {@code mask}, stretched over the area, is opaque. */ - default void drawGlass(IdentifierRef mask, int x, int y, int width, int height) { + default void drawGlass(IdentifierRef mask, int x, int y, int width, int height, int blur) { + } + + /** + * A {@link #drawRoundedRectangle} with a border, frosted with glass blurred + * by {@code blur} when the background isn't solid. A blur of 0 leaves it + * plain, showing the screen's own background blur. + */ + default void drawGlassRectangle(int x, int y, int width, int height, int radius, int blur, int thickness, + int background, int border) { + if (blur > 0 && background >>> 24 < 0xFF) { + drawGlass(x, y, width, height, radius, blur); + } + + drawRoundedRectangle(x, y, width, height, radius, thickness, background, border); } /** diff --git a/src/main/java/org/saturnclient/ui/SaturnScreen.java b/src/main/java/org/saturnclient/ui/SaturnScreen.java index a730b3d..14e6b46 100644 --- a/src/main/java/org/saturnclient/ui/SaturnScreen.java +++ b/src/main/java/org/saturnclient/ui/SaturnScreen.java @@ -46,11 +46,11 @@ public abstract class SaturnScreen { } /** - * How much to blur the game behind this screen and its glass right now: the - * client's setting, faded in over the screen's first moments. + * How much to blur the game behind this screen right now: the client's + * setting, faded in over the screen's first moments. */ - public int getBlurRadius() { - if (start == null) { + public int getBackgroundBlur() { + if (!blurBackground || start == null) { return 0; } @@ -58,14 +58,6 @@ public abstract class SaturnScreen { return Math.round(Config.backgroundBlur.value * Math.min(elapsed / (float) BLUR_FADE_IN, 1f)); } - /** - * How much to blur the whole game behind this screen right now, or 0 when it - * isn't. - */ - public int getBackgroundBlur() { - return blurBackground ? getBlurRadius() : 0; - } - /** * The GUI scale setting this screen follows; the menu scale unless the * screen is another kind. diff --git a/src/main/java/org/saturnclient/ui/components/AccountChip.java b/src/main/java/org/saturnclient/ui/components/AccountChip.java index fa5a49d..9cba3f9 100644 --- a/src/main/java/org/saturnclient/ui/components/AccountChip.java +++ b/src/main/java/org/saturnclient/ui/components/AccountChip.java @@ -3,6 +3,7 @@ package org.saturnclient.ui.components; import org.saturnclient.client.ServiceClient; import org.saturnclient.common.provider.Providers; import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.config.Config; import org.saturnclient.config.Theme; import org.saturnclient.ui.Element; import org.saturnclient.ui.ElementContext; @@ -36,8 +37,8 @@ public class AccountChip extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, Theme.getPanelBg(), - Theme.getBorder()); + renderScope.drawGlassRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Config.widgetBlur.value, 1, + Theme.getPanelBg(), Theme.getBorder()); // The face, then the hat layer over it, from the 64x64 skin IdentifierRef skin = Providers.saturn.getPlayerSkin(); diff --git a/src/main/java/org/saturnclient/ui/components/Dock.java b/src/main/java/org/saturnclient/ui/components/Dock.java index 0959a44..70ad420 100644 --- a/src/main/java/org/saturnclient/ui/components/Dock.java +++ b/src/main/java/org/saturnclient/ui/components/Dock.java @@ -3,6 +3,7 @@ package org.saturnclient.ui.components; import java.util.List; import org.saturnclient.common.ref.asset.IdentifierRef; +import org.saturnclient.config.Config; import org.saturnclient.config.Theme; import org.saturnclient.ui.Element; import org.saturnclient.ui.ElementContext; @@ -32,7 +33,8 @@ public class Dock extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value + 4, 1, Theme.getPanelBg(), + renderScope.drawGlassRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value + 4, Config.widgetBlur.value, 1, + Theme.getPanelBg(), Theme.withAlpha(0.1f, Theme.FOREGROUND.value)); for (int i = 0; i < items.size(); i++) { diff --git a/src/main/java/org/saturnclient/ui/elements/Button.java b/src/main/java/org/saturnclient/ui/elements/Button.java index 30615fd..0ddc0cf 100644 --- a/src/main/java/org/saturnclient/ui/elements/Button.java +++ b/src/main/java/org/saturnclient/ui/elements/Button.java @@ -1,5 +1,6 @@ package org.saturnclient.ui.elements; +import org.saturnclient.config.Config; import org.saturnclient.config.Theme; import org.saturnclient.ui.Element; import org.saturnclient.ui.ElementContext; @@ -38,7 +39,7 @@ public class Button extends Element { * The button's surface, shared with {@link IconButton}. */ static void drawSurface(RenderScope renderScope, int width, int height, boolean hovered) { - renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, + renderScope.drawGlassRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Config.widgetBlur.value, 1, hovered ? Theme.PRIMARY.value : Theme.getPanelBg(), Theme.withAlpha(hovered ? 0.25f : 0.1f, Theme.FOREGROUND.value)); } diff --git a/src/main/java/org/saturnclient/ui/elements/Panel.java b/src/main/java/org/saturnclient/ui/elements/Panel.java index 5392842..8496b35 100644 --- a/src/main/java/org/saturnclient/ui/elements/Panel.java +++ b/src/main/java/org/saturnclient/ui/elements/Panel.java @@ -1,5 +1,6 @@ package org.saturnclient.ui.elements; +import org.saturnclient.config.Config; import org.saturnclient.config.Theme; import org.saturnclient.ui.Element; import org.saturnclient.ui.ElementContext; @@ -17,12 +18,7 @@ public class Panel extends Element { } public static void drawBackground(RenderScope renderScope, int width, int height) { - // Frosted behind a background that isn't solid - if (Theme.getPanelBg() >>> 24 < 0xFF) { - renderScope.drawGlass(0, 0, width, height, Theme.BG_RADIUS.value); - } - - renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, 1, + renderScope.drawGlassRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Config.menuBlur.value, 1, Theme.getPanelBg(), Theme.getBorder()); } } diff --git a/src/main/java/org/saturnclient/ui/screens/EmoteWheel.java b/src/main/java/org/saturnclient/ui/screens/EmoteWheel.java index 3f6b9c3..bf2261b 100644 --- a/src/main/java/org/saturnclient/ui/screens/EmoteWheel.java +++ b/src/main/java/org/saturnclient/ui/screens/EmoteWheel.java @@ -131,8 +131,8 @@ public class EmoteWheel extends SaturnScreen { @Override public void render(RenderScope renderScope, ElementContext ctx) { - renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, - Theme.getPanelBg(), Theme.getBorder()); + renderScope.drawGlassRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Config.widgetBlur.value, + 1, Theme.getPanelBg(), Theme.getBorder()); float titleScale = 0.54f; drawCentered(renderScope, "EMOTES", titleScale, pages > 1 ? 24 : (height - 10) / 2, Theme.getMutedFg()); From 524fc36bf333f37796516e267e5e8de8d5030f7e Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 21:55:47 +0200 Subject: [PATCH 09/10] Say glass is up to the render scope, not the version Co-Authored-By: Claude Opus 5.5 --- src/main/java/org/saturnclient/ui/RenderScope.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/saturnclient/ui/RenderScope.java b/src/main/java/org/saturnclient/ui/RenderScope.java index 85c0607..55179b0 100644 --- a/src/main/java/org/saturnclient/ui/RenderScope.java +++ b/src/main/java/org/saturnclient/ui/RenderScope.java @@ -56,8 +56,8 @@ public interface RenderScope { /** * Frosts a rounded rectangle for a translucent background to be drawn over: * the game behind the screen, blurred by {@code blur}, cut to exactly the - * shape {@link #drawRoundedRectangle} draws. Draws nothing on versions - * without glass. + * shape {@link #drawRoundedRectangle} draws. Render scopes without glass + * draw nothing. */ default void drawGlass(int x, int y, int width, int height, int radius, int blur) { drawGlass(getRoundedRectangle(width, height, radius), x, y, width, height, blur); From 7cc37b7c7455caea920981f982823af16810898a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 21:57:10 +0200 Subject: [PATCH 10/10] Tell when glass can be drawn at all Co-Authored-By: Claude Opus 5.5 --- src/main/java/org/saturnclient/config/Config.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/saturnclient/config/Config.java b/src/main/java/org/saturnclient/config/Config.java index 7d89c6d..c986eca 100644 --- a/src/main/java/org/saturnclient/config/Config.java +++ b/src/main/java/org/saturnclient/config/Config.java @@ -34,6 +34,15 @@ public class Config { return Property.floatProp(1f).range(0.5f, 2f, 0.05f).unit("×"); } + /** + * Whether any glass can be drawn: a glass blur is on, and a background it + * goes behind isn't solid. + */ + public static boolean usesGlass() { + boolean translucent = Theme.getPanelBg() >>> 24 < 0xFF || Theme.PRIMARY.value >>> 24 < 0xFF; + return translucent && (menuBlur.value > 0 || widgetBlur.value > 0); + } + public static IdentifierRef getLogo() { return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO; }