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 <[email protected]>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user