diff --git a/src/main/java/org/saturnclient/config/Config.java b/src/main/java/org/saturnclient/config/Config.java index 01a0ed2..c986eca 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; @@ -18,9 +19,30 @@ 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(); + 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("×"); + } + + /** + * 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; } @@ -34,7 +56,12 @@ 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"); // Initialize a sub namespace for theme Theme.init(config); 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/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/RenderScope.java b/src/main/java/org/saturnclient/ui/RenderScope.java index 0bbc677..55179b0 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,38 @@ 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 by {@code blur}, cut to exactly the + * 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); + } + + /** + * 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, 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); } /** @@ -89,15 +128,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 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/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/components/AccountChip.java b/src/main/java/org/saturnclient/ui/components/AccountChip.java index 554f9c6..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; @@ -15,12 +16,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,13 +31,14 @@ 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 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(); @@ -43,11 +46,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/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/Dock.java b/src/main/java/org/saturnclient/ui/components/Dock.java index e5d174e..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; @@ -15,12 +16,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; @@ -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/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 8c7f701..2247f5b 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,8 +61,10 @@ 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)); + Theme.getPopupBg(), Theme.withAlpha(0.14f, Theme.FOREGROUND.value)); renderOpacity(renderScope); renderSquare(renderScope); @@ -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); 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/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/elements/Button.java b/src/main/java/org/saturnclient/ui/elements/Button.java index e884242..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,8 +39,8 @@ 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, - hovered ? Theme.withAlpha(0.9f, Theme.PRIMARY.value) : Theme.getPanelBg(), + 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 2b3369e..8496b35 100644 --- a/src/main/java/org/saturnclient/ui/elements/Panel.java +++ b/src/main/java/org/saturnclient/ui/elements/Panel.java @@ -1,12 +1,14 @@ 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; 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,7 +18,7 @@ public class Panel extends Element { } public static void drawBackground(RenderScope renderScope, int width, int height) { - 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 771fd40..bf2261b 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; @@ -125,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()); 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..7143339 100644 --- a/src/main/java/org/saturnclient/ui/screens/TitleMenu.java +++ b/src/main/java/org/saturnclient/ui/screens/TitleMenu.java @@ -6,9 +6,11 @@ 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; +import org.saturnclient.ui.anim.Animation; import org.saturnclient.ui.anim.Fade; import org.saturnclient.ui.anim.SlideFade; import org.saturnclient.ui.anim.SlideY; @@ -16,96 +18,133 @@ 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, the shift menu and Minecraft's options, 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; - 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; + /** 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 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; public TitleMenu() { super("Title Menu"); } + @Override + protected FloatProperty getScaleSetting() { + return Config.titleScale; + } + @Override public void ui() { blurBackground = false; // 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 menusHeight = BUTTON_HEIGHT * 2 + GAP; + 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) + .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 + LOGO + 16) - .animation(new Fade(AnimationConfig.logo.duration.value))); + .position((width - WORDMARK_WIDTH) / 2, top + WORDMARK_Y) + .animation(step(new Fade(AnimationConfig.logo.duration.value), STEP_LOGO))); - int rowHeight = BUTTON_HEIGHT + GAP; - int halfWidth = (COLUMN_WIDTH - GAP) / 2; - AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu); + AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu) + .after(stepDelay(STEP_MENUS)); 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 + 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 + // Top corners: the player, and quitting the game draw(new AccountChip() - .position(EDGE, height - EDGE - AccountChip.HEIGHT) - .animation(new Fade(AnimationConfig.mainMenu))); + .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 - TOP_EDGE - QUIT_SIZE, TOP_EDGE) + .animation(step(new SlideFade(AnimationConfig.mainMenu, 8), STEP_QUIT))); + // 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.ICON_HUD_EDITOR, - () -> Providers.saturn.getClient().setScreen(new HudEditor())))); + 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(new SlideFade(AnimationConfig.mainMenu, 12))); + .animation(step(new SlideFade(AnimationConfig.mainMenu, -12), STEP_DOCK))); 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(step(new Fade(AnimationConfig.mainMenu), STEP_VERSION))); } /** - * "Saturn Client 0.1.0 · Minecraft 1.21.11", from the mod version - * "0.1.0+1.21.11". + * 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; + } + + /** + * "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) {