Redesign mod settings

A mod's settings button now opens ModSettings: a header with the mod's
icon, name, version, tags and description, a Reset button and an on/off
switch, then its properties as rows, with nested namespaces as titled
sections and a live preview card beside them for HUD mods.

PanelScreen holds the sidebar-and-panel frame that the mod menu, mod
settings and config editor share. PropertyList lays out any config as
rows. The inputs share one field look: Toggle is a switch, Select steps
with arrows, and the number, keybinding and color inputs are boxes. The
color picker opens to the left and closes on outside clicks.

Every property type now implements reset(), which was a no-op, and
ModLayout keeps its settings in order instead of Map.of's. ConfigEditor
moves onto the same frame and rows and drops its HUD preview, which added
a new element every frame.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-27 17:13:39 +02:00
co-authored by claude
parent dfebd2367d
commit df09271e81
33 changed files with 860 additions and 277 deletions
@@ -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.config.Theme;
import org.saturnclient.mod.HudMod;
import org.saturnclient.mod.Mod;
import org.saturnclient.ui.Element;
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.resources.Fonts;
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
@@ -101,11 +100,7 @@ public class SaturnModule extends Element {
&& mouseY >= getSettingsY() && mouseY <= getSettingsY() + SETTINGS;
if (settingsClicked) {
if (mod instanceof HudMod h) {
Providers.saturn.getClient().setScreen(new ConfigEditor(mod.getConfig(), h));
} else {
Providers.saturn.getClient().setScreen(new ConfigEditor(mod.getConfig()));
}
Providers.saturn.getClient().setScreen(new ModSettings(mod));
} else {
mod.setEnabled(!mod.isEnabled());
}
@@ -2,10 +2,12 @@ package org.saturnclient.ui.components.inputs;
import java.awt.Color;
import org.saturnclient.config.Theme;
import org.saturnclient.config.property.ColorProperty;
import org.saturnclient.ui.Element;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.elements.Field;
public class ColorInput extends Element {
public ColorProperty prop;
@@ -26,10 +28,19 @@ public class ColorInput extends Element {
private static final int 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) {
this.prop = prop;
this.width = 58;
this.height = 20;
this.width = 116;
this.height = Field.HEIGHT;
syncFromColor();
}
@@ -40,15 +51,23 @@ public class ColorInput extends Element {
@Override
public void mouseClicked(double mouseX, double mouseY, int button) {
if (open) {
handlePickerClick(mouseX - width - 8, mouseY);
if (!open) {
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
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
if (open) {
handlePickerDrag(mouseX - width - 8, mouseY);
handlePickerDrag(mouseX - PICKER_X, mouseY);
}
}
@@ -59,16 +78,26 @@ public class ColorInput extends Element {
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
// Draw the main color rectangle
renderScope.drawRoundedRectangle(0, 0, width, height, 6, prop.value);
Field.drawBox(renderScope, width, height, ctx.isHovering(), open);
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)
return;
// Draw the picker relative to (width + 8, 0)
int pickerX = width + 8;
int pickerX = PICKER_X;
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);
renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_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) {
// Opacity
if (draggingOpacity)
@@ -169,6 +203,16 @@ public class ColorInput extends Element {
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() {
int rgb = prop.value & 0xFFFFFF;
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.property.FloatProperty;
import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.elements.Field;
public class FloatInput extends Input {
public FloatProperty prop;
public FloatInput(FloatProperty prop) {
this.width = 120;
this.height = Fonts.getHeight();
this.width = 96;
this.height = Field.HEIGHT;
this.prop = prop;
this.text = String.valueOf(prop.value);
}
@@ -5,6 +5,7 @@ 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;
public abstract class Input extends Element {
@@ -12,9 +13,9 @@ public abstract class Input extends Element {
public int cursorPosition = 0;
/** 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 */
protected int textX = 4;
protected int textX = 10;
public abstract void checkReset();
@@ -70,16 +71,24 @@ public abstract class Input extends Element {
public void render(RenderScope renderScope, ElementContext ctx) {
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();
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) {
int cursorX = getCursorX(visibleText, scrollOffset);
renderScope.drawRectangle(cursorX, 2, 1, height - 4, Theme.ACCENT.value);
renderScope.drawRectangle(getCursorX(visibleText, scrollOffset), textY - 1, 1, textHeight + 2,
Theme.ACCENT.value);
}
}
@@ -2,14 +2,14 @@ package org.saturnclient.ui.components.inputs;
import org.saturnclient.config.ConfigManager;
import org.saturnclient.config.property.IntProperty;
import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.elements.Field;
public class IntInput extends Input {
public IntProperty prop;
public IntInput(IntProperty prop) {
this.width = 120;
this.height = Fonts.getHeight();
this.width = 96;
this.height = Field.HEIGHT;
this.prop = prop;
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.ElementContext;
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 {
KeybindingProperty prop;
public KeybindingSelector(KeybindingProperty prop) {
this.prop = prop;
this.width = 120;
this.height = Fonts.getHeight();
this.width = 96;
this.height = Field.HEIGHT;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
String name = Providers.GLFW.getKeyName(prop.value);
Field.drawBox(renderScope, width, height, ctx.isHovering(), focused);
renderScope.drawText(0.6f,
prop.value == -1 ? "<NONE>"
: name != null ? name.toUpperCase() : "<Invalid-Key>",
4, 4, Theme.FONT.value, textColor);
if (focused) {
Field.drawCenteredText(renderScope, "Press a key", width, height, Theme.getBodyFont(),
Theme.getMutedFg());
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
@@ -5,7 +5,7 @@ import java.util.function.Consumer;
import org.saturnclient.config.Theme;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.resources.Fonts;
import org.saturnclient.ui.elements.Field;
import org.saturnclient.ui.resources.Textures;
/**
@@ -21,7 +21,6 @@ public class SearchInput extends Input {
public SearchInput(String placeholder, Consumer<String> onChange) {
this.placeholder = placeholder;
this.onChange = onChange;
this.textScale = 0.68f;
this.textX = ICON_X + ICON_SIZE + 8;
this.width = 240;
this.height = 36;
@@ -49,31 +48,17 @@ public class SearchInput extends Input {
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
int border = focused ? Theme.withAlpha(0.6f, Theme.ACCENT.value)
: 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);
Field.drawBox(renderScope, width, height, ctx.isHovering(), focused);
renderScope.drawTexture(Textures.ICON_SEARCH, ICON_X, (height - ICON_SIZE) / 2, 0, 0,
ICON_SIZE, ICON_SIZE, Theme.getMutedFg());
int textHeight = (int) (Fonts.getHeight() * textScale);
int textY = (height - textHeight) / 2;
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;
}
int scrollOffset = getScrollOffset();
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);
}
drawText(renderScope);
}
}
@@ -5,36 +5,50 @@ 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.Utils;
import org.saturnclient.ui.elements.Field;
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 {
private SelectProperty prop;
private static final int ARROW = 16;
private static final int ARROW_INSET = 8;
private final SelectProperty prop;
public Select(SelectProperty prop) {
this.prop = prop;
this.width = 150;
this.height = 15;
this.width = 200;
this.height = Field.HEIGHT;
}
@Override
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,
Theme.FONT.value, Theme.FOREGROUND.value);
renderScope.drawTexture(Textures.ICON_CHEVRON_LEFT, ARROW_INSET, arrowY, 0, 0, ARROW, ARROW,
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
public void click(int mouseX, int mouseY) {
if (Utils.isHovering(mouseX - (width - 10), mouseY, 10, height, 1.0f)) {
prop.next();
} else if (Utils.isHovering(mouseX, mouseY, 10, height, 1.0f)) {
if (mouseX < width / 3) {
prop.prev();
} else {
prop.next();
}
}
}
@@ -7,25 +7,22 @@ import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope;
public class Toggle extends Element {
public static final int SWITCH_WIDTH = 42;
public static final int SWITCH_HEIGHT = 24;
private BoolProperty prop;
public Toggle(BoolProperty prop) {
this.prop = prop;
this.width = 60;
this.height = 30;
this.width = SWITCH_WIDTH;
this.height = SWITCH_HEIGHT;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, 60, 30, Theme.WIDGET_RADIUS.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);
drawSwitch(renderScope, 0, 0, prop.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.
*/