Compare commits

...
1 Commits
Author SHA1 Message Date
selimaj-devandclaude a7ad458627 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]>
2026-09-28 19:20:33 +02:00
4 changed files with 59 additions and 33 deletions
@@ -26,6 +26,11 @@ public class ElementRenderer {
for (Element element : elements) { for (Element element : elements) {
element.playAnimationFrame(elapsed); element.playAnimationFrame(elapsed);
// Not faded in yet
if (element.opacity <= 0) {
continue;
}
renderScope.getMatrixStack().push(); renderScope.getMatrixStack().push();
renderScope.setOpacity(element.opacity); renderScope.setOpacity(element.opacity);
renderScope.getMatrixStack().translate(element.x, element.y); renderScope.getMatrixStack().translate(element.x, element.y);
@@ -16,12 +16,12 @@ public class Fade extends Animation {
@Override @Override
public void tick(double progress, Element element) { public void tick(double progress, Element element) {
element.opacity = Math.max(0.1f, targetOpacity * (float) progress); element.opacity = targetOpacity * (float) progress;
} }
@Override @Override
public void init(Element element) { public void init(Element element) {
targetOpacity = element.opacity; targetOpacity = element.opacity;
element.opacity = 0.1f; element.opacity = 0;
} }
} }
@@ -13,6 +13,8 @@ import org.saturnclient.ui.RenderScope;
public class AnimationStagger extends Element { public class AnimationStagger extends Element {
protected List<Element> children = new ArrayList<>(); protected List<Element> children = new ArrayList<>();
public int delay; public int delay;
/** When the first child starts, in ms */
public int start = 0;
public AnimationStagger(int delay) { public AnimationStagger(int delay) {
this.delay = delay; this.delay = delay;
@@ -22,13 +24,21 @@ public class AnimationStagger extends Element {
this.delay = config.stagger.value; 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) { public void draw(Element element) {
if (!Config.stagger.value) { if (!Config.stagger.value) {
element.animation = null; element.animation = null;
} }
if (element.animation != null) { if (element.animation != null) {
element.animation.delay = delay * children.size(); element.animation.delay = start + delay * children.size();
} }
ElementRenderer.draw(children, element); ElementRenderer.draw(children, element);
@@ -10,6 +10,7 @@ import org.saturnclient.config.property.FloatProperty;
import org.saturnclient.config.Theme; import org.saturnclient.config.Theme;
import org.saturnclient.ui.Element; import org.saturnclient.ui.Element;
import org.saturnclient.ui.SaturnScreen; import org.saturnclient.ui.SaturnScreen;
import org.saturnclient.ui.anim.Animation;
import org.saturnclient.ui.anim.Fade; import org.saturnclient.ui.anim.Fade;
import org.saturnclient.ui.anim.SlideFade; import org.saturnclient.ui.anim.SlideFade;
import org.saturnclient.ui.anim.SlideY; 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.components.Dock;
import org.saturnclient.ui.elements.AnimationStagger; import org.saturnclient.ui.elements.AnimationStagger;
import org.saturnclient.ui.elements.Button; import org.saturnclient.ui.elements.Button;
import org.saturnclient.ui.elements.IconButton;
import org.saturnclient.ui.elements.ImageTexture; import org.saturnclient.ui.elements.ImageTexture;
import org.saturnclient.ui.elements.Label; import org.saturnclient.ui.elements.Label;
import org.saturnclient.ui.resources.Fonts; import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.resources.Textures; import org.saturnclient.ui.resources.Textures;
/** /**
* Saturn's title screen: the logo over the game's menus in the middle, and * Saturn's title screen: the logo over the game's menus in the middle, the
* along the bottom the player, a dock of Saturn's screens and the version. * 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 { public class TitleMenu extends SaturnScreen {
// The logo and wordmark as the shift menu has them: the wordmark over the logo's foot // 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 COLUMN_WIDTH = 280;
private static final int BUTTON_HEIGHT = 36; private static final int BUTTON_HEIGHT = 36;
private static final int GAP = 8; private static final int GAP = 8;
private static final int EDGE = 28; /** Space around the account chip, quit button, dock and version, from the screen's edges */
private static final float VERSION_SCALE = 0.62f; 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() { public TitleMenu() {
super("Title Menu"); super("Title Menu");
@@ -54,7 +65,7 @@ public class TitleMenu extends SaturnScreen {
blurBackground = false; blurBackground = false;
// Logo and menus, centered together a little above the middle // 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 blockHeight = LOGO + MENUS_GAP + menusHeight;
int top = (height - blockHeight) / 2 - 20; int top = (height - blockHeight) / 2 - 20;
@@ -67,57 +78,57 @@ public class TitleMenu extends SaturnScreen {
.position((width - WORDMARK_WIDTH) / 2, top + WORDMARK_Y) .position((width - WORDMARK_WIDTH) / 2, top + WORDMARK_Y)
.animation(new Fade(AnimationConfig.logo.duration.value))); .animation(new Fade(AnimationConfig.logo.duration.value)));
int rowHeight = BUTTON_HEIGHT + GAP; AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu).after(MENUS_DELAY);
int halfWidth = (COLUMN_WIDTH - GAP) / 2;
AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu);
addMenuButton(menus, new Button("Singleplayer", addMenuButton(menus, new Button("Singleplayer",
() -> Providers.saturn.getClient().setScreen(MinecraftScreen.SelectWorld)) () -> Providers.saturn.getClient().setScreen(MinecraftScreen.SelectWorld))
.dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, 0)); .dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, 0));
addMenuButton(menus, new Button("Multiplayer", addMenuButton(menus, new Button("Multiplayer",
() -> Providers.saturn.getClient().setScreen(MinecraftScreen.Multiplayer)) () -> Providers.saturn.getClient().setScreen(MinecraftScreen.Multiplayer))
.dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, rowHeight)); .dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, BUTTON_HEIGHT + GAP));
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));
draw(menus.position((width - COLUMN_WIDTH) / 2, top + LOGO + MENUS_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() draw(new AccountChip()
.position(EDGE, height - EDGE - AccountChip.HEIGHT) .position(EDGE, EDGE)
.animation(new Fade(AnimationConfig.mainMenu))); .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( Dock dock = new Dock(List.of(
new Dock.Item(Textures.ICON_MODS, () -> Providers.saturn.getClient().setScreen(new ModMenu())), new Dock.Item(Textures.ICON_MODS, () -> Providers.saturn.getClient().setScreen(new ModMenu())),
new Dock.Item(Textures.ICON_COSMETICS, new Dock.Item(Textures.ICON_COSMETICS,
() -> Providers.saturn.getClient().setScreen(new CosmeticsMenu())), () -> Providers.saturn.getClient().setScreen(new CosmeticsMenu())),
new Dock.Item(Textures.ICON_HUD_EDITOR, new Dock.Item(Textures.LOGO, () -> Providers.saturn.getClient().setScreen(new ClientSettings())),
() -> Providers.saturn.getClient().setScreen(new HudEditor())),
new Dock.Item(Textures.ICON_SETTINGS, 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) 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(); String version = getVersionText();
int versionWidth = (int) (Fonts.getWidth(version, Theme.getBodyFont()) * VERSION_SCALE);
int versionHeight = (int) (Fonts.getHeight() * VERSION_SCALE); int versionHeight = (int) (Fonts.getHeight() * VERSION_SCALE);
draw(new Label(() -> version, VERSION_SCALE, Theme::getBodyFont, Theme::getMutedFg) draw(new Label(() -> version, VERSION_SCALE, Theme::getBodyFont,
.position(width - EDGE - versionWidth, height - EDGE - (AccountChip.HEIGHT + versionHeight) / 2) () -> Theme.withAlpha(VERSION_ALPHA, Theme.getMutedFg()))
.animation(new Fade(AnimationConfig.mainMenu))); .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 * "Saturn 0.1.0 · 1.21.11", from the mod version "0.1.0+1.21.11".
* "0.1.0+1.21.11".
*/ */
private static String getVersionText() { private static String getVersionText() {
String[] version = Providers.saturn.getModVersion().split("\\+", 2); String[] version = Providers.saturn.getModVersion().split("\\+", 2);
String text = "Saturn Client " + version[0]; String text = "Saturn " + version[0];
return version.length > 1 ? text + " · Minecraft " + version[1] : text; return version.length > 1 ? text + " · " + version[1] : text;
} }
private static void addMenuButton(AnimationStagger stagger, Element button) { private static void addMenuButton(AnimationStagger stagger, Element button) {