Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef0ead6bf8 | ||
|
|
136621cc8d | ||
|
|
25d288da92 | ||
|
|
f0971ff128 | ||
|
|
2f4ea77e6f | ||
|
|
05dbec028a |
@@ -22,6 +22,12 @@ public interface SaturnProvider {
|
||||
/** The Saturn Client mod version, e.g. {@code 0.1.0-beta3+1.21.11}. */
|
||||
public String getModVersion();
|
||||
|
||||
/**
|
||||
* The signed-in player's 64x64 skin texture, or the default skin until
|
||||
* theirs has loaded.
|
||||
*/
|
||||
public IdentifierRef getPlayerSkin();
|
||||
|
||||
/** Shows a warning toast. Safe to call from any thread. */
|
||||
public void showWarning(String title, String message);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.Optional;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.emotes.core.Emote;
|
||||
import org.saturnclient.emotes.core.EmoteRegistry;
|
||||
import org.saturnclient.ui.Utils;
|
||||
|
||||
/**
|
||||
* Saturn's emotes, loaded from {@code assets/saturnclient/player_animations/<id>.json} (Emotecraft
|
||||
@@ -23,6 +24,13 @@ public class Emotes {
|
||||
/** The emotes that loaded, in wheel order. */
|
||||
public static List<String> availableEmotes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The emote's name as shown to players.
|
||||
*/
|
||||
public static String getName(String id) {
|
||||
return id.equals("tpose") ? "T-Pose" : Utils.nameFromId(id);
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
for (String id : IDS) {
|
||||
String path = "/assets/saturnclient/player_animations/" + id + ".json";
|
||||
|
||||
@@ -11,7 +11,6 @@ public class Element {
|
||||
public boolean focused;
|
||||
public Animation animation;
|
||||
public float scale = 1.0f;
|
||||
public Integer duration = null; // Element duration, will fade out if not null
|
||||
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.saturnclient.ui;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.common.provider.GLFWProvider;
|
||||
|
||||
public class Utils {
|
||||
@@ -47,17 +45,21 @@ public class Utils {
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> T getOrNull(List<T> list, int index) {
|
||||
return (index >= 0 && index < list.size()) ? list.get(index) : null;
|
||||
/**
|
||||
* A display name from an id: each underscore-separated word capitalized, so
|
||||
* "black_hole_white" becomes "Black Hole White".
|
||||
*/
|
||||
public static String nameFromId(String id) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String word : id.split("_")) {
|
||||
if (word.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!builder.isEmpty()) {
|
||||
builder.append(' ');
|
||||
}
|
||||
builder.append(Character.toUpperCase(word.charAt(0))).append(word.substring(1));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// public static void notify(NotificationKind kind, String title, String toast)
|
||||
// {
|
||||
// if (SaturnClient.client.currentScreen instanceof SaturnScreen) {
|
||||
// ((SaturnScreen) SaturnClient.client.currentScreen)
|
||||
// .draw(new Notification(SaturnClient.client.currentScreen.width,
|
||||
// SaturnClient.client.currentScreen.height, kind,
|
||||
// title, toast));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.saturnclient.ui.anim;
|
||||
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui.Element;
|
||||
|
||||
public class FadeOut extends Animation {
|
||||
float targetOpacity;
|
||||
|
||||
public FadeOut(int duration) {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public FadeOut(AnimationConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(double progress, Element element) {
|
||||
element.opacity = targetOpacity - (float) progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Element element) {
|
||||
targetOpacity = element.opacity;
|
||||
// element.opacity = 1.0f;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.saturnclient.ui.anim;
|
||||
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui.Element;
|
||||
|
||||
public class SlideX extends Animation {
|
||||
int offset = 20;
|
||||
double startX;
|
||||
|
||||
public SlideX(AnimationConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
public SlideX(AnimationConfig config, int offset) {
|
||||
super(config);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public SlideX(int duration) {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public SlideX(int duration, int offset) {
|
||||
super(duration);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Element element) {
|
||||
startX = element.x - offset;
|
||||
element.x = (int) startX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(double progress, Element element) {
|
||||
double currentX = startX + offset * progress;
|
||||
element.x = (int) currentX;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.elements.Field;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
/**
|
||||
* The signed-in player: their skin's face, their name, and a dot that's green
|
||||
* while connected to Saturn.
|
||||
*/
|
||||
public class AccountChip extends Element {
|
||||
public static final int HEIGHT = 40;
|
||||
|
||||
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 CONNECTED = 0xFF7FD1A8;
|
||||
|
||||
private final String name;
|
||||
|
||||
public AccountChip() {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1, Theme.getPanelBg(),
|
||||
Theme.getBorder());
|
||||
|
||||
// The face, then the hat layer over it, from the 64x64 skin
|
||||
IdentifierRef skin = Providers.saturn.getPlayerSkin();
|
||||
int headY = (height - HEAD) / 2;
|
||||
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;
|
||||
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;
|
||||
renderScope.drawRoundedRectangle(dotX, (height - DOT) / 2, DOT, DOT, DOT / 2,
|
||||
ServiceClient.isConnected() ? CONNECTED : Theme.withAlpha(0.3f, Theme.FOREGROUND.value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A glass bar of square icon buttons.
|
||||
*/
|
||||
public class Dock extends Element {
|
||||
public record Item(IdentifierRef icon, Runnable onClick) {
|
||||
}
|
||||
|
||||
public static final int HEIGHT = 52;
|
||||
|
||||
private static final int PADDING = 6;
|
||||
private static final int ITEM = HEIGHT - PADDING * 2;
|
||||
private static final int ICON = 18;
|
||||
private static final int GAP = 4;
|
||||
|
||||
private final List<Item> items;
|
||||
|
||||
public Dock(List<Item> items) {
|
||||
this.items = items;
|
||||
this.height = HEIGHT;
|
||||
this.width = PADDING * 2 + items.size() * ITEM + (items.size() - 1) * GAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value + 4, 1, Theme.getPanelBg(),
|
||||
Theme.withAlpha(0.1f, Theme.FOREGROUND.value));
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
int x = getItemX(i);
|
||||
boolean hovered = ctx.isHovering(x, PADDING, ITEM, ITEM);
|
||||
|
||||
if (hovered) {
|
||||
renderScope.drawRoundedRectangle(x, PADDING, ITEM, ITEM, Theme.WIDGET_RADIUS.value,
|
||||
Theme.withAlpha(0.08f, Theme.FOREGROUND.value));
|
||||
}
|
||||
|
||||
int offset = (ITEM - ICON) / 2;
|
||||
renderScope.drawTexture(items.get(i).icon(), x + offset, PADDING + offset, 0, 0, ICON, ICON,
|
||||
hovered ? Theme.ACCENT.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
int x = getItemX(i);
|
||||
if (mouseX >= x && mouseX <= x + ITEM) {
|
||||
items.get(i).onClick().run();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getItemX(int index) {
|
||||
return PADDING + index * (ITEM + GAP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.cosmetics.Emotes;
|
||||
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 emote in the emote wheel: its preview in its own colors and its name.
|
||||
* An emote without a preview shows an icon instead.
|
||||
*/
|
||||
public class EmoteTile extends Element {
|
||||
public static final int SIZE = 86;
|
||||
|
||||
private static final int INSET = 6;
|
||||
private static final int NAME_AREA = 20;
|
||||
private static final int PLACEHOLDER = 26;
|
||||
private static final float NAME_SCALE = 0.56f;
|
||||
|
||||
private final String name;
|
||||
/** The preview, or null when the emote has none */
|
||||
private final IdentifierRef preview;
|
||||
private final Runnable onPlay;
|
||||
|
||||
public EmoteTile(String emote, Runnable onPlay) {
|
||||
IdentifierRef texture = Textures.getEmotePreview(emote);
|
||||
|
||||
this.name = Emotes.getName(emote);
|
||||
this.preview = Textures.exists(texture) ? texture : null;
|
||||
this.onPlay = onPlay;
|
||||
this.width = SIZE;
|
||||
this.height = SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
boolean hovered = ctx.isHovering();
|
||||
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, hovered ? 2 : 1,
|
||||
Theme.withAlpha(0.72f, Theme.PRIMARY.value),
|
||||
hovered ? Theme.ACCENT.value : Theme.withAlpha(0.06f, Theme.FOREGROUND.value));
|
||||
|
||||
int imageSize = height - INSET * 2 - NAME_AREA;
|
||||
|
||||
if (preview != null) {
|
||||
renderScope.drawTexture(preview, (width - imageSize) / 2, INSET, 0, 0, imageSize, imageSize);
|
||||
} else {
|
||||
int iconY = INSET + (imageSize - PLACEHOLDER) / 2;
|
||||
renderScope.drawTexture(Textures.ICON_EMOTE, (width - PLACEHOLDER) / 2, iconY, 0, 0, PLACEHOLDER,
|
||||
PLACEHOLDER, Theme.getMutedFg());
|
||||
}
|
||||
|
||||
String text = Fonts.ellipsize(name, Theme.FONT.value, NAME_SCALE, width - INSET * 2);
|
||||
int textX = (width - (int) (Fonts.getWidth(text, Theme.FONT.value) * NAME_SCALE)) / 2;
|
||||
int textY = height - INSET - NAME_AREA + (NAME_AREA - (int) (Fonts.getHeight() * NAME_SCALE)) / 2;
|
||||
renderScope.drawText(NAME_SCALE, text, textX, textY, Theme.FONT.value,
|
||||
hovered ? Theme.FOREGROUND.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
onPlay.run();
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,21 @@ public class HudModPreview extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope scope, ElementContext ctx) {
|
||||
ModLayout layout = feature.getDimensions();
|
||||
draw(scope, feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a HUD mod with sample content and the background it has in game,
|
||||
* at the origin and its layout's unscaled size.
|
||||
*/
|
||||
public static void draw(RenderScope scope, HudMod mod) {
|
||||
ModLayout layout = mod.getDimensions();
|
||||
|
||||
if (layout.renderBackground) {
|
||||
scope.drawRoundedRectangle(0, 0, layout.width, layout.height,
|
||||
layout.radius.value, layout.bgColor.value);
|
||||
}
|
||||
|
||||
feature.renderDummy(scope);
|
||||
mod.renderDummy(scope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,41 @@ import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
/**
|
||||
* A text button: a translucent, outlined surface that brightens on hover.
|
||||
*/
|
||||
public class Button extends Element {
|
||||
private String text;
|
||||
private Runnable onClick;
|
||||
public static final int HEIGHT = 40;
|
||||
|
||||
private static final float TEXT_SCALE = 0.72f;
|
||||
|
||||
private final String text;
|
||||
private final Runnable onClick;
|
||||
|
||||
public Button(String text, Runnable onClick) {
|
||||
this.text = text;
|
||||
this.onClick = onClick;
|
||||
this.dimensions(Fonts.getWidth(text, Theme.FONT.value) + 50, 38);
|
||||
this.width = (int) (Fonts.getWidth(text, Theme.FONT.value) * TEXT_SCALE) + 48;
|
||||
this.height = HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||
boolean hovered = ctx.isHovering();
|
||||
drawSurface(renderScope, width, height, hovered);
|
||||
|
||||
renderScope.drawText(text, Fonts.centerX(width, text, Theme.FONT.value), Fonts.centerY(height),
|
||||
Theme.FONT.value,
|
||||
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
|
||||
int textX = (width - (int) (Fonts.getWidth(text, Theme.FONT.value) * TEXT_SCALE)) / 2;
|
||||
renderScope.drawText(TEXT_SCALE, text, textX, Field.textY(height, TEXT_SCALE), Theme.FONT.value,
|
||||
hovered ? Theme.FOREGROUND.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(),
|
||||
Theme.withAlpha(hovered ? 0.25f : 0.1f, Theme.FOREGROUND.value));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,30 +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;
|
||||
|
||||
public class ClickableTexture extends Element {
|
||||
IdentifierRef sprite;
|
||||
private Runnable onClick;
|
||||
|
||||
public ClickableTexture(IdentifierRef sprite, Runnable onClick) {
|
||||
this.sprite = sprite;
|
||||
this.onClick = onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height,
|
||||
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
if (onClick != null) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* A square {@link Button} showing an icon, which turns the accent color on
|
||||
* hover.
|
||||
*/
|
||||
public class IconButton extends Element {
|
||||
private static final float ICON_RATIO = 0.48f;
|
||||
|
||||
private final IdentifierRef icon;
|
||||
private final Runnable onClick;
|
||||
|
||||
public IconButton(IdentifierRef icon, Runnable onClick) {
|
||||
this.icon = icon;
|
||||
this.onClick = onClick;
|
||||
this.width = Button.HEIGHT;
|
||||
this.height = Button.HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
boolean hovered = ctx.isHovering();
|
||||
Button.drawSurface(renderScope, width, height, hovered);
|
||||
|
||||
int size = (int) (Math.min(width, height) * ICON_RATIO);
|
||||
renderScope.drawTexture(icon, (width - size) / 2, (height - size) / 2, 0, 0, size, size,
|
||||
hovered ? Theme.ACCENT.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.ColorProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.anim.SlideX;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
public class Notification extends Element {
|
||||
private static ColorProperty error = Property.color(0xFFbf212f);
|
||||
private static ColorProperty info = Property.color(0xFF264b96);
|
||||
private static ColorProperty success = Property.color(0xFF27b376);
|
||||
|
||||
public static enum NotificationKind {
|
||||
Error,
|
||||
Info,
|
||||
Success,
|
||||
}
|
||||
|
||||
NotificationKind kind;
|
||||
String text;
|
||||
String title;
|
||||
|
||||
public Notification(int width, int height, NotificationKind kind, String title, String text) {
|
||||
this.kind = kind;
|
||||
this.text = text;
|
||||
this.title = title;
|
||||
this.width = (int) Math.max(Fonts.getWidth(text, 1) * 0.6f, Fonts.getWidth(title, 1)) + 20;
|
||||
this.height = 50;
|
||||
this.x = width - this.width - 15;
|
||||
this.y = height - this.height - 15;
|
||||
this.animation = new SlideX(700, -width);
|
||||
this.duration = 3000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, 10, Theme.PRIMARY.value);
|
||||
switch (kind) {
|
||||
case Error:
|
||||
renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value);
|
||||
break;
|
||||
|
||||
case Info:
|
||||
renderScope.drawTexture(Textures.INFO, 9, 9, 0, 0, 15, 15, info.value);
|
||||
break;
|
||||
|
||||
case Success:
|
||||
renderScope.drawTexture(Textures.CHECK, 9, 9, 0, 0, 15, 15, success.value);
|
||||
break;
|
||||
}
|
||||
renderScope.drawText(0.7f, title, 32, 10, 1, -1);
|
||||
renderScope.drawText(0.5f, text, 32, 28, 1, -1);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
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;
|
||||
|
||||
public class Text extends Element {
|
||||
private String text;
|
||||
|
||||
public Text(String text) {
|
||||
this.text = text;
|
||||
width = Fonts.getWidth(text, Theme.FONT.value);
|
||||
height = Fonts.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawText(this.text, 0, 0, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +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;
|
||||
|
||||
public class TextureButton extends Element {
|
||||
IdentifierRef sprite;
|
||||
private Runnable onClick;
|
||||
static int padding = 14;
|
||||
|
||||
public TextureButton(IdentifierRef sprite, Runnable onClick) {
|
||||
this.sprite = sprite;
|
||||
this.onClick = onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||
ctx.isHovering() ? 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,
|
||||
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
if (onClick != null) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,14 @@
|
||||
package org.saturnclient.ui.resources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
|
||||
public class Textures {
|
||||
public static final IdentifierRef CROSSHAIR_RANGE = IdentifierRef
|
||||
.ofSaturn("textures/gui/hud/crosshair_range.png");
|
||||
public static final IdentifierRef COSMETICS = IdentifierRef.ofSaturn("textures/gui/components/cosmetics.png");
|
||||
public static final IdentifierRef HUD_ICON = IdentifierRef.ofSaturn("textures/gui/components/hud.png");
|
||||
public static final IdentifierRef SEARCH = IdentifierRef.ofSaturn("textures/gui/components/search.png");
|
||||
public static final IdentifierRef CLOSE = IdentifierRef.ofSaturn("textures/gui/components/close.png");
|
||||
public static final IdentifierRef MODS_TAB = IdentifierRef.ofSaturn("textures/gui/components/mods_tab.png");
|
||||
public static final IdentifierRef SETTINGS = IdentifierRef.ofSaturn("textures/gui/components/settings.png");
|
||||
public static final IdentifierRef CLOAK = IdentifierRef.ofSaturn("textures/gui/components/cloak.png");
|
||||
public static final IdentifierRef HAT = IdentifierRef.ofSaturn("textures/gui/components/hat.png");
|
||||
public static final IdentifierRef RESET = IdentifierRef.ofSaturn("textures/gui/components/reset.png");
|
||||
public static final IdentifierRef TOGGLE_INDICATOR = IdentifierRef
|
||||
.ofSaturn("textures/gui/components/toggle_indicator.png");
|
||||
public static final IdentifierRef LEFT = IdentifierRef.ofSaturn("textures/gui/components/left.png");
|
||||
public static final IdentifierRef RIGHT = IdentifierRef.ofSaturn("textures/gui/components/right.png");
|
||||
public static final IdentifierRef STORE = IdentifierRef.ofSaturn("textures/gui/components/store.png");
|
||||
public static final IdentifierRef COINS = IdentifierRef.ofSaturn("textures/gui/components/coins.png");
|
||||
public static final IdentifierRef SHIRT = IdentifierRef.ofSaturn("textures/gui/components/shirt.png");
|
||||
public static final IdentifierRef CIRCLE_X = IdentifierRef.ofSaturn("textures/gui/components/circle_x.png");
|
||||
public static final IdentifierRef INFO = IdentifierRef.ofSaturn("textures/gui/components/info.png");
|
||||
public static final IdentifierRef CHECK = IdentifierRef.ofSaturn("textures/gui/components/check.png");
|
||||
|
||||
// Lucide icons (SVG, drawn in white so they can be tinted)
|
||||
public static final IdentifierRef ICON_MODS = IdentifierRef.ofSaturn("textures/gui/components/mods.svg");
|
||||
@@ -43,6 +28,7 @@ public class Textures {
|
||||
.ofSaturn("textures/gui/components/chevron_right.svg");
|
||||
public static final IdentifierRef ICON_CHEVRON_DOWN = IdentifierRef
|
||||
.ofSaturn("textures/gui/components/chevron_down.svg");
|
||||
public static final IdentifierRef ICON_EMOTE = IdentifierRef.ofSaturn("textures/gui/components/emote.svg");
|
||||
public static final IdentifierRef ICON_CHECK = IdentifierRef.ofSaturn("textures/gui/components/check.svg");
|
||||
|
||||
public static final IdentifierRef LOGO = IdentifierRef.ofSaturn("textures/logo/logo.png");
|
||||
@@ -51,15 +37,17 @@ public class Textures {
|
||||
public static final IdentifierRef LOGO_TEXT_BIG = IdentifierRef.ofSaturn("textures/logo/text_big.png");
|
||||
public static final IdentifierRef SPLASH = IdentifierRef.ofSaturn("textures/gui/splash.png");
|
||||
|
||||
// Widgets
|
||||
public static final IdentifierRef BUTTON_BORDER = IdentifierRef.ofSaturn("button_border");
|
||||
public static final IdentifierRef BUTTON = IdentifierRef.ofSaturn("button");
|
||||
public static final IdentifierRef BUTTON_OP = IdentifierRef.ofSaturn("button_op");
|
||||
public static final IdentifierRef SETTINGS_BG = IdentifierRef.ofSaturn("settings_bg");
|
||||
public static final IdentifierRef RECT = IdentifierRef.ofSaturn("rect");
|
||||
public static final IdentifierRef RECT_BORDER = IdentifierRef.ofSaturn("rect_border");
|
||||
public static final IdentifierRef MOD = IdentifierRef.ofSaturn("textures/gui/components/mod.png");
|
||||
public static final IdentifierRef MOD_BG = IdentifierRef.ofSaturn("textures/gui/components/mod_bg.png");
|
||||
/**
|
||||
* Whether a texture file is in the resources, for falling back when an
|
||||
* optional one (like an emote's preview) is missing.
|
||||
*/
|
||||
public static boolean exists(IdentifierRef texture) {
|
||||
try (InputStream in = Providers.saturn.getClient().getResource(texture)) {
|
||||
return in != null;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IdentifierRef getCloakPreview(String cloak) {
|
||||
return IdentifierRef.ofSaturn("textures/gui/cloak/" + (cloak.isEmpty() ? "none" : cloak) + ".png");
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.PanelScreen;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.Utils;
|
||||
import org.saturnclient.ui.anim.SlideFade;
|
||||
import org.saturnclient.ui.components.CosmeticTile;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
@@ -193,18 +194,7 @@ public class CosmeticsMenu extends PanelScreen {
|
||||
|
||||
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();
|
||||
return name != null ? name : Utils.nameFromId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,21 +1,43 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import org.saturnclient.cosmetics.Emotes;
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.client.ServiceClient;
|
||||
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.Theme;
|
||||
import org.saturnclient.cosmetics.Emotes;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.Utils;
|
||||
import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
import org.saturnclient.ui.components.EmoteTile;
|
||||
import org.saturnclient.ui.elements.AnimationStagger;
|
||||
import org.saturnclient.ui.elements.ImageTexture;
|
||||
import org.saturnclient.ui.elements.TextureButton;
|
||||
import org.saturnclient.ui.elements.Label;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
* A 3x3 wheel of emotes to play, with the page controls in the middle. The
|
||||
* emote wheel key opens and closes it (in the version's key handler).
|
||||
*/
|
||||
public class EmoteWheel extends SaturnScreen {
|
||||
int page = 1;
|
||||
private static final int GAP = 10;
|
||||
private static final int GRID = EmoteTile.SIZE * 3 + GAP * 2;
|
||||
private static final int LOGO = 48;
|
||||
|
||||
/** The grid's cells in the order emotes fill them, around the middle one */
|
||||
private static final int[][] SLOTS = {
|
||||
{ 0, 0 }, { 1, 0 }, { 2, 0 },
|
||||
{ 0, 1 }, { 2, 1 },
|
||||
{ 0, 2 }, { 1, 2 }, { 2, 2 } };
|
||||
|
||||
private int page = 0;
|
||||
|
||||
public EmoteWheel() {
|
||||
super("Emote Wheel");
|
||||
@@ -23,88 +45,131 @@ public class EmoteWheel extends SaturnScreen {
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
|
||||
// =============================
|
||||
// Logo
|
||||
// =============================
|
||||
|
||||
draw(new ImageTexture(Textures.LOGO_TEXT)
|
||||
.dimensions(98, 10)
|
||||
.center(width, height, 0, -105)
|
||||
.animation(new Fade(AnimationConfig.logo.duration.value)));
|
||||
List<String> emotes = Emotes.availableEmotes;
|
||||
int left = (width - GRID) / 2;
|
||||
int top = (height - GRID) / 2 + LOGO / 2;
|
||||
|
||||
draw(new ImageTexture(Config.getLogo())
|
||||
.dimensions(98, 98)
|
||||
.center(width, height, 0, -149)
|
||||
.animation(new SlideY(AnimationConfig.logo, -20)));
|
||||
.dimensions(LOGO, LOGO)
|
||||
.position((width - LOGO) / 2, top - 20 - LOGO)
|
||||
.animation(new SlideY(AnimationConfig.logo, -12)));
|
||||
|
||||
// =============================
|
||||
// Navigation Buttons
|
||||
// =============================
|
||||
|
||||
draw(new TextureButton(Textures.LEFT, () -> {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
elements.clear();
|
||||
ui();
|
||||
}
|
||||
}).dimensions(30, 30)
|
||||
.center(width, height, -50, 105)
|
||||
.animation(new Fade(500)));
|
||||
|
||||
draw(new TextureButton(Textures.RIGHT, () -> {
|
||||
if ((page * 8) < Emotes.availableEmotes.size()) {
|
||||
page++;
|
||||
elements.clear();
|
||||
ui();
|
||||
}
|
||||
}).dimensions(30, 30)
|
||||
.center(width, height, 50, 105)
|
||||
.animation(new Fade(500)));
|
||||
|
||||
// =============================
|
||||
// Emote Grid (Staggered)
|
||||
// =============================
|
||||
|
||||
AnimationStagger emoteStagger = new AnimationStagger(AnimationConfig.emoteWheel);
|
||||
|
||||
while (row < 3) {
|
||||
|
||||
int idx = (row * 3 + col) + ((page - 1) * 8);
|
||||
|
||||
if (row == 1 && col == 1) {
|
||||
col++;
|
||||
}
|
||||
|
||||
String emote = Utils.getOrNull(Emotes.availableEmotes, idx);
|
||||
|
||||
emoteStagger.draw(
|
||||
new TextureButton(Textures.getEmotePreview(emote), () -> {
|
||||
Providers.saturn.playEmote(ServiceClient.uuid, emote);
|
||||
ServiceClient.emote(emote);
|
||||
provider.close();
|
||||
})
|
||||
.dimensions(70, 70)
|
||||
.center(width, height,
|
||||
-80 + (col * 80),
|
||||
-35 + (row * 80))
|
||||
.animation(new SlideY(AnimationConfig.emoteWheel, 12)));
|
||||
|
||||
if (col < 2) {
|
||||
col++;
|
||||
} else {
|
||||
col = 0;
|
||||
row++;
|
||||
}
|
||||
if (emotes.isEmpty()) {
|
||||
String message = "No emotes available";
|
||||
int x = (width - (int) (Fonts.getWidth(message, Theme.getBodyFont()) * 0.7f)) / 2;
|
||||
draw(new Label(() -> message, 0.7f, Theme::getBodyFont, Theme::getMutedFg).position(x, top + GRID / 2));
|
||||
return;
|
||||
}
|
||||
|
||||
draw(emoteStagger);
|
||||
int pages = (emotes.size() + SLOTS.length - 1) / SLOTS.length;
|
||||
page = Math.min(page, pages - 1);
|
||||
|
||||
AnimationStagger grid = new AnimationStagger(AnimationConfig.emoteWheel);
|
||||
|
||||
for (int slot = 0; slot < SLOTS.length; slot++) {
|
||||
int index = page * SLOTS.length + slot;
|
||||
if (index >= emotes.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
String emote = emotes.get(index);
|
||||
grid.draw(new EmoteTile(emote, () -> play(emote))
|
||||
.position(SLOTS[slot][0] * (EmoteTile.SIZE + GAP), SLOTS[slot][1] * (EmoteTile.SIZE + GAP))
|
||||
.animation(new SlideY(AnimationConfig.emoteWheel, 12)));
|
||||
}
|
||||
|
||||
draw(grid.position(left, top));
|
||||
|
||||
draw(new PageControl(page, pages, () -> turnPage(-1), () -> turnPage(1))
|
||||
.position(left + EmoteTile.SIZE + GAP, top + EmoteTile.SIZE + GAP)
|
||||
.animation(new Fade(AnimationConfig.emoteWheel)));
|
||||
}
|
||||
|
||||
private void play(String emote) {
|
||||
Providers.saturn.playEmote(ServiceClient.uuid, emote);
|
||||
ServiceClient.emote(emote);
|
||||
provider.close();
|
||||
}
|
||||
|
||||
private void turnPage(int direction) {
|
||||
page += direction;
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPause() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The middle of the wheel: "Emotes", and the page with arrows to turn it
|
||||
* when there's more than one.
|
||||
*/
|
||||
private static class PageControl extends Element {
|
||||
private static final int ARROW = 16;
|
||||
private static final int ARROW_AREA = 26;
|
||||
|
||||
private final int page;
|
||||
private final int pages;
|
||||
private final Runnable onPrevious;
|
||||
private final Runnable onNext;
|
||||
|
||||
PageControl(int page, int pages, Runnable onPrevious, Runnable onNext) {
|
||||
this.page = page;
|
||||
this.pages = pages;
|
||||
this.onPrevious = onPrevious;
|
||||
this.onNext = onNext;
|
||||
this.width = EmoteTile.SIZE;
|
||||
this.height = EmoteTile.SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||
Theme.getPanelBg(), Theme.getBorder());
|
||||
|
||||
float titleScale = 0.54f;
|
||||
drawCentered(renderScope, "EMOTES", titleScale, pages > 1 ? 24 : (height - 10) / 2, Theme.getMutedFg());
|
||||
|
||||
if (pages <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawCentered(renderScope, (page + 1) + " / " + pages, 0.66f, 48, Theme.FOREGROUND.value);
|
||||
|
||||
int arrowY = 48 + ((int) (Fonts.getHeight() * 0.66f) - ARROW) / 2;
|
||||
drawArrow(renderScope, ctx, Textures.ICON_CHEVRON_LEFT, 4, arrowY, canGoBack());
|
||||
drawArrow(renderScope, ctx, Textures.ICON_CHEVRON_RIGHT, width - 4 - ARROW, arrowY, canGoForward());
|
||||
}
|
||||
|
||||
private void drawArrow(RenderScope renderScope, ElementContext ctx, IdentifierRef icon, int x, int y,
|
||||
boolean enabled) {
|
||||
boolean hovered = enabled && ctx.isHovering(x - 5, 0, ARROW_AREA, height);
|
||||
int color = !enabled ? Theme.withAlpha(0.2f, Theme.FOREGROUND.value)
|
||||
: hovered ? Theme.ACCENT.value : Theme.getMutedFg();
|
||||
renderScope.drawTexture(icon, x, y, 0, 0, ARROW, ARROW, color);
|
||||
}
|
||||
|
||||
private void drawCentered(RenderScope renderScope, String text, float scale, int y, int color) {
|
||||
int x = (width - (int) (Fonts.getWidth(text, Theme.FONT.value) * scale)) / 2;
|
||||
renderScope.drawText(scale, text, x, y, Theme.FONT.value, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
if (mouseX < ARROW_AREA && canGoBack()) {
|
||||
onPrevious.run();
|
||||
} else if (mouseX > width - ARROW_AREA && canGoForward()) {
|
||||
onNext.run();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canGoBack() {
|
||||
return page > 0;
|
||||
}
|
||||
|
||||
private boolean canGoForward() {
|
||||
return page < pages - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,71 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import org.saturnclient.common.ref.render.MatrixStackRef;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.mod.HudMod;
|
||||
import org.saturnclient.mod.Mod;
|
||||
import org.saturnclient.mod.ModLayout;
|
||||
import org.saturnclient.mod.ModManager;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.components.HudModPreview;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public class HudEditor extends SaturnScreen {
|
||||
private static final String HINT = "Drag to move · Drag the corner to resize · Esc when done";
|
||||
|
||||
/** Size of the resize handle, and how far around it still grabs it */
|
||||
private static final int HANDLE = 4;
|
||||
private static final int HANDLE_REACH = 4;
|
||||
private static final float LABEL_SCALE = 0.4f;
|
||||
private static final float HINT_SCALE = 0.45f;
|
||||
|
||||
private final List<HudMod> hudMods = new ArrayList<>();
|
||||
|
||||
private HudMod draggingMod = null;
|
||||
private boolean resizing = false;
|
||||
private int offsetX, offsetY;
|
||||
private static final int RESIZE_MARGIN = 3;
|
||||
|
||||
public HudEditor() {
|
||||
super("HUD Editor");
|
||||
for (Mod m : ModManager.ENABLED_MODS) {
|
||||
if (m.isEnabled() && m instanceof HudMod) {
|
||||
hudMods.add((HudMod) m);
|
||||
if (m.isEnabled() && m instanceof HudMod hudMod) {
|
||||
hudMods.add(hudMod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
// Placing HUDs needs a clear view of the game.
|
||||
blurBackground = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (button == 0) {
|
||||
for (int m = hudMods.size() - 1; m >= 0; m--) {
|
||||
HudMod mod = hudMods.get(m);
|
||||
HudMod mod = getModAt(mouseX, mouseY);
|
||||
|
||||
if (mod != null) {
|
||||
ModLayout dim = mod.getDimensions();
|
||||
|
||||
// Check if clicking the corner for resizing
|
||||
if (isCorner(mouseX, mouseY, dim)) {
|
||||
draggingMod = mod;
|
||||
resizing = true;
|
||||
offsetX = (int) (mouseX - (dim.x.value + dim.width * dim.scale.value));
|
||||
offsetY = (int) (mouseY - (dim.y.value + dim.height * dim.scale.value));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if clicking inside the mod
|
||||
if (isInside(mouseX, mouseY, dim)) {
|
||||
draggingMod = mod;
|
||||
resizing = false;
|
||||
offsetX = (int) (mouseX - dim.x.value);
|
||||
offsetY = (int) (mouseY - dim.y.value);
|
||||
return true;
|
||||
}
|
||||
draggingMod = mod;
|
||||
resizing = isOnHandle(mouseX, mouseY, dim);
|
||||
offsetX = (int) (mouseX - (resizing ? right(dim) : dim.x.value));
|
||||
offsetY = (int) (mouseY - (resizing ? bottom(dim) : dim.y.value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
@@ -72,57 +84,121 @@ public class HudEditor extends SaturnScreen {
|
||||
ModLayout dim = draggingMod.getDimensions();
|
||||
|
||||
if (resizing) {
|
||||
// Scale based on cursor movement
|
||||
float newScale = Math.max(0.5f, ((float) (mouseX - dim.x.value) / dim.width));
|
||||
dim.scale.value = newScale;
|
||||
// The handle follows the cursor: the scale is the new width over the unscaled width.
|
||||
float scale = (float) (mouseX - offsetX - dim.x.value) / dim.width;
|
||||
float min = dim.scale.min != null ? dim.scale.min : 0.5f;
|
||||
float max = dim.scale.max != null ? dim.scale.max : 4f;
|
||||
dim.scale.value = Math.max(min, Math.min(max, scale));
|
||||
} else {
|
||||
// Move the mod
|
||||
dim.x.value = (int) mouseX - offsetX;
|
||||
dim.y.value = (int) mouseY - offsetY;
|
||||
}
|
||||
|
||||
keepOnScreen(dim);
|
||||
}
|
||||
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
for (HudMod mod : hudMods) {
|
||||
ModLayout dim = mod.getDimensions();
|
||||
MatrixStackRef matrices = renderScope.getMatrixStack();
|
||||
matrices.push();
|
||||
boolean active = mod == hovered;
|
||||
|
||||
matrices.translate(dim.x.value, dim.y.value);
|
||||
matrices.scale(dim.scale.value, dim.scale.value);
|
||||
renderScope.getMatrixStack().push();
|
||||
renderScope.getMatrixStack().translate(dim.x.value, dim.y.value);
|
||||
renderScope.getMatrixStack().scale(dim.scale.value, dim.scale.value);
|
||||
HudModPreview.draw(renderScope, mod);
|
||||
renderScope.getMatrixStack().pop();
|
||||
|
||||
// Render the mod
|
||||
mod.renderDummy(renderScope);
|
||||
drawFrame(renderScope, mod, dim, active);
|
||||
}
|
||||
|
||||
matrices.pop();
|
||||
drawHint(renderScope);
|
||||
}
|
||||
|
||||
// Draw border around mod
|
||||
int borderX = dim.x.value;
|
||||
int borderY = dim.y.value;
|
||||
int borderW = (int) (dim.width * dim.scale.value);
|
||||
int borderH = (int) (dim.height * dim.scale.value);
|
||||
/**
|
||||
* The outline around a HUD, its resize handle, and its name while it's
|
||||
* hovered or dragged.
|
||||
*/
|
||||
private void drawFrame(RenderScope renderScope, HudMod mod, ModLayout dim, boolean active) {
|
||||
int x = dim.x.value;
|
||||
int y = dim.y.value;
|
||||
int w = right(dim) - x;
|
||||
int h = bottom(dim) - y;
|
||||
int color = active ? Theme.ACCENT.value : Theme.withAlpha(0.45f, Theme.FOREGROUND.value);
|
||||
|
||||
renderScope.drawBorder(borderX, borderY, borderW, borderH, 0xFFFFFFFF); // White border
|
||||
if (active) {
|
||||
renderScope.drawRectangle(x, y, w, h, Theme.withAlpha(0.08f, Theme.ACCENT.value));
|
||||
}
|
||||
|
||||
renderScope.drawBorder(x - 1, y - 1, w + 2, h + 2, color);
|
||||
renderScope.drawRectangle(x + w - HANDLE / 2, y + h - HANDLE / 2, HANDLE, HANDLE, color);
|
||||
|
||||
if (active && mod instanceof Mod named) {
|
||||
int labelHeight = (int) (Fonts.getHeight() * LABEL_SCALE);
|
||||
int labelY = y - labelHeight - 4 < 0 ? y + h + 4 : y - labelHeight - 4;
|
||||
renderScope.drawText(LABEL_SCALE, named.getName(), x, labelY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInside(double mouseX, double mouseY, ModLayout dim) {
|
||||
return mouseX >= dim.x.value && mouseX <= dim.x.value + (dim.width * dim.scale.value) &&
|
||||
mouseY >= dim.y.value && mouseY <= dim.y.value + (dim.height * dim.scale.value);
|
||||
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 y = 8;
|
||||
|
||||
renderScope.drawRoundedRectangle(x, y, pillWidth, pillHeight, pillHeight / 2, 1, Theme.getPanelBg(),
|
||||
Theme.getBorder());
|
||||
renderScope.drawText(HINT_SCALE, HINT, x + 10, y + 5, Theme.getBodyFont(), Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
private boolean isCorner(double mouseX, double mouseY, ModLayout dim) {
|
||||
float rightX = dim.x.value + dim.width * dim.scale.value;
|
||||
float bottomY = dim.y.value + dim.height * dim.scale.value;
|
||||
/**
|
||||
* The HUD under the mouse, preferring the one drawn on top, or null.
|
||||
*/
|
||||
private HudMod getModAt(double mouseX, double mouseY) {
|
||||
for (int i = hudMods.size() - 1; i >= 0; i--) {
|
||||
ModLayout dim = hudMods.get(i).getDimensions();
|
||||
if (isOnHandle(mouseX, mouseY, dim) || isInside(mouseX, mouseY, dim)) {
|
||||
return hudMods.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.abs(mouseX - rightX) < RESIZE_MARGIN && Math.abs(mouseY - bottomY) < RESIZE_MARGIN;
|
||||
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));
|
||||
}
|
||||
|
||||
private static int right(ModLayout dim) {
|
||||
return dim.x.value + (int) (dim.width * dim.scale.value);
|
||||
}
|
||||
|
||||
private static int bottom(ModLayout dim) {
|
||||
return dim.y.value + (int) (dim.height * dim.scale.value);
|
||||
}
|
||||
|
||||
private static boolean isInside(double mouseX, double mouseY, ModLayout dim) {
|
||||
return mouseX >= dim.x.value && mouseX <= right(dim) && mouseY >= dim.y.value && mouseY <= bottom(dim);
|
||||
}
|
||||
|
||||
private static boolean isOnHandle(double mouseX, double mouseY, ModLayout dim) {
|
||||
return Math.abs(mouseX - right(dim)) <= HANDLE_REACH && Math.abs(mouseY - bottom(dim)) <= HANDLE_REACH;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,18 +8,24 @@ import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
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.TextureButton;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
* The in-game quick menu: the logo over the HUD editor, mod menu and
|
||||
* cosmetics.
|
||||
*/
|
||||
public class ShiftMenu extends SaturnScreen {
|
||||
private static final int MODS_WIDTH = 120;
|
||||
private static final int GAP = 6;
|
||||
|
||||
public ShiftMenu() {
|
||||
super("Shift Menu");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
// Logo animations (unchanged)
|
||||
draw(new ImageTexture(Textures.LOGO_TEXT)
|
||||
.dimensions(98, 10)
|
||||
.center(width, height, 0, -36)
|
||||
@@ -30,32 +36,24 @@ public class ShiftMenu extends SaturnScreen {
|
||||
.center(width, height, 0, -80)
|
||||
.animation(new SlideY(AnimationConfig.logo, -20)));
|
||||
|
||||
// Spacing and sizes
|
||||
int btnWidth = 120;
|
||||
int btnHeight = 40;
|
||||
int spacing = 4;
|
||||
|
||||
// Create stagger
|
||||
|
||||
AnimationStagger stagger = new AnimationStagger(AnimationConfig.shiftMenu.stagger.value);
|
||||
int x = 0;
|
||||
|
||||
stagger.draw(new TextureButton(Textures.HUD_ICON, () -> {
|
||||
Providers.saturn.getClient().setScreen(new HudEditor());
|
||||
}).dimensions(btnHeight, btnHeight).position(0, 0)
|
||||
.animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
stagger.draw(new IconButton(Textures.ICON_HUD_EDITOR,
|
||||
() -> Providers.saturn.getClient().setScreen(new HudEditor()))
|
||||
.position(x, 0).animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
x += Button.HEIGHT + GAP;
|
||||
|
||||
stagger.draw(new Button("Settings", () -> {
|
||||
Providers.saturn.getClient().setScreen(new ModMenu());
|
||||
}).dimensions(btnWidth, btnHeight).position(btnHeight + spacing, 0)
|
||||
.animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
stagger.draw(new Button("Mods", () -> Providers.saturn.getClient().setScreen(new ModMenu()))
|
||||
.dimensions(MODS_WIDTH, Button.HEIGHT)
|
||||
.position(x, 0).animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
x += MODS_WIDTH + GAP;
|
||||
|
||||
stagger.draw(new TextureButton(Textures.COSMETICS, () -> {
|
||||
Providers.saturn.getClient().setScreen(new CosmeticsMenu());
|
||||
}).dimensions(btnHeight, btnHeight).position(btnWidth + btnHeight + (spacing * 2), 0)
|
||||
.animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
stagger.draw(new IconButton(Textures.ICON_COSMETICS,
|
||||
() -> Providers.saturn.getClient().setScreen(new CosmeticsMenu()))
|
||||
.position(x, 0).animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
x += Button.HEIGHT;
|
||||
|
||||
// Draw the stagger at the absolute center of the screen
|
||||
draw(stagger.dimensions(btnWidth + (btnHeight * 2) + (spacing * 2), btnHeight).center(width, height, 0,
|
||||
0));
|
||||
draw(stagger.position((width - x) / 2, (height - Button.HEIGHT) / 2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideFade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
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.ImageTexture;
|
||||
import org.saturnclient.ui.elements.TextureButton;
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
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;
|
||||
|
||||
public TitleMenu() {
|
||||
super("Title Menu");
|
||||
}
|
||||
@@ -24,53 +43,72 @@ public class TitleMenu extends SaturnScreen {
|
||||
public void ui() {
|
||||
blurBackground = false;
|
||||
|
||||
draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).center(width, height, 0, -40)
|
||||
.animation(new Fade(AnimationConfig.logo.duration.value)));
|
||||
draw(new ImageTexture(Config.getLogo()).dimensions(98, 98).center(width, height, 0, -84)
|
||||
// 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 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)
|
||||
.dimensions(WORDMARK_WIDTH, WORDMARK_HEIGHT)
|
||||
.position((width - WORDMARK_WIDTH) / 2, top + LOGO + 16)
|
||||
.animation(new Fade(AnimationConfig.logo.duration.value)));
|
||||
|
||||
AnimationStagger mainButtonStagger = new AnimationStagger(AnimationConfig.mainMenu);
|
||||
int rowHeight = BUTTON_HEIGHT + GAP;
|
||||
int halfWidth = (COLUMN_WIDTH - GAP) / 2;
|
||||
AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu);
|
||||
|
||||
mainButtonStagger.draw(new Button("SINGLEPLAYER", () -> {
|
||||
Providers.saturn.getClient().setScreen(MinecraftScreen.SelectWorld);
|
||||
}).scale(0.69f).dimensions(420, 52).position(0, 0)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
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));
|
||||
|
||||
mainButtonStagger.draw(new Button("MULTIPLAYER", () -> {
|
||||
Providers.saturn.getClient().setScreen(MinecraftScreen.Multiplayer);
|
||||
}).scale(0.69f).dimensions(420, 52).position(0, 40)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
draw(menus.position((width - COLUMN_WIDTH) / 2, top + LOGO + 16 + WORDMARK_HEIGHT + 36));
|
||||
|
||||
mainButtonStagger.draw(new Button("OPTIONS", () -> {
|
||||
Providers.saturn.getClient().setScreen(MinecraftScreen.Options);
|
||||
}).scale(0.69f).dimensions(206, 52).position(0, 80)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
// Along the bottom: the player, Saturn's screens, the version
|
||||
draw(new AccountChip()
|
||||
.position(EDGE, height - EDGE - AccountChip.HEIGHT)
|
||||
.animation(new Fade(AnimationConfig.mainMenu)));
|
||||
|
||||
mainButtonStagger.draw(new Button("QUIT", () -> {
|
||||
Providers.saturn.getClient().scheduleStop();
|
||||
}).scale(0.69f).dimensions(206, 52).position(146, 80)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
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()))));
|
||||
draw(dock.position((width - dock.width) / 2, height - EDGE - Dock.HEIGHT)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, 12)));
|
||||
|
||||
draw(mainButtonStagger.dimensions(289, 143).center(width, height, 0, 50));
|
||||
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)));
|
||||
}
|
||||
|
||||
int s = Fonts.getHeight() + 20;
|
||||
/**
|
||||
* "Saturn Client 0.1.0 · Minecraft 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;
|
||||
}
|
||||
|
||||
AnimationStagger saturnStagger = new AnimationStagger(AnimationConfig.mainMenu.stagger.value);
|
||||
|
||||
saturnStagger.draw(new TextureButton(Textures.HUD_ICON, () -> {
|
||||
Providers.saturn.getClient().setScreen(new HudEditor());
|
||||
}).dimensions(s, s).position(0, 0).animation(new SlideFade(AnimationConfig.mainMenu, s + 8)));
|
||||
|
||||
saturnStagger.draw(new TextureButton(Textures.SETTINGS, () -> {
|
||||
Providers.saturn.getClient().setScreen(new ModMenu());
|
||||
}).dimensions(s, s).position(s + 2, 0)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, s + 8)));
|
||||
|
||||
saturnStagger.draw(new TextureButton(Textures.CLOAK, () -> {
|
||||
Providers.saturn.getClient().setScreen(new CosmeticsMenu());
|
||||
}).dimensions(s, s).position((s + 2) * 2, 0)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, s + 8)));
|
||||
|
||||
draw(saturnStagger.dimensions(((s + 2) * 2) + s, s).centerHorizontal(width, height, 0, 8));
|
||||
private static void addMenuButton(AnimationStagger stagger, Element button) {
|
||||
stagger.draw(button.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/></svg>
|
||||
|
After Width: | Height: | Size: 328 B |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 645 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 771 B |
|
Before Width: | Height: | Size: 357 B |
|
Before Width: | Height: | Size: 523 B |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 884 B |
|
Before Width: | Height: | Size: 958 B |