Preview easing curves beside curve dropdowns
CurveProperty holds the easing curves in the order Animation.getCurve takes them, replacing three copies of the list in zoom, freelook and the animation config; it's saved the same way. Its dropdown opens a CurveOptions popup: a CurvePreview graph beside the list, with a dot running along the hovered curve, or the current one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.ElementRenderer;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
/**
|
||||
* A curve dropdown's popup: a {@link CurvePreview} beside the list of curves,
|
||||
* showing the hovered curve, or the current one when none is hovered.
|
||||
*/
|
||||
public class CurveOptions extends Element {
|
||||
public static final int GAP = 8;
|
||||
|
||||
private final List<Element> children;
|
||||
|
||||
public CurveOptions(OptionList list, int current) {
|
||||
CurvePreview preview = new CurvePreview(() -> list.getHovered() >= 0 ? list.getHovered() : current);
|
||||
|
||||
preview.position(0, 0);
|
||||
list.position(preview.width + GAP, 0);
|
||||
this.children = List.of(preview, list);
|
||||
|
||||
this.width = list.x + list.width;
|
||||
this.height = Math.max(preview.height, list.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* How far left of the list this popup starts, so the list can line up
|
||||
* under the dropdown.
|
||||
*/
|
||||
public int getListX() {
|
||||
return children.get(1).x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
ElementRenderer.render(children, ctx.elapsed, renderScope, ctx.mouseX, ctx.mouseY, ctx.childFrame());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||
ElementRenderer.mouseClicked(children, mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scroll(int mouseX, int mouseY, double horizontalAmount, double verticalAmount) {
|
||||
ElementRenderer.mouseScrolled(children, mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.anim.Animation;
|
||||
|
||||
/**
|
||||
* A graph of an easing curve, with a dot running along it on a loop to show
|
||||
* how the animation moves.
|
||||
*/
|
||||
public class CurvePreview extends Element {
|
||||
private static final int PADDING = 14;
|
||||
private static final int TITLE_HEIGHT = 20;
|
||||
private static final float TITLE_SCALE = 0.56f;
|
||||
private static final int DOT = 7;
|
||||
|
||||
/** How long the dot takes to run the curve, then how long it rests at the end, in ms */
|
||||
private static final int RUN = 1200;
|
||||
private static final int REST = 500;
|
||||
|
||||
private final IntSupplier curve;
|
||||
|
||||
/**
|
||||
* @param curve the curve to show, as an index {@link Animation#getCurve}
|
||||
* takes; read every frame
|
||||
*/
|
||||
public CurvePreview(IntSupplier curve) {
|
||||
this.curve = curve;
|
||||
this.width = 190;
|
||||
this.height = 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||
Theme.withAlpha(0.97f, Theme.BACKGROUND.value), Theme.withAlpha(0.14f, Theme.FOREGROUND.value));
|
||||
|
||||
renderScope.drawText(TITLE_SCALE, "PREVIEW", PADDING, PADDING - 2, Theme.FONT.value, Theme.getMutedFg());
|
||||
|
||||
Function<Double, Double> ease = Animation.getCurve(curve.getAsInt());
|
||||
|
||||
int graphX = PADDING;
|
||||
int graphY = PADDING + TITLE_HEIGHT;
|
||||
int graphWidth = width - PADDING * 2;
|
||||
int graphHeight = height - PADDING * 2 - TITLE_HEIGHT;
|
||||
|
||||
// Curves like "back" overshoot, so fit the graph to the curve's own range.
|
||||
double min = 0, max = 1;
|
||||
for (int i = 0; i <= graphWidth; i++) {
|
||||
double value = ease.apply(i / (double) graphWidth);
|
||||
min = Math.min(min, value);
|
||||
max = Math.max(max, value);
|
||||
}
|
||||
|
||||
double range = max - min;
|
||||
int startY = toY(0, min, range, graphY, graphHeight);
|
||||
int endY = toY(1, min, range, graphY, graphHeight);
|
||||
|
||||
// Where the animation starts and ends
|
||||
int guide = Theme.withAlpha(0.1f, Theme.FOREGROUND.value);
|
||||
renderScope.fill(graphX, startY, graphX + graphWidth, startY + 1, guide);
|
||||
renderScope.fill(graphX, endY, graphX + graphWidth, endY + 1, guide);
|
||||
|
||||
// The curve, as small squares close enough to read as a line
|
||||
int steps = graphWidth * 3;
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
double t = i / (double) steps;
|
||||
int x = graphX + (int) (t * graphWidth);
|
||||
int y = toY(ease.apply(t), min, range, graphY, graphHeight);
|
||||
renderScope.fill(x - 1, y - 1, x + 1, y + 1, Theme.ACCENT.value);
|
||||
}
|
||||
|
||||
// The dot, looping along it
|
||||
double t = Math.min(1.0, (ctx.elapsed % (RUN + REST)) / (double) RUN);
|
||||
int dotX = graphX + (int) (t * graphWidth);
|
||||
int dotY = toY(ease.apply(t), min, range, graphY, graphHeight);
|
||||
renderScope.drawRoundedRectangle(dotX - DOT / 2, dotY - DOT / 2, DOT, DOT, DOT / 2, 2,
|
||||
Theme.FOREGROUND.value, Theme.BACKGROUND.value);
|
||||
}
|
||||
|
||||
private static int toY(double value, double min, double range, int graphY, int graphHeight) {
|
||||
return graphY + graphHeight - (int) ((value - min) / range * graphHeight);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ public class OptionList extends Element {
|
||||
private int first;
|
||||
/** Wheel movement not yet turned into whole options, so trackpads scroll smoothly */
|
||||
private double pendingScroll = 0;
|
||||
/** The option under the mouse on the last frame, or -1 */
|
||||
private int hovered = -1;
|
||||
|
||||
public OptionList(String[] options, int selected, int width, IntConsumer onPick) {
|
||||
this.options = options;
|
||||
@@ -46,7 +48,7 @@ public class OptionList extends Element {
|
||||
Theme.withAlpha(0.97f, Theme.BACKGROUND.value), Theme.withAlpha(0.14f, Theme.FOREGROUND.value));
|
||||
|
||||
int visible = Math.min(options.length, MAX_VISIBLE);
|
||||
int hovered = getIndexAt(ctx.mouseX, ctx.mouseY);
|
||||
hovered = getIndexAt(ctx.mouseX, ctx.mouseY);
|
||||
|
||||
for (int row = 0; row < visible; row++) {
|
||||
int index = first + row;
|
||||
@@ -95,6 +97,13 @@ public class OptionList extends Element {
|
||||
first = clampFirst(first + whole);
|
||||
}
|
||||
|
||||
/**
|
||||
* The option under the mouse on the last frame, or -1.
|
||||
*/
|
||||
public int getHovered() {
|
||||
return hovered;
|
||||
}
|
||||
|
||||
/**
|
||||
* The option under the mouse, or -1.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.CurveProperty;
|
||||
import org.saturnclient.config.property.SelectProperty;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
@@ -54,6 +55,12 @@ public class Select extends Element {
|
||||
Popup.close();
|
||||
});
|
||||
|
||||
Popup.openBelow(this, list, lastContext);
|
||||
if (prop instanceof CurveProperty) {
|
||||
// The preview goes left of the list, so the list still lines up under this.
|
||||
CurveOptions options = new CurveOptions(list, prop.value);
|
||||
Popup.openBelow(this, options, lastContext, -options.getListX());
|
||||
} else {
|
||||
Popup.openBelow(this, list, lastContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user