Redesign the mod menu, settings and cosmetics screens #6

Merged
selimaj-dev merged 13 commits from ui-redesign into master 2026-09-27 16:27:35 +00:00
10 changed files with 204 additions and 15 deletions
Showing only changes of commit 06a90d8e51 - Show all commits
@@ -42,8 +42,8 @@ public class AnimationConfig {
public final SelectProperty curve;
public AnimationConfig(boolean enabled, int delay, int stagger) {
this.duration = Property.integer(delay);
this.stagger = Property.integer(stagger);
this.duration = Property.integer(delay).range(0, 2000, 50).unit("ms");
this.stagger = Property.integer(stagger).range(0, 300, 10).unit("ms");
this.curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic", "Ease In Out Back");
}
@@ -32,8 +32,8 @@ public class Theme {
public static final ColorProperty SCROLL = Property.color(0xFFE2BF7D);
// UI metrics
public static final IntProperty BG_RADIUS = Property.integer(12);
public static final IntProperty WIDGET_RADIUS = Property.integer(10);
public static final IntProperty BG_RADIUS = Property.integer(12).range(0, 24);
public static final IntProperty WIDGET_RADIUS = Property.integer(10).range(0, 20);
// Font
public static final SelectProperty FONT = Property.font(2);
@@ -7,11 +7,36 @@ public class FloatProperty extends Property {
public float value;
public final float defaultValue;
/** Bounds for a slider, when the setting has a sensible range */
public Float min, max;
public float step = 0.1f;
/** Shown after the value, like "×" */
public String unit = "";
public FloatProperty(float defaultValue) {
this.value = defaultValue;
this.defaultValue = defaultValue;
}
/**
* Gives the setting a range, so it's edited with a slider.
*/
public FloatProperty range(float min, float max, float step) {
this.min = min;
this.max = max;
this.step = step;
return this;
}
public FloatProperty unit(String unit) {
this.unit = unit;
return this;
}
public boolean hasRange() {
return min != null && max != null;
}
@Override
public void reset() {
this.value = defaultValue;
@@ -7,11 +7,40 @@ public class IntProperty extends Property {
public int value;
public final int defaultValue;
/** Bounds for a slider, when the setting has a sensible range */
public Integer min, max;
public int step = 1;
/** Shown after the value, like "ms" */
public String unit = "";
public IntProperty(int defaultValue) {
this.value = defaultValue;
this.defaultValue = defaultValue;
}
/**
* Gives the setting a range, so it's edited with a slider.
*/
public IntProperty range(int min, int max) {
return range(min, max, 1);
}
public IntProperty range(int min, int max, int step) {
this.min = min;
this.max = max;
this.step = step;
return this;
}
public IntProperty unit(String unit) {
this.unit = unit;
return this;
}
public boolean hasRange() {
return min != null && max != null;
}
@Override
public void reset() {
this.value = defaultValue;
@@ -13,10 +13,10 @@ import org.saturnclient.config.property.NamedProperty;
public class ModLayout {
public IntProperty x = Property.integer(0);
public IntProperty y = Property.integer(0);
public FloatProperty scale = Property.floatProp(1.0f);
public FloatProperty scale = Property.floatProp(1.0f).range(0.5f, 4f, 0.1f).unit("×");
public ColorProperty bgColor = Property.color(0x00000000);
public ColorProperty fgColor = Property.color(0xFFffffff);
public IntProperty radius = Property.integer(0);
public IntProperty radius = Property.integer(0).range(0, 20);
public SelectProperty font = Property.font(0);
public boolean renderBackground = true;
@@ -22,9 +22,9 @@ public class FreelookMod extends Mod {
private static final BoolProperty toggle = Property.bool(true);
private static final KeybindingProperty freelookKey = Property.keybinding(GLFWProvider.GLFW_KEY_H);
public static final FloatProperty zoomLevel = Property.floatProp(2.0f);
public static final IntProperty zoomInDuration = Property.integer(500);
public static final IntProperty zoomOutDuration = Property.integer(300);
public static final FloatProperty zoomLevel = Property.floatProp(2.0f).range(1f, 5f, 0.1f).unit("×");
public static final IntProperty zoomInDuration = Property.integer(500).range(0, 2000, 50).unit("ms");
public static final IntProperty zoomOutDuration = Property.integer(300).range(0, 2000, 50).unit("ms");
public static final SelectProperty curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic", "Ease In Out Back");
// --- State Variables ---
@@ -19,7 +19,7 @@ import org.saturnclient.mod.ModSpec;
public class FullbrightMod extends Mod {
private static final BoolProperty enabled = Property.bool(false);
private static final IntProperty brightness = Property.integer(100);
private static final IntProperty brightness = Property.integer(100).range(0, 1000, 10);
public FullbrightMod() {
super(
@@ -18,9 +18,9 @@ public class ZoomMod extends Mod {
private static final BoolProperty enabled = Property.bool(false);
private static final BoolProperty toggle = Property.bool(false);
public static final KeybindingProperty zoomKey = Property.keybinding(GLFWProvider.GLFW_KEY_C);
public static final FloatProperty zoomLevel = Property.floatProp(3.0f);
public static final IntProperty zoomInDuration = Property.integer(700);
public static final IntProperty zoomOutDuration = Property.integer(300);
public static final FloatProperty zoomLevel = Property.floatProp(3.0f).range(1f, 10f, 0.5f).unit("×");
public static final IntProperty zoomInDuration = Property.integer(700).range(0, 2000, 50).unit("ms");
public static final IntProperty zoomOutDuration = Property.integer(300).range(0, 2000, 50).unit("ms");
public static final SelectProperty curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic",
"Ease In Out Back");
@@ -23,6 +23,7 @@ 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.Slider;
import org.saturnclient.ui.components.inputs.Toggle;
import org.saturnclient.ui.elements.Label;
import org.saturnclient.ui.elements.Scroll;
@@ -104,9 +105,9 @@ public final class PropertyList {
} else if (prop instanceof ColorProperty p) {
return new ColorInput(p);
} else if (prop instanceof IntProperty p) {
return new IntInput(p);
return p.hasRange() ? Slider.of(p) : new IntInput(p);
} else if (prop instanceof FloatProperty p) {
return new FloatInput(p);
return p.hasRange() ? Slider.of(p) : new FloatInput(p);
} else if (prop instanceof SelectProperty p) {
return new Select(p);
} else if (prop instanceof KeybindingProperty p) {
@@ -0,0 +1,134 @@
package org.saturnclient.ui.components.inputs;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Locale;
import java.util.function.DoubleConsumer;
import java.util.function.DoubleSupplier;
import org.saturnclient.config.Theme;
import org.saturnclient.config.property.FloatProperty;
import org.saturnclient.config.property.IntProperty;
import org.saturnclient.ui.Element;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.elements.Field;
/**
* Edits a number within a range by clicking or dragging along a track, with
* the value shown beside it.
*/
public class Slider extends Element {
private static final int TRACK_HEIGHT = 6;
private static final int KNOB = 16;
private static final int VALUE_WIDTH = 72;
private static final int VALUE_GAP = 14;
private final double min, max, step;
private final int decimals;
private final String unit;
private final DoubleSupplier value;
private final DoubleConsumer onChange;
private boolean dragging = false;
private Slider(double min, double max, double step, String unit, DoubleSupplier value, DoubleConsumer onChange) {
this.min = min;
this.max = max;
this.step = step;
this.unit = unit;
this.value = value;
this.onChange = onChange;
this.decimals = Math.max(0, BigDecimal.valueOf(step).stripTrailingZeros().scale());
this.width = 280;
this.height = Field.HEIGHT;
}
public static Slider of(IntProperty prop) {
return new Slider(prop.min, prop.max, prop.step, prop.unit, () -> prop.value,
v -> prop.value = (int) Math.round(v));
}
public static Slider of(FloatProperty prop) {
return new Slider(prop.min, prop.max, prop.step, prop.unit, () -> prop.value,
v -> prop.value = (float) v);
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
double current = value.getAsDouble();
float progress = (float) Math.max(0, Math.min(1, (current - min) / (max - min)));
int trackWidth = getTrackWidth();
int trackY = (height - TRACK_HEIGHT) / 2;
int fillWidth = (int) (trackWidth * progress);
renderScope.drawRoundedRectangle(KNOB / 2, trackY, trackWidth, TRACK_HEIGHT, TRACK_HEIGHT / 2,
Theme.withAlpha(0.14f, Theme.FOREGROUND.value));
if (fillWidth > 0) {
renderScope.drawRoundedRectangle(KNOB / 2, trackY, fillWidth, TRACK_HEIGHT, TRACK_HEIGHT / 2,
Theme.ACCENT.value);
}
boolean knobHovered = dragging || ctx.isHovering(fillWidth, 0, KNOB, height);
renderScope.drawRoundedRectangle(fillWidth, (height - KNOB) / 2, KNOB, KNOB, KNOB / 2, 3,
knobHovered ? Theme.ACCENT.value : Theme.FOREGROUND.value, Theme.BACKGROUND.value);
// Value
int valueX = width - VALUE_WIDTH;
renderScope.drawRoundedRectangle(valueX, 0, VALUE_WIDTH, height, Theme.WIDGET_RADIUS.value,
Theme.withAlpha(0.07f, Theme.FOREGROUND.value));
renderScope.getMatrixStack().push();
renderScope.getMatrixStack().translate(valueX, 0);
Field.drawCenteredText(renderScope, format(current), VALUE_WIDTH, height, Theme.FONT.value,
Theme.FOREGROUND.value);
renderScope.getMatrixStack().pop();
}
@Override
public void mouseClicked(double mouseX, double mouseY, int button) {
dragging = mouseX >= 0 && mouseX <= getTrackWidth() + KNOB && mouseY >= 0 && mouseY <= height;
if (dragging) {
setFromMouse(mouseX);
}
}
@Override
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
if (dragging) {
setFromMouse(mouseX);
}
}
@Override
public void mouseReleased(double mouseX, double mouseY, int button) {
dragging = false;
}
private void setFromMouse(double mouseX) {
double progress = Math.max(0, Math.min(1, (mouseX - KNOB / 2.0) / getTrackWidth()));
double raw = min + progress * (max - min);
double snapped = min + Math.round((raw - min) / step) * step;
// Snapping in floating point can leave 2.9999999, so round to the step's decimals.
double rounded = BigDecimal.valueOf(snapped).setScale(decimals, RoundingMode.HALF_UP).doubleValue();
onChange.accept(Math.max(min, Math.min(max, rounded)));
}
private String format(double current) {
String number = String.format(Locale.ROOT, "%." + decimals + "f", current);
if (unit.isEmpty()) {
return number;
}
// Word units get a space ("700 ms"); symbols don't ("3.0×").
return Character.isLetter(unit.charAt(0)) ? number + " " + unit : number + unit;
}
private int getTrackWidth() {
return width - VALUE_WIDTH - VALUE_GAP - KNOB;
}
}