Redesign the mod menu, settings and cosmetics screens #6
@@ -12,6 +12,12 @@ public class BoolProperty extends Property {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.value = defaultValue;
|
||||||
|
this.isReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode toJson() {
|
public JsonNode toJson() {
|
||||||
return JsonNodeFactory.instance.booleanNode(this.value);
|
return JsonNodeFactory.instance.booleanNode(this.value);
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ public class ColorProperty extends Property {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.value = defaultValue;
|
||||||
|
this.isReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode toJson() {
|
public JsonNode toJson() {
|
||||||
return JsonNodeFactory.instance.textNode(
|
return JsonNodeFactory.instance.textNode(
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ public class FloatProperty extends Property {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.value = defaultValue;
|
||||||
|
this.isReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode toJson() {
|
public JsonNode toJson() {
|
||||||
return JsonNodeFactory.instance.numberNode(this.value);
|
return JsonNodeFactory.instance.numberNode(this.value);
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ public class IntProperty extends Property {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.value = defaultValue;
|
||||||
|
this.isReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode toJson() {
|
public JsonNode toJson() {
|
||||||
return JsonNodeFactory.instance.numberNode(this.value);
|
return JsonNodeFactory.instance.numberNode(this.value);
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ public class KeybindingProperty extends Property {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.value = defaultValue;
|
||||||
|
this.isReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode toJson() {
|
public JsonNode toJson() {
|
||||||
return JsonNodeFactory.instance.numberNode(this.value);
|
return JsonNodeFactory.instance.numberNode(this.value);
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ public class NamespaceProperty extends Property {
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.value.values().forEach(Property::reset);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode toJson() {
|
public JsonNode toJson() {
|
||||||
ObjectNode nested = JsonNodeFactory.instance.objectNode();
|
ObjectNode nested = JsonNodeFactory.instance.objectNode();
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ public class SelectProperty extends Property {
|
|||||||
this.availableValues = availableValues;
|
this.availableValues = availableValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.value = defaultValue;
|
||||||
|
this.isReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode toJson() {
|
public JsonNode toJson() {
|
||||||
return JsonNodeFactory.instance.numberNode(this.value);
|
return JsonNodeFactory.instance.numberNode(this.value);
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ public class StringProperty extends Property {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.value = defaultValue;
|
||||||
|
this.isReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode toJson() {
|
public JsonNode toJson() {
|
||||||
return JsonNodeFactory.instance.textNode(this.value);
|
return JsonNodeFactory.instance.textNode(this.value);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.saturnclient.mod;
|
package org.saturnclient.mod;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.saturnclient.config.property.Property;
|
import org.saturnclient.config.property.Property;
|
||||||
@@ -23,14 +24,17 @@ public class ModLayout {
|
|||||||
public int height = 0;
|
public int height = 0;
|
||||||
|
|
||||||
public NamedProperty prop() {
|
public NamedProperty prop() {
|
||||||
return Property.namespace(Map.of(
|
// A LinkedHashMap, unlike Map.of, keeps the order the settings are shown in.
|
||||||
"X", x,
|
Map<String, Property> props = new LinkedHashMap<>();
|
||||||
"Y", y,
|
props.put("X", x);
|
||||||
"Scale", scale,
|
props.put("Y", y);
|
||||||
"Background Color", bgColor,
|
props.put("Scale", scale);
|
||||||
"Foreground Color", fgColor,
|
props.put("Background Color", bgColor);
|
||||||
"Corner Radius", radius,
|
props.put("Foreground Color", fgColor);
|
||||||
"Font", font)).named("In-Game Display");
|
props.put("Corner Radius", radius);
|
||||||
|
props.put("Font", font);
|
||||||
|
|
||||||
|
return Property.namespace(props).named("In-Game Display");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModLayout() {
|
public ModLayout() {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.saturnclient.ui;
|
||||||
|
|
||||||
|
import org.saturnclient.ui.components.Sidebar;
|
||||||
|
import org.saturnclient.ui.elements.ImageTexture;
|
||||||
|
import org.saturnclient.ui.elements.Panel;
|
||||||
|
import org.saturnclient.ui.resources.Textures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A screen laid out as the sidebar beside one panel, centered and shrunk to
|
||||||
|
* fit smaller windows. Subclasses fill the panel in {@link #panel()}, using the
|
||||||
|
* content bounds this class computes.
|
||||||
|
*/
|
||||||
|
public abstract class PanelScreen extends SaturnScreen {
|
||||||
|
private static final int MAX_WIDTH = 900;
|
||||||
|
private static final int MAX_HEIGHT = 580;
|
||||||
|
private static final int MARGIN = 40;
|
||||||
|
private static final int GAP = 12;
|
||||||
|
|
||||||
|
protected static final int PADDING = 20;
|
||||||
|
|
||||||
|
private final int sidebarItem;
|
||||||
|
|
||||||
|
protected int panelX, panelY, panelWidth, panelHeight;
|
||||||
|
/** The panel's area inside its padding */
|
||||||
|
protected int contentX, contentY, contentWidth, contentHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sidebarItem the {@link Sidebar} item to highlight
|
||||||
|
*/
|
||||||
|
public PanelScreen(String title, int sidebarItem) {
|
||||||
|
super(title);
|
||||||
|
this.sidebarItem = sidebarItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void ui() {
|
||||||
|
panelWidth = Math.min(MAX_WIDTH, width - MARGIN * 2 - Sidebar.WIDTH - GAP);
|
||||||
|
panelHeight = Math.min(MAX_HEIGHT, height - MARGIN * 2);
|
||||||
|
|
||||||
|
int left = (width - (Sidebar.WIDTH + GAP + panelWidth)) / 2;
|
||||||
|
panelX = left + Sidebar.WIDTH + GAP;
|
||||||
|
panelY = (height - panelHeight) / 2;
|
||||||
|
|
||||||
|
contentX = panelX + PADDING;
|
||||||
|
contentY = panelY + PADDING;
|
||||||
|
contentWidth = panelWidth - PADDING * 2;
|
||||||
|
contentHeight = panelHeight - PADDING * 2;
|
||||||
|
|
||||||
|
draw(new Sidebar(sidebarItem, this.provider::close)
|
||||||
|
.dimensions(Sidebar.WIDTH, panelHeight)
|
||||||
|
.position(left, panelY));
|
||||||
|
|
||||||
|
draw(new Panel().dimensions(panelWidth, panelHeight).position(panelX, panelY));
|
||||||
|
|
||||||
|
panel();
|
||||||
|
|
||||||
|
draw(new ImageTexture(Textures.LOGO_TEXT_BIG).dimensions(180, 18).position(width - 180 - 20, height - 18 - 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the panel's content.
|
||||||
|
*/
|
||||||
|
protected abstract void panel();
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.saturnclient.ui.components;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Theme;
|
||||||
|
import org.saturnclient.mod.HudMod;
|
||||||
|
import org.saturnclient.mod.ModLayout;
|
||||||
|
import org.saturnclient.ui.Element;
|
||||||
|
import org.saturnclient.ui.ElementContext;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A card showing a HUD mod as it looks in game, following its settings live.
|
||||||
|
* The mod is drawn at its in-game scale, shrunk if needed to fit the card.
|
||||||
|
*/
|
||||||
|
public class HudPreviewCard extends Element {
|
||||||
|
private static final int PADDING = 16;
|
||||||
|
private static final int TITLE_HEIGHT = 24;
|
||||||
|
private static final float TITLE_SCALE = 0.6f;
|
||||||
|
|
||||||
|
private final HudMod mod;
|
||||||
|
private final HudModPreview preview;
|
||||||
|
|
||||||
|
public HudPreviewCard(HudMod mod) {
|
||||||
|
this.mod = mod;
|
||||||
|
this.preview = new HudModPreview(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
|
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value + 4, 1,
|
||||||
|
Theme.withAlpha(0.72f, Theme.PRIMARY.value), Theme.withAlpha(0.06f, Theme.FOREGROUND.value));
|
||||||
|
|
||||||
|
renderScope.drawText(TITLE_SCALE, "PREVIEW", PADDING, PADDING, Theme.FONT.value, Theme.getMutedFg());
|
||||||
|
|
||||||
|
ModLayout layout = mod.getDimensions();
|
||||||
|
if (layout.width <= 0 || layout.height <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HUD mods are laid out in GUI pixels, which are two Saturn units.
|
||||||
|
int areaWidth = width - PADDING * 2;
|
||||||
|
int areaHeight = height - PADDING * 2 - TITLE_HEIGHT;
|
||||||
|
float scale = Math.min(layout.scale.value * 2,
|
||||||
|
Math.min((float) areaWidth / layout.width, (float) areaHeight / layout.height));
|
||||||
|
|
||||||
|
float x = PADDING + (areaWidth - layout.width * scale) / 2;
|
||||||
|
float y = PADDING + TITLE_HEIGHT + (areaHeight - layout.height * scale) / 2;
|
||||||
|
|
||||||
|
renderScope.getMatrixStack().push();
|
||||||
|
renderScope.getMatrixStack().translate(x, y);
|
||||||
|
renderScope.getMatrixStack().scale(scale, scale);
|
||||||
|
preview.render(renderScope, ctx);
|
||||||
|
renderScope.getMatrixStack().pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.saturnclient.ui.components;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Theme;
|
||||||
|
import org.saturnclient.mod.Mod;
|
||||||
|
import org.saturnclient.ui.Element;
|
||||||
|
import org.saturnclient.ui.ElementContext;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The top of a mod's settings: its icon, name, version and tags, and description.
|
||||||
|
*/
|
||||||
|
public class ModHeader extends Element {
|
||||||
|
public static final int HEIGHT = 60;
|
||||||
|
|
||||||
|
private static final int ICON = 30;
|
||||||
|
private static final int TEXT_X = HEIGHT + 16;
|
||||||
|
private static final float NAME_SCALE = 1.2f;
|
||||||
|
private static final float DESCRIPTION_SCALE = 0.7f;
|
||||||
|
|
||||||
|
private static final int CHIP_HEIGHT = 22;
|
||||||
|
private static final int CHIP_PADDING = 8;
|
||||||
|
private static final int CHIP_GAP = 6;
|
||||||
|
private static final float CHIP_SCALE = 0.58f;
|
||||||
|
|
||||||
|
private final Mod mod;
|
||||||
|
|
||||||
|
public ModHeader(Mod mod, int width) {
|
||||||
|
this.mod = mod;
|
||||||
|
this.width = width;
|
||||||
|
this.height = HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
|
boolean enabled = mod.isEnabled();
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
renderScope.drawRoundedRectangle(0, 0, HEIGHT, HEIGHT, Theme.WIDGET_RADIUS.value + 4,
|
||||||
|
enabled ? Theme.withAlpha(0.18f, Theme.ACCENT.value) : Theme.withAlpha(0.06f, Theme.FOREGROUND.value));
|
||||||
|
renderScope.drawTexture(mod.getIconTexture(), (HEIGHT - ICON) / 2, (HEIGHT - ICON) / 2, 0, 0, ICON, ICON,
|
||||||
|
Theme.PRIMARY_FG.value);
|
||||||
|
|
||||||
|
// Name, then the version and tags as chips
|
||||||
|
int nameHeight = (int) (Fonts.getHeight() * NAME_SCALE);
|
||||||
|
int nameY = 4;
|
||||||
|
renderScope.drawText(NAME_SCALE, mod.getName(), TEXT_X, nameY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
|
|
||||||
|
int chipX = TEXT_X + (int) (Fonts.getWidth(mod.getName(), Theme.FONT.value) * NAME_SCALE) + 12;
|
||||||
|
int chipY = nameY + (nameHeight - CHIP_HEIGHT) / 2;
|
||||||
|
|
||||||
|
if (mod.getVersion() != null) {
|
||||||
|
chipX = drawChip(renderScope, mod.getVersion(), chipX, chipY, false) + CHIP_GAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String tag : mod.getTags()) {
|
||||||
|
chipX = drawChip(renderScope, tag, chipX, chipY, true) + CHIP_GAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description
|
||||||
|
if (mod.getDescription() != null) {
|
||||||
|
String description = Fonts.ellipsize(mod.getDescription(), Theme.getBodyFont(), DESCRIPTION_SCALE,
|
||||||
|
width - TEXT_X);
|
||||||
|
renderScope.drawText(DESCRIPTION_SCALE, description, TEXT_X, nameY + nameHeight + 8,
|
||||||
|
Theme.getBodyFont(), Theme.getMutedFg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the x after the chip
|
||||||
|
*/
|
||||||
|
private int drawChip(RenderScope renderScope, String text, int x, int y, boolean outlined) {
|
||||||
|
int width = CHIP_PADDING * 2 + (int) (Fonts.getWidth(text, Theme.FONT.value) * CHIP_SCALE);
|
||||||
|
|
||||||
|
if (x + width > this.width) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outlined) {
|
||||||
|
renderScope.drawRoundedBorder(x, y, width, CHIP_HEIGHT, 6, 1, Theme.withAlpha(0.14f, Theme.FOREGROUND.value));
|
||||||
|
} else {
|
||||||
|
renderScope.drawRoundedRectangle(x, y, width, CHIP_HEIGHT, 6, Theme.withAlpha(0.07f, Theme.FOREGROUND.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
int textY = y + (CHIP_HEIGHT - (int) (Fonts.getHeight() * CHIP_SCALE)) / 2;
|
||||||
|
renderScope.drawText(CHIP_SCALE, text, x + CHIP_PADDING, textY, Theme.FONT.value,
|
||||||
|
outlined ? Theme.PRIMARY_FG.value : Theme.getMutedFg());
|
||||||
|
|
||||||
|
return x + width;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package org.saturnclient.ui.components;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Theme;
|
||||||
|
import org.saturnclient.config.property.BoolProperty;
|
||||||
|
import org.saturnclient.config.property.ColorProperty;
|
||||||
|
import org.saturnclient.config.property.FloatProperty;
|
||||||
|
import org.saturnclient.config.property.IntProperty;
|
||||||
|
import org.saturnclient.config.property.KeybindingProperty;
|
||||||
|
import org.saturnclient.config.property.NamespaceProperty;
|
||||||
|
import org.saturnclient.config.property.Property;
|
||||||
|
import org.saturnclient.config.property.SelectProperty;
|
||||||
|
import org.saturnclient.ui.Element;
|
||||||
|
import org.saturnclient.ui.ElementContext;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.components.inputs.ColorInput;
|
||||||
|
import org.saturnclient.ui.components.inputs.FloatInput;
|
||||||
|
import org.saturnclient.ui.components.inputs.IntInput;
|
||||||
|
import org.saturnclient.ui.components.inputs.KeybindingSelector;
|
||||||
|
import org.saturnclient.ui.components.inputs.Select;
|
||||||
|
import org.saturnclient.ui.components.inputs.Toggle;
|
||||||
|
import org.saturnclient.ui.elements.Label;
|
||||||
|
import org.saturnclient.ui.elements.Scroll;
|
||||||
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lays out config properties as rows of name and control, one per property.
|
||||||
|
* A nested namespace becomes a titled section.
|
||||||
|
*/
|
||||||
|
public final class PropertyList {
|
||||||
|
public static final int ROW_HEIGHT = 56;
|
||||||
|
|
||||||
|
private static final int SECTION_GAP = 18;
|
||||||
|
private static final float SECTION_SCALE = 0.6f;
|
||||||
|
|
||||||
|
private PropertyList() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds rows for {@code properties} to {@code scroll}, starting at {@code y}.
|
||||||
|
*
|
||||||
|
* @param skip names of top-level properties to leave out
|
||||||
|
* @return the y below the last row
|
||||||
|
*/
|
||||||
|
public static int draw(Scroll scroll, Map<String, Property> properties, Set<String> skip, int width, int y) {
|
||||||
|
// Controls go on after every row, so an open color picker covers the rows below it.
|
||||||
|
List<Element> controls = new ArrayList<>();
|
||||||
|
y = drawRows(scroll, properties, skip, width, y, controls);
|
||||||
|
controls.forEach(scroll::draw);
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int drawRows(Scroll scroll, Map<String, Property> properties, Set<String> skip, int width, int y,
|
||||||
|
List<Element> controls) {
|
||||||
|
for (Map.Entry<String, Property> entry : properties.entrySet()) {
|
||||||
|
String name = entry.getKey();
|
||||||
|
Property prop = entry.getValue();
|
||||||
|
|
||||||
|
if (skip.contains(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop instanceof NamespaceProperty namespace) {
|
||||||
|
y += SECTION_GAP;
|
||||||
|
String title = name.toUpperCase(Locale.ROOT);
|
||||||
|
scroll.draw(new Label(() -> title, SECTION_SCALE, () -> Theme.FONT.value, Theme::getMutedFg)
|
||||||
|
.position(0, y));
|
||||||
|
y += (int) (Fonts.getHeight() * SECTION_SCALE) + 4;
|
||||||
|
y = drawRows(scroll, namespace.value, Set.of(), width, y, controls);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Element control = createControl(prop);
|
||||||
|
if (control == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll.draw(new Row(name, width, control.width).position(0, y));
|
||||||
|
controls.add(control.position(width - control.width, y + (ROW_HEIGHT - control.height) / 2));
|
||||||
|
y += ROW_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The input for a property, or null for types without one (strings).
|
||||||
|
*/
|
||||||
|
private static Element createControl(Property prop) {
|
||||||
|
if (prop instanceof BoolProperty p) {
|
||||||
|
return new Toggle(p);
|
||||||
|
} else if (prop instanceof ColorProperty p) {
|
||||||
|
return new ColorInput(p);
|
||||||
|
} else if (prop instanceof IntProperty p) {
|
||||||
|
return new IntInput(p);
|
||||||
|
} else if (prop instanceof FloatProperty p) {
|
||||||
|
return new FloatInput(p);
|
||||||
|
} else if (prop instanceof SelectProperty p) {
|
||||||
|
return new Select(p);
|
||||||
|
} else if (prop instanceof KeybindingProperty p) {
|
||||||
|
return new KeybindingSelector(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A row's name and the line under it. The control is a separate element on
|
||||||
|
* top, so it gets the clicks and key presses.
|
||||||
|
*/
|
||||||
|
private static class Row extends Element {
|
||||||
|
private static final float NAME_SCALE = 0.74f;
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final int controlWidth;
|
||||||
|
|
||||||
|
Row(String name, int width, int controlWidth) {
|
||||||
|
this.name = name;
|
||||||
|
this.controlWidth = controlWidth;
|
||||||
|
this.width = width;
|
||||||
|
this.height = ROW_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
|
int nameY = (height - (int) (Fonts.getHeight() * NAME_SCALE)) / 2;
|
||||||
|
String text = Fonts.ellipsize(name, Theme.FONT.value, NAME_SCALE, width - controlWidth - 16);
|
||||||
|
|
||||||
|
renderScope.drawText(NAME_SCALE, text, 0, nameY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
|
renderScope.drawRectangle(0, height - 1, width, 1, Theme.withAlpha(0.06f, Theme.FOREGROUND.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package org.saturnclient.ui.components;
|
|||||||
|
|
||||||
import org.saturnclient.common.provider.Providers;
|
import org.saturnclient.common.provider.Providers;
|
||||||
import org.saturnclient.config.Theme;
|
import org.saturnclient.config.Theme;
|
||||||
import org.saturnclient.mod.HudMod;
|
|
||||||
import org.saturnclient.mod.Mod;
|
import org.saturnclient.mod.Mod;
|
||||||
import org.saturnclient.ui.Element;
|
import org.saturnclient.ui.Element;
|
||||||
import org.saturnclient.ui.ElementContext;
|
import org.saturnclient.ui.ElementContext;
|
||||||
@@ -10,7 +9,7 @@ import org.saturnclient.ui.RenderScope;
|
|||||||
import org.saturnclient.ui.components.inputs.Toggle;
|
import org.saturnclient.ui.components.inputs.Toggle;
|
||||||
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.ConfigEditor;
|
import org.saturnclient.ui.screens.ModSettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mod's card in the mod menu: icon, name, description, a settings button and
|
* A mod's card in the mod menu: icon, name, description, a settings button and
|
||||||
@@ -101,11 +100,7 @@ public class SaturnModule extends Element {
|
|||||||
&& mouseY >= getSettingsY() && mouseY <= getSettingsY() + SETTINGS;
|
&& mouseY >= getSettingsY() && mouseY <= getSettingsY() + SETTINGS;
|
||||||
|
|
||||||
if (settingsClicked) {
|
if (settingsClicked) {
|
||||||
if (mod instanceof HudMod h) {
|
Providers.saturn.getClient().setScreen(new ModSettings(mod));
|
||||||
Providers.saturn.getClient().setScreen(new ConfigEditor(mod.getConfig(), h));
|
|
||||||
} else {
|
|
||||||
Providers.saturn.getClient().setScreen(new ConfigEditor(mod.getConfig()));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
mod.setEnabled(!mod.isEnabled());
|
mod.setEnabled(!mod.isEnabled());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package org.saturnclient.ui.components.inputs;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Theme;
|
||||||
import org.saturnclient.config.property.ColorProperty;
|
import org.saturnclient.config.property.ColorProperty;
|
||||||
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.elements.Field;
|
||||||
|
|
||||||
public class ColorInput extends Element {
|
public class ColorInput extends Element {
|
||||||
public ColorProperty prop;
|
public ColorProperty prop;
|
||||||
@@ -26,10 +28,19 @@ public class ColorInput extends Element {
|
|||||||
private static final int SLIDER_HEIGHT = 10;
|
private static final int SLIDER_HEIGHT = 10;
|
||||||
private static final int HUE_SLIDER_HEIGHT = 10;
|
private static final int HUE_SLIDER_HEIGHT = 10;
|
||||||
|
|
||||||
|
// The picker opens to the left, since the input sits at the right of its row.
|
||||||
|
private static final int PICKER_X = -PICKER_WIDTH - 12;
|
||||||
|
private static final int SWATCH = 20;
|
||||||
|
|
||||||
|
// Bounds of the picker's backdrop, relative to the color square
|
||||||
|
private static final int BACKDROP_PADDING = 8;
|
||||||
|
private static final int BACKDROP_TOP = -SLIDER_HEIGHT - 10 - BACKDROP_PADDING;
|
||||||
|
private static final int BACKDROP_BOTTOM = PICKER_HEIGHT + 10 + HUE_SLIDER_HEIGHT + BACKDROP_PADDING;
|
||||||
|
|
||||||
public ColorInput(ColorProperty prop) {
|
public ColorInput(ColorProperty prop) {
|
||||||
this.prop = prop;
|
this.prop = prop;
|
||||||
this.width = 58;
|
this.width = 116;
|
||||||
this.height = 20;
|
this.height = Field.HEIGHT;
|
||||||
syncFromColor();
|
syncFromColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,15 +51,23 @@ public class ColorInput extends Element {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
if (open) {
|
if (!open) {
|
||||||
handlePickerClick(mouseX - width - 8, mouseY);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean onInput = mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height;
|
||||||
|
if (!onInput && !isOverPicker(mouseX - PICKER_X, mouseY)) {
|
||||||
|
open = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePickerClick(mouseX - PICKER_X, mouseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||||
if (open) {
|
if (open) {
|
||||||
handlePickerDrag(mouseX - width - 8, mouseY);
|
handlePickerDrag(mouseX - PICKER_X, mouseY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,16 +78,26 @@ public class ColorInput extends Element {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
// Draw the main color rectangle
|
Field.drawBox(renderScope, width, height, ctx.isHovering(), open);
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, 6, prop.value);
|
|
||||||
|
int swatchY = (height - SWATCH) / 2;
|
||||||
|
renderScope.drawRoundedRectangle(swatchY, swatchY, SWATCH, SWATCH, 5, 1, prop.value,
|
||||||
|
Theme.withAlpha(0.3f, Theme.FOREGROUND.value));
|
||||||
|
|
||||||
|
renderScope.drawText(Field.TEXT_SCALE, toHex(prop.value), swatchY + SWATCH + 8,
|
||||||
|
Field.textY(height, Field.TEXT_SCALE), Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
|
|
||||||
if (!open)
|
if (!open)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Draw the picker relative to (width + 8, 0)
|
int pickerX = PICKER_X;
|
||||||
int pickerX = width + 8;
|
|
||||||
int pickerY = 0;
|
int pickerY = 0;
|
||||||
|
|
||||||
|
// Backdrop behind the picker and its two sliders
|
||||||
|
renderScope.drawRoundedRectangle(pickerX - BACKDROP_PADDING, BACKDROP_TOP,
|
||||||
|
PICKER_WIDTH + BACKDROP_PADDING * 2, BACKDROP_BOTTOM - BACKDROP_TOP,
|
||||||
|
Theme.WIDGET_RADIUS.value, 1, Theme.withAlpha(0.95f, Theme.BACKGROUND.value), Theme.getBorder());
|
||||||
|
|
||||||
renderColorSquare(renderScope, pickerX, pickerY);
|
renderColorSquare(renderScope, pickerX, pickerY);
|
||||||
renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_HEIGHT - 10);
|
renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_HEIGHT - 10);
|
||||||
renderHueSlider(renderScope, pickerX, pickerY + PICKER_HEIGHT + 10);
|
renderHueSlider(renderScope, pickerX, pickerY + PICKER_HEIGHT + 10);
|
||||||
@@ -138,6 +167,11 @@ public class ColorInput extends Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isOverPicker(double x, double y) {
|
||||||
|
return x >= -BACKDROP_PADDING && x <= PICKER_WIDTH + BACKDROP_PADDING
|
||||||
|
&& y >= BACKDROP_TOP && y <= BACKDROP_BOTTOM;
|
||||||
|
}
|
||||||
|
|
||||||
private void handlePickerDrag(double mouseX, double mouseY) {
|
private void handlePickerDrag(double mouseX, double mouseY) {
|
||||||
// Opacity
|
// Opacity
|
||||||
if (draggingOpacity)
|
if (draggingOpacity)
|
||||||
@@ -169,6 +203,16 @@ public class ColorInput extends Element {
|
|||||||
updateColorFromSelection();
|
updateColorFromSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* #RRGGBB, or #AARRGGBB when the color is translucent.
|
||||||
|
*/
|
||||||
|
private static String toHex(int color) {
|
||||||
|
int alpha = (color >>> 24) & 0xFF;
|
||||||
|
return alpha == 0xFF
|
||||||
|
? String.format("#%06X", color & 0xFFFFFF)
|
||||||
|
: String.format("#%08X", color);
|
||||||
|
}
|
||||||
|
|
||||||
private void syncFromColor() {
|
private void syncFromColor() {
|
||||||
int rgb = prop.value & 0xFFFFFF;
|
int rgb = prop.value & 0xFFFFFF;
|
||||||
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
|
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package org.saturnclient.ui.components.inputs;
|
|||||||
|
|
||||||
import org.saturnclient.config.ConfigManager;
|
import org.saturnclient.config.ConfigManager;
|
||||||
import org.saturnclient.config.property.FloatProperty;
|
import org.saturnclient.config.property.FloatProperty;
|
||||||
import org.saturnclient.ui.resources.Fonts;
|
import org.saturnclient.ui.elements.Field;
|
||||||
|
|
||||||
public class FloatInput extends Input {
|
public class FloatInput extends Input {
|
||||||
public FloatProperty prop;
|
public FloatProperty prop;
|
||||||
|
|
||||||
public FloatInput(FloatProperty prop) {
|
public FloatInput(FloatProperty prop) {
|
||||||
this.width = 120;
|
this.width = 96;
|
||||||
this.height = Fonts.getHeight();
|
this.height = Field.HEIGHT;
|
||||||
this.prop = prop;
|
this.prop = prop;
|
||||||
this.text = String.valueOf(prop.value);
|
this.text = String.valueOf(prop.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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.elements.Field;
|
||||||
import org.saturnclient.ui.resources.Fonts;
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
|
|
||||||
public abstract class Input extends Element {
|
public abstract class Input extends Element {
|
||||||
@@ -12,9 +13,9 @@ public abstract class Input extends Element {
|
|||||||
public int cursorPosition = 0;
|
public int cursorPosition = 0;
|
||||||
|
|
||||||
/** Scale the text is drawn at */
|
/** Scale the text is drawn at */
|
||||||
protected float textScale = 0.6f;
|
protected float textScale = Field.TEXT_SCALE;
|
||||||
/** Horizontal inset of the text on both sides */
|
/** Horizontal inset of the text on both sides */
|
||||||
protected int textX = 4;
|
protected int textX = 10;
|
||||||
|
|
||||||
public abstract void checkReset();
|
public abstract void checkReset();
|
||||||
|
|
||||||
@@ -70,16 +71,24 @@ public abstract class Input extends Element {
|
|||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
checkReset();
|
checkReset();
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, 10, Theme.PRIMARY.value);
|
Field.drawBox(renderScope, width, height, ctx.isHovering(), focused);
|
||||||
|
drawText(renderScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the visible part of the text, and the cursor while focused.
|
||||||
|
*/
|
||||||
|
protected void drawText(RenderScope renderScope) {
|
||||||
|
int textHeight = (int) (Fonts.getHeight() * textScale);
|
||||||
|
int textY = Field.textY(height, textScale);
|
||||||
|
|
||||||
int scrollOffset = getScrollOffset();
|
int scrollOffset = getScrollOffset();
|
||||||
String visibleText = getVisibleText(scrollOffset);
|
String visibleText = getVisibleText(scrollOffset);
|
||||||
renderScope.drawText(textScale, visibleText, textX, 4, Theme.FONT.value, Theme.PRIMARY_FG.value);
|
renderScope.drawText(textScale, visibleText, textX, textY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
|
|
||||||
if (focused) {
|
if (focused) {
|
||||||
int cursorX = getCursorX(visibleText, scrollOffset);
|
renderScope.drawRectangle(getCursorX(visibleText, scrollOffset), textY - 1, 1, textHeight + 2,
|
||||||
|
Theme.ACCENT.value);
|
||||||
renderScope.drawRectangle(cursorX, 2, 1, height - 4, Theme.ACCENT.value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package org.saturnclient.ui.components.inputs;
|
|||||||
|
|
||||||
import org.saturnclient.config.ConfigManager;
|
import org.saturnclient.config.ConfigManager;
|
||||||
import org.saturnclient.config.property.IntProperty;
|
import org.saturnclient.config.property.IntProperty;
|
||||||
import org.saturnclient.ui.resources.Fonts;
|
import org.saturnclient.ui.elements.Field;
|
||||||
|
|
||||||
public class IntInput extends Input {
|
public class IntInput extends Input {
|
||||||
public IntProperty prop;
|
public IntProperty prop;
|
||||||
|
|
||||||
public IntInput(IntProperty prop) {
|
public IntInput(IntProperty prop) {
|
||||||
this.width = 120;
|
this.width = 96;
|
||||||
this.height = Fonts.getHeight();
|
this.height = Field.HEIGHT;
|
||||||
this.prop = prop;
|
this.prop = prop;
|
||||||
this.text = String.valueOf(prop.value);
|
this.text = String.valueOf(prop.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,27 +7,41 @@ import org.saturnclient.config.property.KeybindingProperty;
|
|||||||
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;
|
import org.saturnclient.ui.elements.Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a key binding. Click it, then press a key to bind it, or backspace to
|
||||||
|
* unbind.
|
||||||
|
*/
|
||||||
public class KeybindingSelector extends Element {
|
public class KeybindingSelector extends Element {
|
||||||
KeybindingProperty prop;
|
KeybindingProperty prop;
|
||||||
|
|
||||||
public KeybindingSelector(KeybindingProperty prop) {
|
public KeybindingSelector(KeybindingProperty prop) {
|
||||||
this.prop = prop;
|
this.prop = prop;
|
||||||
this.width = 120;
|
this.width = 96;
|
||||||
this.height = Fonts.getHeight();
|
this.height = Field.HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
|
Field.drawBox(renderScope, width, height, ctx.isHovering(), focused);
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
|
|
||||||
String name = Providers.GLFW.getKeyName(prop.value);
|
|
||||||
|
|
||||||
renderScope.drawText(0.6f,
|
if (focused) {
|
||||||
prop.value == -1 ? "<NONE>"
|
Field.drawCenteredText(renderScope, "Press a key", width, height, Theme.getBodyFont(),
|
||||||
: name != null ? name.toUpperCase() : "<Invalid-Key>",
|
Theme.getMutedFg());
|
||||||
4, 4, Theme.FONT.value, textColor);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Field.drawCenteredText(renderScope, getKeyLabel(), width, height, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getKeyLabel() {
|
||||||
|
if (prop.value == -1) {
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = Providers.GLFW.getKeyName(prop.value);
|
||||||
|
return name != null ? name.toUpperCase() : "Key " + prop.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import java.util.function.Consumer;
|
|||||||
import org.saturnclient.config.Theme;
|
import org.saturnclient.config.Theme;
|
||||||
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;
|
import org.saturnclient.ui.elements.Field;
|
||||||
import org.saturnclient.ui.resources.Textures;
|
import org.saturnclient.ui.resources.Textures;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,7 +21,6 @@ public class SearchInput extends Input {
|
|||||||
public SearchInput(String placeholder, Consumer<String> onChange) {
|
public SearchInput(String placeholder, Consumer<String> onChange) {
|
||||||
this.placeholder = placeholder;
|
this.placeholder = placeholder;
|
||||||
this.onChange = onChange;
|
this.onChange = onChange;
|
||||||
this.textScale = 0.68f;
|
|
||||||
this.textX = ICON_X + ICON_SIZE + 8;
|
this.textX = ICON_X + ICON_SIZE + 8;
|
||||||
this.width = 240;
|
this.width = 240;
|
||||||
this.height = 36;
|
this.height = 36;
|
||||||
@@ -49,31 +48,17 @@ public class SearchInput extends Input {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
int border = focused ? Theme.withAlpha(0.6f, Theme.ACCENT.value)
|
Field.drawBox(renderScope, width, height, ctx.isHovering(), focused);
|
||||||
: ctx.isHovering() ? Theme.withAlpha(0.25f, Theme.FOREGROUND.value)
|
|
||||||
: Theme.withAlpha(0.1f, Theme.FOREGROUND.value);
|
|
||||||
|
|
||||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
|
||||||
Theme.withAlpha(0.06f, Theme.FOREGROUND.value), border);
|
|
||||||
|
|
||||||
renderScope.drawTexture(Textures.ICON_SEARCH, ICON_X, (height - ICON_SIZE) / 2, 0, 0,
|
renderScope.drawTexture(Textures.ICON_SEARCH, ICON_X, (height - ICON_SIZE) / 2, 0, 0,
|
||||||
ICON_SIZE, ICON_SIZE, Theme.getMutedFg());
|
ICON_SIZE, ICON_SIZE, Theme.getMutedFg());
|
||||||
|
|
||||||
int textHeight = (int) (Fonts.getHeight() * textScale);
|
|
||||||
int textY = (height - textHeight) / 2;
|
|
||||||
|
|
||||||
if (text.isEmpty() && !focused) {
|
if (text.isEmpty() && !focused) {
|
||||||
renderScope.drawText(textScale, placeholder, textX, textY, Theme.getBodyFont(), Theme.getMutedFg());
|
renderScope.drawText(textScale, placeholder, textX, Field.textY(height, textScale),
|
||||||
|
Theme.getBodyFont(), Theme.getMutedFg());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int scrollOffset = getScrollOffset();
|
drawText(renderScope);
|
||||||
String visibleText = getVisibleText(scrollOffset);
|
|
||||||
renderScope.drawText(textScale, visibleText, textX, textY, Theme.FONT.value, Theme.FOREGROUND.value);
|
|
||||||
|
|
||||||
if (focused) {
|
|
||||||
renderScope.drawRectangle(getCursorX(visibleText, scrollOffset), textY - 1, 1, textHeight + 2,
|
|
||||||
Theme.ACCENT.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,36 +5,50 @@ import org.saturnclient.config.property.SelectProperty;
|
|||||||
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.Utils;
|
import org.saturnclient.ui.elements.Field;
|
||||||
import org.saturnclient.ui.resources.Fonts;
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
|
import org.saturnclient.ui.resources.Textures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steps through a {@link SelectProperty}'s values with arrows on either side.
|
||||||
|
* Clicking the left third goes back; anywhere else goes forward.
|
||||||
|
*/
|
||||||
public class Select extends Element {
|
public class Select extends Element {
|
||||||
private SelectProperty prop;
|
private static final int ARROW = 16;
|
||||||
|
private static final int ARROW_INSET = 8;
|
||||||
|
|
||||||
|
private final SelectProperty prop;
|
||||||
|
|
||||||
public Select(SelectProperty prop) {
|
public Select(SelectProperty prop) {
|
||||||
this.prop = prop;
|
this.prop = prop;
|
||||||
this.width = 150;
|
this.width = 200;
|
||||||
this.height = 15;
|
this.height = Field.HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
renderScope.drawText(0.8f, "<", 0, 0, Theme.FONT.value, Theme.FOREGROUND.value);
|
Field.drawBox(renderScope, width, height, ctx.isHovering(), false);
|
||||||
|
|
||||||
String text = prop.getSelection();
|
boolean prevHovered = ctx.isHovering(0, 0, width / 3, height);
|
||||||
|
boolean nextHovered = ctx.isHovering() && !prevHovered;
|
||||||
|
int arrowY = (height - ARROW) / 2;
|
||||||
|
|
||||||
renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, Theme.FONT.value) * 0.6f)) / 2, 1,
|
renderScope.drawTexture(Textures.ICON_CHEVRON_LEFT, ARROW_INSET, arrowY, 0, 0, ARROW, ARROW,
|
||||||
Theme.FONT.value, Theme.FOREGROUND.value);
|
prevHovered ? Theme.ACCENT.value : Theme.getMutedFg());
|
||||||
|
renderScope.drawTexture(Textures.ICON_CHEVRON_RIGHT, width - ARROW_INSET - ARROW, arrowY, 0, 0, ARROW, ARROW,
|
||||||
|
nextHovered ? Theme.ACCENT.value : Theme.getMutedFg());
|
||||||
|
|
||||||
renderScope.drawText(0.8f, ">", width - 10, 0, Theme.FONT.value, Theme.FOREGROUND.value);
|
int textSpace = width - (ARROW_INSET + ARROW + 4) * 2;
|
||||||
|
String text = Fonts.ellipsize(prop.getSelection(), Theme.FONT.value, Field.TEXT_SCALE, textSpace);
|
||||||
|
Field.drawCenteredText(renderScope, text, width, height, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void click(int mouseX, int mouseY) {
|
public void click(int mouseX, int mouseY) {
|
||||||
if (Utils.isHovering(mouseX - (width - 10), mouseY, 10, height, 1.0f)) {
|
if (mouseX < width / 3) {
|
||||||
prop.next();
|
|
||||||
} else if (Utils.isHovering(mouseX, mouseY, 10, height, 1.0f)) {
|
|
||||||
prop.prev();
|
prop.prev();
|
||||||
|
} else {
|
||||||
|
prop.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,25 +7,22 @@ import org.saturnclient.ui.ElementContext;
|
|||||||
import org.saturnclient.ui.RenderScope;
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
|
||||||
public class Toggle extends Element {
|
public class Toggle extends Element {
|
||||||
|
public static final int SWITCH_WIDTH = 42;
|
||||||
|
public static final int SWITCH_HEIGHT = 24;
|
||||||
|
|
||||||
private BoolProperty prop;
|
private BoolProperty prop;
|
||||||
|
|
||||||
public Toggle(BoolProperty prop) {
|
public Toggle(BoolProperty prop) {
|
||||||
this.prop = prop;
|
this.prop = prop;
|
||||||
this.width = 60;
|
this.width = SWITCH_WIDTH;
|
||||||
this.height = 30;
|
this.height = SWITCH_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
renderScope.drawRoundedRectangle(0, 0, 60, 30, Theme.WIDGET_RADIUS.value,
|
drawSwitch(renderScope, 0, 0, prop.value);
|
||||||
Theme.withAlpha(0.5f, prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value));
|
|
||||||
renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, Theme.WIDGET_RADIUS.value,
|
|
||||||
prop.value ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int SWITCH_WIDTH = 42;
|
|
||||||
public static final int SWITCH_HEIGHT = 24;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws an on/off switch: a pill track with a round knob on the right when on.
|
* Draws an on/off switch: a pill track with a round knob on the right when on.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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.resources.Fonts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A secondary button with an icon and a label: outlined, or plain text that
|
||||||
|
* lights up on hover (for links like "back").
|
||||||
|
*/
|
||||||
|
public class ActionButton extends Element {
|
||||||
|
private static final int HEIGHT = 34;
|
||||||
|
private static final int PADDING = 12;
|
||||||
|
private static final int ICON = 15;
|
||||||
|
private static final int ICON_GAP = 7;
|
||||||
|
private static final float TEXT_SCALE = 0.66f;
|
||||||
|
|
||||||
|
private final IdentifierRef icon;
|
||||||
|
private final String text;
|
||||||
|
private final boolean outlined;
|
||||||
|
private final Runnable onClick;
|
||||||
|
|
||||||
|
public ActionButton(IdentifierRef icon, String text, boolean outlined, Runnable onClick) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.text = text;
|
||||||
|
this.outlined = outlined;
|
||||||
|
this.onClick = onClick;
|
||||||
|
this.height = HEIGHT;
|
||||||
|
this.width = PADDING * 2 + ICON + ICON_GAP + (int) (Fonts.getWidth(text, Theme.FONT.value) * TEXT_SCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
|
boolean hovered = ctx.isHovering();
|
||||||
|
|
||||||
|
if (outlined) {
|
||||||
|
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||||
|
hovered ? Theme.withAlpha(0.06f, Theme.FOREGROUND.value) : 0,
|
||||||
|
Theme.withAlpha(hovered ? 0.25f : 0.12f, Theme.FOREGROUND.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
int color = hovered ? Theme.FOREGROUND.value : outlined ? Theme.PRIMARY_FG.value : Theme.getMutedFg();
|
||||||
|
|
||||||
|
renderScope.drawTexture(icon, PADDING, (height - ICON) / 2, 0, 0, ICON, ICON, color);
|
||||||
|
renderScope.drawText(TEXT_SCALE, text, PADDING + ICON + ICON_GAP, Field.textY(height, TEXT_SCALE),
|
||||||
|
Theme.FONT.value, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void click(int mouseX, int mouseY) {
|
||||||
|
onClick.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A faint horizontal line across the element's width.
|
||||||
|
*/
|
||||||
|
public class Divider extends Element {
|
||||||
|
|
||||||
|
public Divider(int width) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
|
renderScope.drawRectangle(0, 0, width, 1, Theme.getBorder());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.saturnclient.ui.elements;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Theme;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shared look of input fields: a faint box whose outline brightens on hover
|
||||||
|
* and turns to the accent color while focused.
|
||||||
|
*/
|
||||||
|
public final class Field {
|
||||||
|
/** Height of the fields in settings rows */
|
||||||
|
public static final int HEIGHT = 32;
|
||||||
|
public static final float TEXT_SCALE = 0.68f;
|
||||||
|
|
||||||
|
private Field() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void drawBox(RenderScope renderScope, int width, int height, boolean hovered, boolean focused) {
|
||||||
|
int border = focused ? Theme.withAlpha(0.6f, Theme.ACCENT.value)
|
||||||
|
: hovered ? Theme.withAlpha(0.25f, Theme.FOREGROUND.value)
|
||||||
|
: Theme.withAlpha(0.1f, Theme.FOREGROUND.value);
|
||||||
|
|
||||||
|
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||||
|
Theme.withAlpha(0.06f, Theme.FOREGROUND.value), border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y at which text drawn at {@code scale} is vertically centered in a
|
||||||
|
* field of the given height.
|
||||||
|
*/
|
||||||
|
public static int textY(int height, float scale) {
|
||||||
|
return (height - (int) (Fonts.getHeight() * scale)) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws {@code text} centered in a field of the given size.
|
||||||
|
*/
|
||||||
|
public static void drawCenteredText(RenderScope renderScope, String text, int width, int height, int font,
|
||||||
|
int color) {
|
||||||
|
int x = (width - (int) (Fonts.getWidth(text, font) * TEXT_SCALE)) / 2;
|
||||||
|
renderScope.drawText(TEXT_SCALE, text, x, textY(height, TEXT_SCALE), font, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.saturnclient.ui.elements;
|
||||||
|
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Theme;
|
||||||
|
import org.saturnclient.ui.Element;
|
||||||
|
import org.saturnclient.ui.ElementContext;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.components.inputs.Toggle;
|
||||||
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A labelled on/off switch ("Enabled" / "Disabled") that tints itself with the
|
||||||
|
* accent color while on.
|
||||||
|
*/
|
||||||
|
public class SwitchButton extends Element {
|
||||||
|
private static final int HEIGHT = 34;
|
||||||
|
private static final int PADDING_LEFT = 14;
|
||||||
|
private static final int PADDING_RIGHT = 5;
|
||||||
|
private static final int GAP = 10;
|
||||||
|
private static final float TEXT_SCALE = 0.66f;
|
||||||
|
|
||||||
|
private final String onText;
|
||||||
|
private final String offText;
|
||||||
|
private final BooleanSupplier value;
|
||||||
|
private final Runnable onToggle;
|
||||||
|
|
||||||
|
public SwitchButton(String onText, String offText, BooleanSupplier value, Runnable onToggle) {
|
||||||
|
this.onText = onText;
|
||||||
|
this.offText = offText;
|
||||||
|
this.value = value;
|
||||||
|
this.onToggle = onToggle;
|
||||||
|
this.height = HEIGHT;
|
||||||
|
|
||||||
|
// Sized for the longer label, so the button doesn't change width when toggled
|
||||||
|
int textWidth = Math.max(Fonts.getWidth(onText, Theme.FONT.value), Fonts.getWidth(offText, Theme.FONT.value));
|
||||||
|
this.width = PADDING_LEFT + (int) (textWidth * TEXT_SCALE) + GAP + Toggle.SWITCH_WIDTH + PADDING_RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||||
|
boolean on = value.getAsBoolean();
|
||||||
|
float tint = ctx.isHovering() ? 0.1f : 0.06f;
|
||||||
|
|
||||||
|
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||||
|
on ? Theme.withAlpha(tint * 2.5f, Theme.ACCENT.value) : Theme.withAlpha(tint, Theme.FOREGROUND.value));
|
||||||
|
|
||||||
|
renderScope.drawText(TEXT_SCALE, on ? onText : offText, PADDING_LEFT, Field.textY(height, TEXT_SCALE),
|
||||||
|
Theme.FONT.value, on ? Theme.ACCENT.value : Theme.PRIMARY_FG.value);
|
||||||
|
|
||||||
|
Toggle.drawSwitch(renderScope, width - PADDING_RIGHT - Toggle.SWITCH_WIDTH,
|
||||||
|
(height - Toggle.SWITCH_HEIGHT) / 2, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void click(int mouseX, int mouseY) {
|
||||||
|
onToggle.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,11 @@ public class Textures {
|
|||||||
public static final IdentifierRef ICON_CLOSE = IdentifierRef.ofSaturn("textures/gui/components/close.svg");
|
public static final IdentifierRef ICON_CLOSE = IdentifierRef.ofSaturn("textures/gui/components/close.svg");
|
||||||
public static final IdentifierRef ICON_SEARCH = IdentifierRef.ofSaturn("textures/gui/components/search.svg");
|
public static final IdentifierRef ICON_SEARCH = IdentifierRef.ofSaturn("textures/gui/components/search.svg");
|
||||||
public static final IdentifierRef ICON_SLIDERS = IdentifierRef.ofSaturn("textures/gui/components/sliders.svg");
|
public static final IdentifierRef ICON_SLIDERS = IdentifierRef.ofSaturn("textures/gui/components/sliders.svg");
|
||||||
|
public static final IdentifierRef ICON_RESET = IdentifierRef.ofSaturn("textures/gui/components/reset.svg");
|
||||||
|
public static final IdentifierRef ICON_CHEVRON_LEFT = IdentifierRef
|
||||||
|
.ofSaturn("textures/gui/components/chevron_left.svg");
|
||||||
|
public static final IdentifierRef ICON_CHEVRON_RIGHT = IdentifierRef
|
||||||
|
.ofSaturn("textures/gui/components/chevron_right.svg");
|
||||||
|
|
||||||
public static final IdentifierRef LOGO = IdentifierRef.ofSaturn("textures/logo/logo.png");
|
public static final IdentifierRef LOGO = IdentifierRef.ofSaturn("textures/logo/logo.png");
|
||||||
public static final IdentifierRef REALISTIC_LOGO = IdentifierRef.ofSaturn("textures/logo/realistic.png");
|
public static final IdentifierRef REALISTIC_LOGO = IdentifierRef.ofSaturn("textures/logo/realistic.png");
|
||||||
|
|||||||
@@ -1,173 +1,37 @@
|
|||||||
package org.saturnclient.ui.screens;
|
package org.saturnclient.ui.screens;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.saturnclient.config.ConfigManager;
|
import org.saturnclient.config.ConfigManager;
|
||||||
import org.saturnclient.config.Theme;
|
import org.saturnclient.config.Theme;
|
||||||
import org.saturnclient.config.property.BoolProperty;
|
import org.saturnclient.ui.PanelScreen;
|
||||||
import org.saturnclient.config.property.ColorProperty;
|
import org.saturnclient.ui.components.PropertyList;
|
||||||
import org.saturnclient.config.property.FloatProperty;
|
|
||||||
import org.saturnclient.config.property.IntProperty;
|
|
||||||
import org.saturnclient.config.property.KeybindingProperty;
|
|
||||||
import org.saturnclient.config.property.NamespaceProperty;
|
|
||||||
import org.saturnclient.config.property.Property;
|
|
||||||
import org.saturnclient.config.property.SelectProperty;
|
|
||||||
import org.saturnclient.mod.HudMod;
|
|
||||||
import org.saturnclient.mod.ModLayout;
|
|
||||||
import org.saturnclient.ui.RenderScope;
|
|
||||||
import org.saturnclient.ui.SaturnScreen;
|
|
||||||
import org.saturnclient.ui.components.HudModPreview;
|
|
||||||
import org.saturnclient.ui.components.Sidebar;
|
import org.saturnclient.ui.components.Sidebar;
|
||||||
import org.saturnclient.ui.components.inputs.FloatInput;
|
import org.saturnclient.ui.elements.Label;
|
||||||
import org.saturnclient.ui.components.inputs.ColorInput;
|
|
||||||
import org.saturnclient.ui.components.inputs.IntInput;
|
|
||||||
import org.saturnclient.ui.components.inputs.KeybindingSelector;
|
|
||||||
import org.saturnclient.ui.components.inputs.Select;
|
|
||||||
import org.saturnclient.ui.components.inputs.Toggle;
|
|
||||||
import org.saturnclient.ui.elements.Scroll;
|
import org.saturnclient.ui.elements.Scroll;
|
||||||
import org.saturnclient.ui.elements.Text;
|
|
||||||
import org.saturnclient.ui.resources.Fonts;
|
|
||||||
|
|
||||||
public class ConfigEditor extends SaturnScreen {
|
/**
|
||||||
private ConfigManager config;
|
* Edits every property of a config, such as the client's own settings.
|
||||||
private HudMod hudMod;
|
*/
|
||||||
private HudModPreview preview;
|
public class ConfigEditor extends PanelScreen {
|
||||||
|
private static final int SCROLLBAR_SPACE = 14;
|
||||||
|
private static final int LIST_Y = 44;
|
||||||
|
|
||||||
|
private final ConfigManager config;
|
||||||
|
|
||||||
public ConfigEditor(ConfigManager config) {
|
public ConfigEditor(ConfigManager config) {
|
||||||
super("Config Editor");
|
super("Config Editor", Sidebar.SETTINGS);
|
||||||
|
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigEditor(ConfigManager config, HudMod hudMod) {
|
|
||||||
super("Config Editor");
|
|
||||||
|
|
||||||
this.config = config;
|
|
||||||
this.hudMod = hudMod;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void ui() {
|
protected void panel() {
|
||||||
int p = 10;
|
draw(new Label(() -> "Settings", 1.15f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
|
||||||
int g = 10;
|
.position(contentX, contentY + 2));
|
||||||
|
|
||||||
Scroll scroll = new Scroll(p);
|
Scroll scroll = new Scroll(0).noBackground();
|
||||||
|
PropertyList.draw(scroll, config.getProperties(), Set.of(), contentWidth, 0);
|
||||||
drawProperties(scroll, config.getProperties(), 0, 0, 480 + 10 + (g * 2));
|
draw(scroll.dimensions(contentWidth + SCROLLBAR_SPACE, contentHeight - LIST_Y)
|
||||||
|
.position(contentX, contentY + LIST_Y));
|
||||||
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
|
|
||||||
|
|
||||||
if (this.hudMod != null) {
|
|
||||||
this.preview = new HudModPreview(this.hudMod);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw(scroll.dimensions(scrollWidth, 350).center(width, height));
|
|
||||||
|
|
||||||
draw(new Sidebar(Sidebar.SETTINGS, this.provider::close).center(width, height, -(scrollWidth / 2 + 35), 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private int drawProperties(Scroll configScroll, Map<String, Property> properties, int row, int col, int w) {
|
|
||||||
for (Map.Entry<String, Property> propEntry : properties.entrySet()) {
|
|
||||||
Property prop = propEntry.getValue();
|
|
||||||
String propName = propEntry.getKey();
|
|
||||||
boolean full = isFull(prop);
|
|
||||||
|
|
||||||
// Always reset to col 0 if full-width component needs to be drawn mid-row
|
|
||||||
if (full && col != 0) {
|
|
||||||
col = 0;
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int modX = (w / 2) * col; // two-column layout
|
|
||||||
int modY = 25 * row;
|
|
||||||
|
|
||||||
if (prop instanceof NamespaceProperty) {
|
|
||||||
configScroll.draw(
|
|
||||||
new Text(propName)
|
|
||||||
.position(Fonts.centerX(520, propName, Theme.FONT.value), modY + 1)
|
|
||||||
.scale(0.7f));
|
|
||||||
} else {
|
|
||||||
configScroll.draw(
|
|
||||||
new Text(propName)
|
|
||||||
.position(modX, modY + 1)
|
|
||||||
.scale(0.7f));
|
|
||||||
}
|
|
||||||
|
|
||||||
// instanceof dispatch
|
|
||||||
if (prop instanceof BoolProperty p) {
|
|
||||||
configScroll.draw(
|
|
||||||
new Toggle(p)
|
|
||||||
.position(modX + (w / 2) - 40, modY)
|
|
||||||
.scale(0.5f));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (prop instanceof ColorProperty p) {
|
|
||||||
configScroll.draw(
|
|
||||||
new ColorInput(p)
|
|
||||||
.position(w / 2, modY));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (prop instanceof IntProperty p) {
|
|
||||||
configScroll.draw(
|
|
||||||
new IntInput(p)
|
|
||||||
.position(w / 2, modY));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (prop instanceof FloatProperty p) {
|
|
||||||
configScroll.draw(
|
|
||||||
new FloatInput(p)
|
|
||||||
.position(w / 2, modY));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (prop instanceof NamespaceProperty p) {
|
|
||||||
Map<String, Property> nestedProperties = p.value;
|
|
||||||
row = drawProperties(configScroll, nestedProperties, row + 1, col, w);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (prop instanceof SelectProperty p) {
|
|
||||||
configScroll.draw(
|
|
||||||
new Select(p)
|
|
||||||
.position(w / 2, modY));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (prop instanceof KeybindingProperty p) {
|
|
||||||
configScroll.draw(
|
|
||||||
new KeybindingSelector(p)
|
|
||||||
.position(w / 2, modY));
|
|
||||||
}
|
|
||||||
|
|
||||||
// STRING → intentionally no component (same as before)
|
|
||||||
|
|
||||||
if (full) {
|
|
||||||
row++; // full-width takes a whole row
|
|
||||||
col = 0;
|
|
||||||
} else {
|
|
||||||
col++;
|
|
||||||
if (col > 1) { // max 2 columns
|
|
||||||
col = 0;
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isFull(Property prop) {
|
|
||||||
return !(prop instanceof BoolProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(RenderScope renderScope, int mouseX, int mouseY, float delta, long elapsed) {
|
|
||||||
if (this.preview != null) {
|
|
||||||
ModLayout layout = hudMod.getDimensions();
|
|
||||||
draw(this.preview
|
|
||||||
.dimensions(layout.width, (int) (layout.height * layout.scale.value * 2.0f))
|
|
||||||
.scale(1.0f) // Because the centering won't work properly
|
|
||||||
.center(width, height, (530 / 2) + 40, 0)
|
|
||||||
.scale(layout.scale.value * 2.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
super.render(renderScope, mouseX, mouseY, delta, elapsed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,28 +8,20 @@ import org.saturnclient.config.AnimationConfig;
|
|||||||
import org.saturnclient.config.Theme;
|
import org.saturnclient.config.Theme;
|
||||||
import org.saturnclient.mod.Mod;
|
import org.saturnclient.mod.Mod;
|
||||||
import org.saturnclient.mod.ModManager;
|
import org.saturnclient.mod.ModManager;
|
||||||
import org.saturnclient.ui.SaturnScreen;
|
import org.saturnclient.ui.PanelScreen;
|
||||||
import org.saturnclient.ui.anim.SlideFade;
|
import org.saturnclient.ui.anim.SlideFade;
|
||||||
import org.saturnclient.ui.components.SaturnModule;
|
import org.saturnclient.ui.components.SaturnModule;
|
||||||
import org.saturnclient.ui.components.Sidebar;
|
import org.saturnclient.ui.components.Sidebar;
|
||||||
import org.saturnclient.ui.components.inputs.SearchInput;
|
import org.saturnclient.ui.components.inputs.SearchInput;
|
||||||
import org.saturnclient.ui.elements.AnimationStagger;
|
import org.saturnclient.ui.elements.AnimationStagger;
|
||||||
import org.saturnclient.ui.elements.ChipGroup;
|
import org.saturnclient.ui.elements.ChipGroup;
|
||||||
import org.saturnclient.ui.elements.ImageTexture;
|
|
||||||
import org.saturnclient.ui.elements.Label;
|
import org.saturnclient.ui.elements.Label;
|
||||||
import org.saturnclient.ui.elements.Panel;
|
|
||||||
import org.saturnclient.ui.elements.Scroll;
|
import org.saturnclient.ui.elements.Scroll;
|
||||||
import org.saturnclient.ui.resources.Fonts;
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
import org.saturnclient.ui.resources.Textures;
|
|
||||||
|
|
||||||
public class ModMenu extends SaturnScreen {
|
public class ModMenu extends PanelScreen {
|
||||||
private static final String ALL = "All";
|
private static final String ALL = "All";
|
||||||
|
|
||||||
private static final int MAX_WIDTH = 900;
|
|
||||||
private static final int MAX_HEIGHT = 580;
|
|
||||||
private static final int MARGIN = 40;
|
|
||||||
private static final int GAP = 12;
|
|
||||||
private static final int PADDING = 20;
|
|
||||||
private static final int COLUMNS = 3;
|
private static final int COLUMNS = 3;
|
||||||
private static final int CARD_GAP = 10;
|
private static final int CARD_GAP = 10;
|
||||||
private static final int SCROLLBAR_SPACE = 14;
|
private static final int SCROLLBAR_SPACE = 14;
|
||||||
@@ -41,43 +33,30 @@ public class ModMenu extends SaturnScreen {
|
|||||||
private int gridWidth;
|
private int gridWidth;
|
||||||
|
|
||||||
public ModMenu() {
|
public ModMenu() {
|
||||||
super("Mod Menu");
|
super("Mod Menu", Sidebar.MODS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void ui() {
|
protected void panel() {
|
||||||
// The search box starts empty after a resize, so the query does too.
|
// The search box starts empty after a resize, so the query does too.
|
||||||
query = "";
|
query = "";
|
||||||
|
gridWidth = contentWidth;
|
||||||
int panelWidth = Math.min(MAX_WIDTH, width - MARGIN * 2 - Sidebar.WIDTH - GAP);
|
|
||||||
int panelHeight = Math.min(MAX_HEIGHT, height - MARGIN * 2);
|
|
||||||
int left = (width - (Sidebar.WIDTH + GAP + panelWidth)) / 2;
|
|
||||||
int top = (height - panelHeight) / 2;
|
|
||||||
int panelX = left + Sidebar.WIDTH + GAP;
|
|
||||||
int contentX = panelX + PADDING;
|
|
||||||
gridWidth = panelWidth - PADDING * 2;
|
|
||||||
|
|
||||||
draw(new Sidebar(Sidebar.MODS, this.provider::close)
|
|
||||||
.dimensions(Sidebar.WIDTH, panelHeight)
|
|
||||||
.position(left, top));
|
|
||||||
|
|
||||||
draw(new Panel().dimensions(panelWidth, panelHeight).position(panelX, top));
|
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
draw(new Label(() -> "Mods", 1.15f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
|
draw(new Label(() -> "Mods", 1.15f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
|
||||||
.position(contentX, top + PADDING + 2));
|
.position(contentX, contentY + 2));
|
||||||
|
|
||||||
draw(new Label(this::getEnabledSummary, 0.66f, Theme::getBodyFont, Theme::getMutedFg)
|
draw(new Label(this::getEnabledSummary, 0.66f, Theme::getBodyFont, Theme::getMutedFg)
|
||||||
.position(contentX, top + PADDING + 28));
|
.position(contentX, contentY + 28));
|
||||||
|
|
||||||
SearchInput search = new SearchInput("Search mods", text -> {
|
SearchInput search = new SearchInput("Search mods", text -> {
|
||||||
query = text;
|
query = text;
|
||||||
refresh(false);
|
refresh(false);
|
||||||
});
|
});
|
||||||
draw(search.position(panelX + panelWidth - PADDING - search.width, top + PADDING + 2));
|
draw(search.position(contentX + contentWidth - search.width, contentY + 2));
|
||||||
|
|
||||||
// Categories
|
// Categories
|
||||||
int chipsY = top + PADDING + 56;
|
int chipsY = contentY + 56;
|
||||||
ChipGroup categories = new ChipGroup(getCategories(), category, selected -> {
|
ChipGroup categories = new ChipGroup(getCategories(), category, selected -> {
|
||||||
category = selected;
|
category = selected;
|
||||||
refresh(false);
|
refresh(false);
|
||||||
@@ -87,11 +66,9 @@ public class ModMenu extends SaturnScreen {
|
|||||||
// Mods
|
// Mods
|
||||||
int scrollY = chipsY + categories.height + 14;
|
int scrollY = chipsY + categories.height + 14;
|
||||||
scroll = new Scroll(0).noBackground();
|
scroll = new Scroll(0).noBackground();
|
||||||
scroll.dimensions(gridWidth + SCROLLBAR_SPACE, top + panelHeight - 12 - scrollY);
|
scroll.dimensions(gridWidth + SCROLLBAR_SPACE, panelY + panelHeight - 12 - scrollY);
|
||||||
refresh(true);
|
refresh(true);
|
||||||
draw(scroll.position(contentX, scrollY));
|
draw(scroll.position(contentX, scrollY));
|
||||||
|
|
||||||
draw(new ImageTexture(Textures.LOGO_TEXT_BIG).dimensions(180, 18).position(width - 180 - 20, height - 18 - 20));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package org.saturnclient.ui.screens;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.saturnclient.common.provider.Providers;
|
||||||
|
import org.saturnclient.config.property.Property;
|
||||||
|
import org.saturnclient.mod.HudMod;
|
||||||
|
import org.saturnclient.mod.Mod;
|
||||||
|
import org.saturnclient.ui.PanelScreen;
|
||||||
|
import org.saturnclient.ui.components.HudPreviewCard;
|
||||||
|
import org.saturnclient.ui.components.ModHeader;
|
||||||
|
import org.saturnclient.ui.components.PropertyList;
|
||||||
|
import org.saturnclient.ui.components.Sidebar;
|
||||||
|
import org.saturnclient.ui.elements.ActionButton;
|
||||||
|
import org.saturnclient.ui.elements.Divider;
|
||||||
|
import org.saturnclient.ui.elements.Scroll;
|
||||||
|
import org.saturnclient.ui.elements.SwitchButton;
|
||||||
|
import org.saturnclient.ui.resources.Textures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One mod's settings: a header with the mod's details and its on/off switch,
|
||||||
|
* the rest of its properties below, and a live preview for HUD mods.
|
||||||
|
*/
|
||||||
|
public class ModSettings extends PanelScreen {
|
||||||
|
/**
|
||||||
|
* Every mod's first property. It's shown as the header's switch, so the list
|
||||||
|
* leaves it out.
|
||||||
|
*/
|
||||||
|
private static final String ENABLED = "Enabled";
|
||||||
|
|
||||||
|
private static final int HEADER_Y = 40;
|
||||||
|
private static final int PREVIEW_WIDTH = 280;
|
||||||
|
private static final int PREVIEW_MAX_HEIGHT = 220;
|
||||||
|
private static final int COLUMN_GAP = 22;
|
||||||
|
private static final int SCROLLBAR_SPACE = 14;
|
||||||
|
|
||||||
|
private final Mod mod;
|
||||||
|
|
||||||
|
public ModSettings(Mod mod) {
|
||||||
|
super(mod.getName() + " Settings", Sidebar.MODS);
|
||||||
|
this.mod = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void panel() {
|
||||||
|
draw(new ActionButton(Textures.ICON_CHEVRON_LEFT, "Mods", false,
|
||||||
|
() -> Providers.saturn.getClient().setScreen(new ModMenu()))
|
||||||
|
.position(contentX - 12, contentY - 4));
|
||||||
|
|
||||||
|
// Header: the mod's details, then Reset and the on/off switch on the right
|
||||||
|
int headerY = contentY + HEADER_Y;
|
||||||
|
|
||||||
|
SwitchButton enabled = new SwitchButton("Enabled", "Disabled", mod::isEnabled,
|
||||||
|
() -> mod.setEnabled(!mod.isEnabled()));
|
||||||
|
int buttonY = headerY + (ModHeader.HEIGHT - enabled.height) / 2;
|
||||||
|
draw(enabled.position(contentX + contentWidth - enabled.width, buttonY));
|
||||||
|
|
||||||
|
ActionButton reset = new ActionButton(Textures.ICON_RESET, "Reset", true, this::reset);
|
||||||
|
draw(reset.position(enabled.x - 10 - reset.width, buttonY));
|
||||||
|
|
||||||
|
draw(new ModHeader(mod, reset.x - 16 - contentX).position(contentX, headerY));
|
||||||
|
|
||||||
|
int dividerY = headerY + ModHeader.HEIGHT + 16;
|
||||||
|
draw(new Divider(contentWidth).position(contentX, dividerY));
|
||||||
|
|
||||||
|
// Body: the properties, with a preview beside them for HUD mods
|
||||||
|
int bodyY = dividerY + 1;
|
||||||
|
int bodyHeight = contentY + contentHeight - bodyY;
|
||||||
|
int listWidth = contentWidth;
|
||||||
|
|
||||||
|
if (mod instanceof HudMod hudMod) {
|
||||||
|
listWidth -= PREVIEW_WIDTH + COLUMN_GAP;
|
||||||
|
draw(new HudPreviewCard(hudMod)
|
||||||
|
.dimensions(PREVIEW_WIDTH, Math.min(PREVIEW_MAX_HEIGHT, bodyHeight - 16))
|
||||||
|
.position(contentX + contentWidth - PREVIEW_WIDTH, bodyY + 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
Scroll scroll = new Scroll(0).noBackground();
|
||||||
|
PropertyList.draw(scroll, mod.getConfig().getProperties(), Set.of(ENABLED), listWidth, 0);
|
||||||
|
draw(scroll.dimensions(listWidth + SCROLLBAR_SPACE, bodyHeight).position(contentX, bodyY));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts every setting except the on/off switch back to its default, then
|
||||||
|
* rebuilds the screen so the inputs show the new values.
|
||||||
|
*/
|
||||||
|
private void reset() {
|
||||||
|
for (Map.Entry<String, Property> entry : mod.getConfig().getProperties().entrySet()) {
|
||||||
|
if (!entry.getKey().equals(ENABLED)) {
|
||||||
|
entry.getValue().reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"><path d="m15 18-6-6 6-6"/></svg>
|
||||||
|
After Width: | Height: | Size: 209 B |
@@ -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"><path d="m9 18 6-6-6-6"/></svg>
|
||||||
|
After Width: | Height: | Size: 208 B |
@@ -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"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
||||||
|
After Width: | Height: | Size: 264 B |
Reference in New Issue
Block a user