From 06a90d8e518cd37f8308d2fd937e135bbfc62ace Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 27 Sep 2026 17:39:27 +0200 Subject: [PATCH] Edit ranged settings with sliders IntProperty and FloatProperty can take an optional range, step and unit. PropertyList shows those settings as a Slider: a track to click or drag, snapping to the step, with the value and unit beside it. Zoom and freelook levels and durations, fullbright brightness, the theme radii, animation timings and HUD scale and radius get ranges; the rest stay number boxes. Co-Authored-By: Claude Opus 5.5 --- .../saturnclient/config/AnimationConfig.java | 4 +- .../java/org/saturnclient/config/Theme.java | 4 +- .../config/property/FloatProperty.java | 25 ++++ .../config/property/IntProperty.java | 29 ++++ .../java/org/saturnclient/mod/ModLayout.java | 4 +- .../saturnclient/mod/mods/FreelookMod.java | 6 +- .../saturnclient/mod/mods/FullbrightMod.java | 2 +- .../org/saturnclient/mod/mods/ZoomMod.java | 6 +- .../ui/components/PropertyList.java | 5 +- .../ui/components/inputs/Slider.java | 134 ++++++++++++++++++ 10 files changed, 204 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/saturnclient/ui/components/inputs/Slider.java diff --git a/src/main/java/org/saturnclient/config/AnimationConfig.java b/src/main/java/org/saturnclient/config/AnimationConfig.java index acaadda..5451773 100644 --- a/src/main/java/org/saturnclient/config/AnimationConfig.java +++ b/src/main/java/org/saturnclient/config/AnimationConfig.java @@ -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"); } diff --git a/src/main/java/org/saturnclient/config/Theme.java b/src/main/java/org/saturnclient/config/Theme.java index dbfca5f..67f79e6 100644 --- a/src/main/java/org/saturnclient/config/Theme.java +++ b/src/main/java/org/saturnclient/config/Theme.java @@ -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); diff --git a/src/main/java/org/saturnclient/config/property/FloatProperty.java b/src/main/java/org/saturnclient/config/property/FloatProperty.java index 316e90c..413999d 100644 --- a/src/main/java/org/saturnclient/config/property/FloatProperty.java +++ b/src/main/java/org/saturnclient/config/property/FloatProperty.java @@ -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; diff --git a/src/main/java/org/saturnclient/config/property/IntProperty.java b/src/main/java/org/saturnclient/config/property/IntProperty.java index 66d1a84..3a07ed8 100644 --- a/src/main/java/org/saturnclient/config/property/IntProperty.java +++ b/src/main/java/org/saturnclient/config/property/IntProperty.java @@ -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; diff --git a/src/main/java/org/saturnclient/mod/ModLayout.java b/src/main/java/org/saturnclient/mod/ModLayout.java index e7df485..2f79c30 100644 --- a/src/main/java/org/saturnclient/mod/ModLayout.java +++ b/src/main/java/org/saturnclient/mod/ModLayout.java @@ -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; diff --git a/src/main/java/org/saturnclient/mod/mods/FreelookMod.java b/src/main/java/org/saturnclient/mod/mods/FreelookMod.java index 53b6067..46f75d1 100644 --- a/src/main/java/org/saturnclient/mod/mods/FreelookMod.java +++ b/src/main/java/org/saturnclient/mod/mods/FreelookMod.java @@ -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 --- diff --git a/src/main/java/org/saturnclient/mod/mods/FullbrightMod.java b/src/main/java/org/saturnclient/mod/mods/FullbrightMod.java index 9b34c5b..1ca00d9 100644 --- a/src/main/java/org/saturnclient/mod/mods/FullbrightMod.java +++ b/src/main/java/org/saturnclient/mod/mods/FullbrightMod.java @@ -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( diff --git a/src/main/java/org/saturnclient/mod/mods/ZoomMod.java b/src/main/java/org/saturnclient/mod/mods/ZoomMod.java index 92a66c8..f2f899f 100644 --- a/src/main/java/org/saturnclient/mod/mods/ZoomMod.java +++ b/src/main/java/org/saturnclient/mod/mods/ZoomMod.java @@ -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"); diff --git a/src/main/java/org/saturnclient/ui/components/PropertyList.java b/src/main/java/org/saturnclient/ui/components/PropertyList.java index 91e1a39..96b49b9 100644 --- a/src/main/java/org/saturnclient/ui/components/PropertyList.java +++ b/src/main/java/org/saturnclient/ui/components/PropertyList.java @@ -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) { diff --git a/src/main/java/org/saturnclient/ui/components/inputs/Slider.java b/src/main/java/org/saturnclient/ui/components/inputs/Slider.java new file mode 100644 index 0000000..b9f24ab --- /dev/null +++ b/src/main/java/org/saturnclient/ui/components/inputs/Slider.java @@ -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; + } +}