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:
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user