Redesign the mod menu, settings and cosmetics screens #6

Merged
selimaj-dev merged 13 commits from ui-redesign into master 2026-09-27 16:27:35 +00:00
12 changed files with 412 additions and 568 deletions
Showing only changes of commit 7264c7b38b - Show all commits
@@ -1,43 +0,0 @@
package org.saturnclient.ui.components;
import org.saturnclient.common.ref.asset.IdentifierRef;
import org.saturnclient.config.Theme;
import org.saturnclient.ui.Element;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope;
public class CosmeticPreview extends Element {
static int padding = 14;
IdentifierRef sprite;
private Runnable onClick;
private boolean isSelected;
public CosmeticPreview(boolean isSelected, IdentifierRef sprite, Runnable onClick) {
this.sprite = sprite;
this.onClick = onClick;
this.isSelected = isSelected;
this.width = 50;
this.height = 111;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value);
int texWidth = width - padding;
int texHeight = height - padding;
renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
Theme.FOREGROUND.value);
}
@Override
public void click(int mouseX, int mouseY) {
if (onClick != null) {
onClick.run();
}
}
}
@@ -0,0 +1,125 @@
package org.saturnclient.ui.components;
import java.util.function.BooleanSupplier;
import org.saturnclient.common.ref.asset.IdentifierRef;
import org.saturnclient.config.Theme;
import org.saturnclient.ui.Element;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.resources.Textures;
/**
* One cosmetic in the cosmetics grid: its preview and name. It shows whether
* it's equipped, and cosmetics not owned yet are dimmed with a "Get" label.
*/
public class CosmeticTile extends Element {
public static final int HEIGHT = 168;
private static final int INSET = 8;
private static final int IMAGE_AREA = 124;
private static final float NAME_SCALE = 0.62f;
private static final float BADGE_SCALE = 0.54f;
private static final int BADGE_HEIGHT = 20;
/** How long a claim may take before it can be tried again, in ms */
private static final long CLAIM_TIMEOUT = 5000;
private final String name;
private final IdentifierRef preview;
private final float previewAspect;
private final BooleanSupplier owned;
private final BooleanSupplier equipped;
private final Runnable onEquip;
private final Runnable onClaim;
private long claimedAt = 0;
/**
* @param previewAspect the preview texture's width divided by its height
*/
public CosmeticTile(String name, IdentifierRef preview, float previewAspect, int width, BooleanSupplier owned,
BooleanSupplier equipped, Runnable onEquip, Runnable onClaim) {
this.name = name;
this.preview = preview;
this.previewAspect = previewAspect;
this.owned = owned;
this.equipped = equipped;
this.onEquip = onEquip;
this.onClaim = onClaim;
this.width = width;
this.height = HEIGHT;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
boolean isOwned = owned.getAsBoolean();
boolean isEquipped = equipped.getAsBoolean();
boolean hovered = ctx.isHovering();
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);
// Preview, fitted into its area
int areaWidth = width - INSET * 2;
renderScope.drawRoundedRectangle(INSET, INSET, areaWidth, IMAGE_AREA, Theme.WIDGET_RADIUS.value - 2,
Theme.withAlpha(0.05f, Theme.FOREGROUND.value));
int imageHeight = IMAGE_AREA - 12;
int imageWidth = (int) (imageHeight * previewAspect);
if (imageWidth > areaWidth - 12) {
imageWidth = areaWidth - 12;
imageHeight = (int) (imageWidth / previewAspect);
}
renderScope.drawTexture(preview, INSET + (areaWidth - imageWidth) / 2, INSET + (IMAGE_AREA - imageHeight) / 2,
0, 0, imageWidth, imageHeight, isOwned ? 0xFFFFFFFF : Theme.withAlpha(0.35f, 0xFFFFFFFF));
// Name
int nameY = INSET + IMAGE_AREA + (HEIGHT - INSET - IMAGE_AREA - (int) (Fonts.getHeight() * NAME_SCALE)) / 2;
renderScope.drawText(NAME_SCALE, Fonts.ellipsize(name, Theme.FONT.value, NAME_SCALE, width - 24), 12, nameY,
Theme.FONT.value, isOwned ? Theme.FOREGROUND.value : Theme.getMutedFg());
// Badge in the preview's corner
if (isEquipped) {
drawBadge(renderScope, "On", Theme.ACCENT.value, Theme.ACCENT_FG.value, true);
} else if (!isOwned) {
drawBadge(renderScope, isClaiming() ? "Claiming" : "Get", hovered ? Theme.ACCENT.value : Theme.PRIMARY.value,
hovered ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value, false);
}
}
private void drawBadge(RenderScope renderScope, String text, int background, int foreground, boolean check) {
int icon = check ? 10 : 0;
int badgeWidth = 14 + icon + (check ? 4 : 0) + (int) (Fonts.getWidth(text, Theme.FONT.value) * BADGE_SCALE);
int x = width - INSET - 6 - badgeWidth;
int y = INSET + 6;
renderScope.drawRoundedRectangle(x, y, badgeWidth, BADGE_HEIGHT, 6, background);
if (check) {
renderScope.drawTexture(Textures.CHECK, x + 7, y + (BADGE_HEIGHT - icon) / 2, 0, 0, icon, icon, foreground);
}
int textY = y + (BADGE_HEIGHT - (int) (Fonts.getHeight() * BADGE_SCALE)) / 2;
renderScope.drawText(BADGE_SCALE, text, x + 7 + icon + (check ? 4 : 0), textY, Theme.FONT.value, foreground);
}
@Override
public void click(int mouseX, int mouseY) {
if (owned.getAsBoolean()) {
if (!equipped.getAsBoolean()) {
onEquip.run();
}
} else if (!isClaiming()) {
claimedAt = System.currentTimeMillis();
onClaim.run();
}
}
private boolean isClaiming() {
return System.currentTimeMillis() - claimedAt < CLAIM_TIMEOUT;
}
}
@@ -11,7 +11,7 @@ import org.saturnclient.ui.resources.Textures;
import org.saturnclient.ui.screens.ClientSettings; import org.saturnclient.ui.screens.ClientSettings;
import org.saturnclient.ui.screens.HudEditor; import org.saturnclient.ui.screens.HudEditor;
import org.saturnclient.ui.screens.ModMenu; import org.saturnclient.ui.screens.ModMenu;
import org.saturnclient.ui.screens.cosmetics.CloakMenu; import org.saturnclient.ui.screens.CosmeticsMenu;
/** /**
* The navigation rail beside Saturn's screens: the logo, one button per screen, * The navigation rail beside Saturn's screens: the logo, one button per screen,
@@ -37,7 +37,7 @@ public class Sidebar extends Element {
private final Item[] items = { private final Item[] items = {
new Item(Textures.ICON_MODS, () -> Providers.saturn.getClient().setScreen(new ModMenu())), new Item(Textures.ICON_MODS, () -> Providers.saturn.getClient().setScreen(new ModMenu())),
new Item(Textures.ICON_COSMETICS, () -> Providers.saturn.getClient().setScreen(new CloakMenu())), new Item(Textures.ICON_COSMETICS, () -> Providers.saturn.getClient().setScreen(new CosmeticsMenu())),
new Item(Textures.ICON_HUD_EDITOR, () -> Providers.saturn.getClient().setScreen(new HudEditor())), new Item(Textures.ICON_HUD_EDITOR, () -> Providers.saturn.getClient().setScreen(new HudEditor())),
new Item(Textures.ICON_SETTINGS, () -> Providers.saturn.getClient().setScreen(new ClientSettings())), new Item(Textures.ICON_SETTINGS, () -> Providers.saturn.getClient().setScreen(new ClientSettings())),
}; };
@@ -1,26 +1,68 @@
package org.saturnclient.ui.components; package org.saturnclient.ui.components;
import org.saturnclient.config.Theme;
import org.saturnclient.ui.Element; import org.saturnclient.ui.Element;
import org.saturnclient.ui.ElementContext; import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope; import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
/**
* The player, wearing their cosmetics, filling the element. Dragging it turns
* the player around.
*/
public class SkinPreview extends Element { public class SkinPreview extends Element {
public static EntityDrawer DRAWER; public static EntityDrawer DRAWER;
private boolean negativeAngle = false;
private float angle = 0.0f;
public SkinPreview(float angle, boolean negativeAngle) { /** Degrees turned per GUI pixel dragged */
this.angle = angle; private static final float DRAG_SPEED = 1.2f;
this.negativeAngle = negativeAngle; private static final float HINT_SCALE = 0.66f;
private float yaw;
private boolean dragging = false;
/**
* @param yaw degrees the player starts turned by: 0 faces the viewer, 180
* shows their back
*/
public SkinPreview(float yaw) {
this.yaw = yaw;
} }
@Override @Override
public void render(RenderScope renderScope, ElementContext ctx) { public void render(RenderScope renderScope, ElementContext ctx) {
DRAWER.render(renderScope, negativeAngle, angle, scale, ctx); if (DRAWER != null && DRAWER.render(renderScope, width, height, yaw)) {
return;
}
String hint = "Join a world to preview";
int x = (width - (int) (Fonts.getWidth(hint, Theme.getBodyFont()) * HINT_SCALE)) / 2;
renderScope.drawText(HINT_SCALE, hint, x, height / 2, Theme.getBodyFont(), Theme.getMutedFg());
}
@Override
public void mouseClicked(double mouseX, double mouseY, int button) {
dragging = mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height;
}
@Override
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
if (dragging) {
yaw = (yaw - (float) deltaX * DRAG_SPEED) % 360;
}
}
@Override
public void mouseReleased(double mouseX, double mouseY, int button) {
dragging = false;
} }
public static interface EntityDrawer { public static interface EntityDrawer {
public void render(RenderScope renderScope, boolean negativeAngle, float angle, float scale, /**
ElementContext ctx); * Draws the client's player filling a {@code width} x {@code height} box,
* turned by {@code yaw} degrees (0 faces the viewer).
*
* @return false if there's no player to draw, such as on the title screen
*/
public boolean render(RenderScope renderScope, int width, int height, float yaw);
} }
} }
@@ -1,63 +0,0 @@
package org.saturnclient.ui.elements;
import org.saturnclient.common.ref.asset.IdentifierRef;
import org.saturnclient.config.Theme;
import org.saturnclient.ui.Element;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.Utils;
public class TabMenu extends Element {
public static class TabMenuComponent {
Runnable onClick;
IdentifierRef sprite;
public TabMenuComponent(IdentifierRef sprite, Runnable onClick) {
this.onClick = onClick;
this.sprite = sprite;
}
}
TabMenuComponent[] components;
public int selectedScreen = 0;
public TabMenu(int active, TabMenuComponent... components) {
this.components = components;
width = 25 * components.length + 6;
height = 30;
this.selectedScreen = active;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value);
int col = 0;
for (TabMenuComponent component : components) {
int x = 25 * col + 5;
boolean hovering = ctx.isHovering(x, 5, 20, 20) || selectedScreen == col;
if (hovering) {
renderScope.drawRoundedRectangle(x, 5, 20, 20, Theme.WIDGET_RADIUS.value, Theme.ACCENT.value);
}
renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10,
hovering ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
col++;
}
}
@Override
public void click(int mouseX, int mouseY) {
int col = 0;
for (TabMenuComponent component : components) {
int x = 25 * col + 5;
if (Utils.isHovering(mouseX - x, mouseY - 5, 20, 20, 1.0f)) {
component.onClick.run();
}
col++;
}
}
}
@@ -0,0 +1,233 @@
package org.saturnclient.ui.screens;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.saturnclient.client.ServiceClient;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.common.ref.asset.IdentifierRef;
import org.saturnclient.config.AnimationConfig;
import org.saturnclient.config.Theme;
import org.saturnclient.cosmetics.Cloaks;
import org.saturnclient.cosmetics.Hats;
import org.saturnclient.ui.Element;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.PanelScreen;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.anim.SlideFade;
import org.saturnclient.ui.components.CosmeticTile;
import org.saturnclient.ui.components.Sidebar;
import org.saturnclient.ui.components.SkinPreview;
import org.saturnclient.ui.elements.AnimationStagger;
import org.saturnclient.ui.elements.ChipGroup;
import org.saturnclient.ui.elements.Label;
import org.saturnclient.ui.elements.Scroll;
import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.resources.Textures;
/**
* Cloaks and hats: the player on the left, turned by dragging, and a grid of
* every cosmetic on the right. Clicking an owned cosmetic equips it; clicking
* one not owned yet claims it, which is free.
*/
public class CosmeticsMenu extends PanelScreen {
private enum Kind {
CLOAKS("Cloaks", "Cloak", Cloaks.ALL_CLOAKS, () -> Cloaks.availableCloaks, player -> player.cloak,
Cloaks::setCloak, ServiceClient::buyCloak, Textures::getCloakPreview, 0.5f, 150f),
HATS("Hats", "Hat", Hats.ALL_HATS, () -> Hats.availableHats, player -> player.hat,
Hats::setHat, ServiceClient::buyHat, Textures::getHatPreview, 1f, -25f);
final String title;
final String singular;
final String[] all;
final Supplier<List<String>> owned;
final Function<SaturnPlayer, String> equipped;
final Consumer<String> equip;
final Consumer<String> claim;
final Function<String, IdentifierRef> preview;
/** Width divided by height of the preview textures */
final float previewAspect;
/** The angle the player starts at, showing off this kind of cosmetic */
final float yaw;
Kind(String title, String singular, String[] all, Supplier<List<String>> owned,
Function<SaturnPlayer, String> equipped, Consumer<String> equip, Consumer<String> claim,
Function<String, IdentifierRef> preview, float previewAspect, float yaw) {
this.title = title;
this.singular = singular;
this.all = all;
this.owned = owned;
this.equipped = equipped;
this.equip = equip;
this.claim = claim;
this.preview = preview;
this.previewAspect = previewAspect;
this.yaw = yaw;
}
/**
* "" (nothing), then every cosmetic of this kind.
*/
List<String> ids() {
List<String> ids = new ArrayList<>();
ids.add("");
ids.addAll(List.of(all));
return ids;
}
String getEquipped() {
SaturnPlayer player = SaturnPlayer.get();
return player == null ? "" : equipped.apply(player);
}
}
/** Names that aren't just the id with each word capitalized */
private static final Map<String, String> NAMES = Map.of(
"", "None",
"amg", "AMG",
"amg_petronas", "AMG Petronas",
"bmw", "BMW",
"redbull", "Red Bull");
private static final int STAGE_WIDTH = 300;
private static final int STAGE_PADDING = 16;
private static final int STAGE_FOOTER = 70;
private static final int COLUMN_GAP = 20;
private static final int COLUMNS = 4;
private static final int TILE_GAP = 10;
private static final int GRID_Y = 58;
private static final int SCROLLBAR_SPACE = 14;
private static final float SMALL_SCALE = 0.6f;
private Kind kind = Kind.CLOAKS;
public CosmeticsMenu() {
super("Cosmetics", Sidebar.COSMETICS);
}
@Override
protected void panel() {
drawStage();
int x = contentX + STAGE_WIDTH + COLUMN_GAP;
int width = contentX + contentWidth - x;
// Header
draw(new Label(() -> "Cosmetics", 1.15f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
.position(x, contentY + 2));
draw(new Label(this::getOwnedSummary, 0.66f, Theme::getBodyFont, Theme::getMutedFg)
.position(x, contentY + 28));
ChipGroup tabs = new ChipGroup(List.of(Kind.CLOAKS.title, Kind.HATS.title), kind.title, selected -> {
kind = selected.equals(Kind.HATS.title) ? Kind.HATS : Kind.CLOAKS;
init();
});
draw(tabs.position(x + width - tabs.width, contentY + 4));
// Grid
Scroll scroll = new Scroll(0).noBackground();
int gridHeight = contentHeight - GRID_Y;
if (SaturnPlayer.get() == null) {
String message = "Connect to Saturn to change your cosmetics";
int messageX = (width - (int) (Fonts.getWidth(message, Theme.getBodyFont()) * 0.7f)) / 2;
scroll.draw(new Label(() -> message, 0.7f, Theme::getBodyFont, Theme::getMutedFg)
.position(messageX, 60));
} else {
scroll.draw(createGrid(width));
}
draw(scroll.dimensions(width + SCROLLBAR_SPACE, gridHeight).position(x, contentY + GRID_Y));
}
/**
* The left column: the player, and what they have equipped.
*/
private void drawStage() {
draw(new Stage().dimensions(STAGE_WIDTH, contentHeight).position(contentX, contentY));
int previewY = contentY + STAGE_PADDING + 24;
int previewHeight = contentHeight - STAGE_PADDING - 24 - STAGE_FOOTER;
draw(new SkinPreview(kind.yaw).dimensions(STAGE_WIDTH, previewHeight).position(contentX, previewY));
int footerY = contentY + contentHeight - STAGE_FOOTER + 8;
draw(new Label(() -> getName(kind.getEquipped()), 1f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
.position(contentX + STAGE_PADDING + 4, footerY));
draw(new Label(() -> kind.singular + " · Equipped", 0.64f, Theme::getBodyFont, Theme::getMutedFg)
.position(contentX + STAGE_PADDING + 4, footerY + 26));
}
private Element createGrid(int width) {
int tileWidth = (width - TILE_GAP * (COLUMNS - 1)) / COLUMNS;
AnimationStagger grid = new AnimationStagger(AnimationConfig.cosmeticsMenu);
List<String> ids = kind.ids();
for (int i = 0; i < ids.size(); i++) {
String id = ids.get(i);
Kind k = kind;
CosmeticTile tile = new CosmeticTile(getName(id), k.preview.apply(id), k.previewAspect, tileWidth,
() -> k.owned.get().contains(id),
() -> k.getEquipped().equals(id),
() -> k.equip.accept(id),
() -> k.claim.accept(id));
tile.position((i % COLUMNS) * (tileWidth + TILE_GAP), (i / COLUMNS) * (CosmeticTile.HEIGHT + TILE_GAP));
tile.animation(new SlideFade(AnimationConfig.cosmeticsMenu, -12));
grid.draw(tile);
}
return grid;
}
private String getOwnedSummary() {
// The owned list also holds "", for nothing equipped.
long owned = List.of(kind.all).stream().filter(kind.owned.get()::contains).count();
return owned + " of " + kind.all.length + " " + kind.title.toLowerCase(Locale.ROOT)
+ " owned · every cosmetic is free to claim";
}
private static String getName(String id) {
String name = NAMES.get(id);
if (name != null) {
return name;
}
StringBuilder builder = new StringBuilder();
for (String word : id.split("_")) {
if (!builder.isEmpty()) {
builder.append(' ');
}
builder.append(Character.toUpperCase(word.charAt(0))).append(word.substring(1));
}
return builder.toString();
}
/**
* The darker card behind the player, with its heading and a hint that it
* can be turned.
*/
private static class Stage extends Element {
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value + 4, 1,
Theme.withAlpha(0.45f, 0xFF000000), Theme.withAlpha(0.05f, Theme.FOREGROUND.value));
renderScope.drawText(SMALL_SCALE, "PREVIEW", STAGE_PADDING, STAGE_PADDING, Theme.FONT.value,
Theme.getMutedFg());
String hint = "Drag to rotate";
int hintWidth = (int) (Fonts.getWidth(hint, Theme.getBodyFont()) * SMALL_SCALE);
renderScope.drawText(SMALL_SCALE, hint, width - STAGE_PADDING - hintWidth, STAGE_PADDING,
Theme.getBodyFont(), Theme.getMutedFg());
renderScope.drawRectangle(STAGE_PADDING, height - STAGE_FOOTER, width - STAGE_PADDING * 2, 1,
Theme.getBorder());
}
}
}
@@ -11,7 +11,6 @@ import org.saturnclient.ui.elements.Button;
import org.saturnclient.ui.elements.ImageTexture; import org.saturnclient.ui.elements.ImageTexture;
import org.saturnclient.ui.elements.TextureButton; import org.saturnclient.ui.elements.TextureButton;
import org.saturnclient.ui.resources.Textures; import org.saturnclient.ui.resources.Textures;
import org.saturnclient.ui.screens.cosmetics.CloakMenu;
public class ShiftMenu extends SaturnScreen { public class ShiftMenu extends SaturnScreen {
public ShiftMenu() { public ShiftMenu() {
@@ -51,7 +50,7 @@ public class ShiftMenu extends SaturnScreen {
.animation(new Fade(AnimationConfig.shiftMenu))); .animation(new Fade(AnimationConfig.shiftMenu)));
stagger.draw(new TextureButton(Textures.COSMETICS, () -> { stagger.draw(new TextureButton(Textures.COSMETICS, () -> {
Providers.saturn.getClient().setScreen(new CloakMenu()); Providers.saturn.getClient().setScreen(new CosmeticsMenu());
}).dimensions(btnHeight, btnHeight).position(btnWidth + btnHeight + (spacing * 2), 0) }).dimensions(btnHeight, btnHeight).position(btnWidth + btnHeight + (spacing * 2), 0)
.animation(new Fade(AnimationConfig.shiftMenu))); .animation(new Fade(AnimationConfig.shiftMenu)));
@@ -14,7 +14,6 @@ import org.saturnclient.ui.elements.ImageTexture;
import org.saturnclient.ui.elements.TextureButton; import org.saturnclient.ui.elements.TextureButton;
import org.saturnclient.ui.resources.Fonts; import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.resources.Textures; import org.saturnclient.ui.resources.Textures;
import org.saturnclient.ui.screens.cosmetics.CloakMenu;
public class TitleMenu extends SaturnScreen { public class TitleMenu extends SaturnScreen {
public TitleMenu() { public TitleMenu() {
@@ -68,7 +67,7 @@ public class TitleMenu extends SaturnScreen {
.animation(new SlideFade(AnimationConfig.mainMenu, s + 8))); .animation(new SlideFade(AnimationConfig.mainMenu, s + 8)));
saturnStagger.draw(new TextureButton(Textures.CLOAK, () -> { saturnStagger.draw(new TextureButton(Textures.CLOAK, () -> {
Providers.saturn.getClient().setScreen(new CloakMenu()); Providers.saturn.getClient().setScreen(new CosmeticsMenu());
}).dimensions(s, s).position((s + 2) * 2, 0) }).dimensions(s, s).position((s + 2) * 2, 0)
.animation(new SlideFade(AnimationConfig.mainMenu, s + 8))); .animation(new SlideFade(AnimationConfig.mainMenu, s + 8)));
@@ -1,82 +0,0 @@
package org.saturnclient.ui.screens.cosmetics;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.common.provider.Providers;
import org.saturnclient.config.AnimationConfig;
import org.saturnclient.cosmetics.Cloaks;
import org.saturnclient.ui.SaturnScreen;
import org.saturnclient.ui.anim.Fade;
import org.saturnclient.ui.anim.SlideY;
import org.saturnclient.ui.components.CosmeticPreview;
import org.saturnclient.ui.components.Sidebar;
import org.saturnclient.ui.components.SkinPreview;
import org.saturnclient.ui.elements.AnimationStagger;
import org.saturnclient.ui.elements.Scroll;
import org.saturnclient.ui.elements.TabMenu;
import org.saturnclient.ui.elements.TabMenu.TabMenuComponent;
import org.saturnclient.ui.resources.Textures;
public class CloakMenu extends SaturnScreen {
public CloakMenu() {
super("Cloaks Menu");
}
@Override
public void ui() {
int p = 10;
int g = 10;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
SaturnPlayer player = SaturnPlayer.get();
AnimationStagger stagger = new AnimationStagger(AnimationConfig.cosmeticsMenu);
if (player != null) {
for (String cloak : Cloaks.availableCloaks) {
stagger.draw(
new CosmeticPreview(
cloak == player.cloak,
Textures.getCloakPreview(cloak),
() -> Cloaks.setCloak(cloak))
.position((50 + g) * col, (111 + g) * row)
.animation(new SlideY(AnimationConfig.cosmeticsMenu, 14)));
if (col == 5) {
col = 0;
row++;
} else {
col++;
}
}
}
scroll.draw(stagger);
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
draw(scroll
.dimensions(scrollWidth, 350)
.center(width, height));
draw(new SkinPreview(-30.0f, false)
.scale(3.5f)
.position(scroll.x + (scrollWidth - 220), scroll.y + 40)
.animation(new Fade(500)));
draw(new Sidebar(Sidebar.COSMETICS, this.provider::close)
.center(width, height, -(scrollWidth / 2 + 35), 0));
draw(new TabMenu(0,
new TabMenuComponent(Textures.CLOAK, () -> {
}),
new TabMenuComponent(Textures.HAT, () -> {
Providers.saturn.getClient().setScreen(new HatMenu());
}))
.center(width, height, 0, -195));
}
}
@@ -1,82 +0,0 @@
package org.saturnclient.ui.screens.cosmetics;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.common.provider.Providers;
import org.saturnclient.config.AnimationConfig;
import org.saturnclient.cosmetics.Hats;
import org.saturnclient.ui.SaturnScreen;
import org.saturnclient.ui.anim.Fade;
import org.saturnclient.ui.anim.SlideY;
import org.saturnclient.ui.components.CosmeticPreview;
import org.saturnclient.ui.components.Sidebar;
import org.saturnclient.ui.components.SkinPreview;
import org.saturnclient.ui.elements.AnimationStagger;
import org.saturnclient.ui.elements.Scroll;
import org.saturnclient.ui.elements.TabMenu;
import org.saturnclient.ui.elements.TabMenu.TabMenuComponent;
import org.saturnclient.ui.resources.Textures;
public class HatMenu extends SaturnScreen {
public HatMenu() {
super("Hats Menu");
}
@Override
public void ui() {
int p = 10;
int g = 10;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
SaturnPlayer player = SaturnPlayer.get();
AnimationStagger stagger = new AnimationStagger(AnimationConfig.cosmeticsMenu);
if (player != null) {
for (String hat : Hats.availableHats) {
stagger.draw(
new CosmeticPreview(
hat == player.hat,
Textures.getHatPreview(hat),
() -> Hats.setHat(hat))
.dimensions(50, 50)
.position((50 + g) * col, (50 + g) * row)
.animation(new SlideY(AnimationConfig.cosmeticsMenu, 14)));
if (col == 5) {
col = 0;
row++;
} else {
col++;
}
}
}
scroll.draw(stagger);
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
draw(scroll
.dimensions(scrollWidth, 350)
.center(width, height));
draw(new SkinPreview(170f, true)
.scale(3.5f)
.position(scroll.x + (scrollWidth - 220), scroll.y + 40)
.animation(new Fade(500)));
draw(new Sidebar(Sidebar.COSMETICS, this.provider::close)
.center(width, height, -(scrollWidth / 2 + 35), 0));
draw(new TabMenu(1,
new TabMenuComponent(Textures.CLOAK, () -> {
Providers.saturn.getClient().setScreen(new CloakMenu());
}),
new TabMenuComponent(Textures.HAT, () -> {
}))
.center(width, height, 0, -195));
}
}
@@ -1,142 +0,0 @@
package org.saturnclient.ui.screens.store;
// import org.saturnclient.cosmetics.cloak.Cloaks;
import org.saturnclient.client.ServiceClient;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.common.provider.Providers;
import org.saturnclient.config.AnimationConfig;
import org.saturnclient.config.Theme;
import org.saturnclient.cosmetics.Cloaks;
import org.saturnclient.ui.SaturnScreen;
import org.saturnclient.ui.anim.Fade;
import org.saturnclient.ui.anim.SlideY;
import org.saturnclient.ui.components.CosmeticPreview;
import org.saturnclient.ui.components.Sidebar;
import org.saturnclient.ui.elements.AnimationStagger;
import org.saturnclient.ui.elements.ImageTexture;
import org.saturnclient.ui.elements.Scroll;
import org.saturnclient.ui.elements.TabMenu;
import org.saturnclient.ui.elements.Text;
import org.saturnclient.ui.elements.TabMenu.TabMenuComponent;
import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.resources.Textures;
// import com.mojang.blaze3d.systems.RenderSystem;
public class CloakStore extends SaturnScreen {
private long lastPurchaseTime = 0;
public CloakStore() {
super("Cloak Store");
}
public CloakStore(long lastPurchaseTime) {
super("Cloak Store");
this.lastPurchaseTime = lastPurchaseTime;
}
@Override
public void ui() {
int p = 10;
int gx = 10;
int gy = 20;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
SaturnPlayer player = SaturnPlayer.get();
AnimationStagger stagger = new AnimationStagger(AnimationConfig.cosmeticsMenu);
if (player != null) {
for (String cloak : Cloaks.ALL_CLOAKS) {
if (!Cloaks.availableCloaks.contains(cloak)) {
int x = (50 + gx) * col;
int y = (111 + gy) * row;
stagger.draw(
new CosmeticPreview(
cloak == player.cloak,
Textures.getCloakPreview(cloak),
() -> handlePurchase(cloak))
.position(x, y)
.animation(new SlideY(AnimationConfig.cosmeticsMenu, 16)));
String t = "100";
stagger.draw(
new Text(t)
.position(x + Fonts.centerX(50, t, Theme.FONT.value), y + 113)
.scale(0.5f)
.animation(new Fade(400)));
stagger.draw(
new ImageTexture(Textures.COINS)
.dimensions(16, 16)
.position(x + Fonts.getWidth(t, Theme.FONT.value) - 1, y + 112)
.scale(0.5f)
.animation(new Fade(400)));
if (col == 8) {
col = 0;
row++;
} else {
col++;
}
}
}
}
scroll.draw(stagger);
int scrollWidth = 480 + 40 + (gx * 2) + (p * 2);
draw(scroll
.dimensions(scrollWidth, 350)
.center(width, height, 15, 0));
draw(new Sidebar(Sidebar.COSMETICS, this.provider::close)
.center(width, height, -((scrollWidth - 30) / 2 + 35), 0));
draw(new TabMenu(0,
new TabMenuComponent(Textures.CLOAK, () -> {
}),
new TabMenuComponent(Textures.HAT, () -> {
Providers.saturn.getClient().setScreen(new HatStore());
}))
.center(width, height, 0, -195));
}
private void handlePurchase(String cloak) {
long now = System.currentTimeMillis();
if (now - lastPurchaseTime >= 3000) {
// Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait");
ServiceClient.buyCloak(cloak);
lastPurchaseTime = now;
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
// RenderSystem.recordRenderCall(() -> {
// MinecraftProvider.PROVIDER.setScreen(new CloakStore(now));
// // Utils.notify(NotificationKind.Success,
// // "Purchase complete",
// // "Congrats, enjoy your new cloak!");
// });
}).start();
} else {
// Utils.notify(NotificationKind.Error,
// "Timeout error",
// "Please wait 3 seconds");
}
}
}
@@ -1,142 +0,0 @@
package org.saturnclient.ui.screens.store;
import org.saturnclient.client.ServiceClient;
import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.common.provider.Providers;
import org.saturnclient.config.AnimationConfig;
import org.saturnclient.config.Theme;
import org.saturnclient.cosmetics.Hats;
import org.saturnclient.ui.SaturnScreen;
import org.saturnclient.ui.anim.Fade;
import org.saturnclient.ui.anim.SlideY;
import org.saturnclient.ui.components.CosmeticPreview;
import org.saturnclient.ui.components.Sidebar;
import org.saturnclient.ui.elements.AnimationStagger;
import org.saturnclient.ui.elements.ImageTexture;
import org.saturnclient.ui.elements.Scroll;
import org.saturnclient.ui.elements.TabMenu;
import org.saturnclient.ui.elements.Text;
import org.saturnclient.ui.elements.TabMenu.TabMenuComponent;
import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.resources.Textures;
// import com.mojang.blaze3d.systems.RenderSystem;
public class HatStore extends SaturnScreen {
private long lastPurchaseTime = 0;
public HatStore() {
super("Hat Store");
}
public HatStore(long lastPurchaseTime) {
super("Hat Store");
this.lastPurchaseTime = lastPurchaseTime;
}
@Override
public void ui() {
int p = 10;
int gx = 10;
int gy = 20;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
SaturnPlayer player = SaturnPlayer.get();
AnimationStagger stagger = new AnimationStagger(AnimationConfig.cosmeticsMenu);
if (player != null) {
for (String hat : Hats.ALL_HATS) {
if (!Hats.availableHats.contains(hat)) {
int x = (50 + gx) * col;
int y = (50 + gy) * row;
stagger.draw(
new CosmeticPreview(
hat == player.hat,
Textures.getHatPreview(hat),
() -> handlePurchase(hat))
.dimensions(50, 50)
.position(x, y)
.animation(new SlideY(AnimationConfig.cosmeticsMenu, 16)));
String t = "50";
stagger.draw(
new Text(t)
.position(x + Fonts.centerX(50, t, Theme.FONT.value), y + 53)
.scale(0.5f)
.animation(new Fade(400)));
stagger.draw(
new ImageTexture(Textures.COINS)
.dimensions(16, 16)
.position(x + Fonts.getWidth(t, Theme.FONT.value) + 4, y + 52)
.scale(0.5f)
.animation(new Fade(400)));
if (col == 8) {
col = 0;
row++;
} else {
col++;
}
}
}
}
scroll.draw(stagger);
int scrollWidth = 480 + 40 + (gx * 2) + (p * 2);
draw(scroll
.dimensions(scrollWidth, 350)
.center(width, height, 15, 0));
draw(new Sidebar(Sidebar.COSMETICS, this.provider::close)
.center(width, height, -((scrollWidth - 30) / 2 + 35), 0));
draw(new TabMenu(1,
new TabMenuComponent(Textures.CLOAK, () -> {
Providers.saturn.getClient().setScreen(new CloakStore());
}),
new TabMenuComponent(Textures.HAT, () -> {
}))
.center(width, height, 0, -195));
}
private void handlePurchase(String hat) {
long now = System.currentTimeMillis();
if (now - lastPurchaseTime >= 3000) {
// Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait");
ServiceClient.buyHat(hat);
lastPurchaseTime = now;
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
// RenderSystem.recordRenderCall(() -> {
// MinecraftProvider.PROVIDER.setScreen(new HatStore(now));
// // Utils.notify(NotificationKind.Success,
// // "Purchase complete",
// // "Congrats, enjoy your new hat!");
// });
}).start();
} else {
// Utils.notify(NotificationKind.Error,
// "Timeout error",
// "Please wait 3 seconds");
}
}
}