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 <[email protected]>
This commit is contained in:
2026-09-28 19:20:33 +02:00
co-authored by claude
parent 320b2fd9a5
commit a7ad458627
4 changed files with 59 additions and 33 deletions
@@ -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);
@@ -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;
}
}
@@ -13,6 +13,8 @@ import org.saturnclient.ui.RenderScope;
public class AnimationStagger extends Element {
protected List<Element> 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);
@@ -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) {