Fix menu push, focus scale, and tweened animations
- Implement String.substring so mm_on_push can dispatch the pressed button (Enter did nothing before). - setScale now writes scaleWidth/scaleHeight/scaleDepth (focused item was never enlarged); setRotate stores rotation angles. - animPos/animScale/animColor/animRotate and the animation-table records (move/resize/recolour/fade/rotate) now tween over their duration via a 60fps Animator thread instead of jumping to the final state.
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.anim;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs tween animations on a single daemon thread, applying progress each
|
||||||
|
* frame. Tweens only mutate object state and request a repaint, so they never
|
||||||
|
* re-enter the VSMX interpreter.
|
||||||
|
*/
|
||||||
|
public final class Animator {
|
||||||
|
|
||||||
|
private static final Animator INSTANCE = new Animator();
|
||||||
|
|
||||||
|
private final List<Tween> tweens = new ArrayList<>();
|
||||||
|
|
||||||
|
private Animator() {
|
||||||
|
Thread thread = new Thread(this::run, "umd-animator");
|
||||||
|
thread.setDaemon(true);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Animator getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Registers a tween; it starts on the next animation tick. */
|
||||||
|
public void add(Tween tween) {
|
||||||
|
synchronized (tweens) {
|
||||||
|
tweens.add(tween);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
while (true) {
|
||||||
|
long now = System.nanoTime();
|
||||||
|
List<Tween> done = new ArrayList<>();
|
||||||
|
synchronized (tweens) {
|
||||||
|
for (Tween tween : tweens) {
|
||||||
|
if (tween.step(now)) {
|
||||||
|
done.add(tween);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tweens.removeAll(done);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(16); // ~60 fps
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,8 +18,18 @@ public class FadeAnim extends BaseAnim {
|
|||||||
@Override
|
@Override
|
||||||
protected void doPlay(VsmxBaseObject object) {
|
protected void doPlay(VsmxBaseObject object) {
|
||||||
applyTo(ref, target -> {
|
applyTo(ref, target -> {
|
||||||
target.alphaScale.setFloatValue(transparency.getFloatValue());
|
float ta = transparency.getFloatValue();
|
||||||
target.onDisplayUpdated();
|
long millis = (long) duration.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
target.alphaScale.setFloatValue(ta);
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float sa = target.alphaScale.getFloatValue();
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
target.alphaScale.setFloatValue(sa + (ta - sa) * p);
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,26 @@ public class MoveToAnim extends BaseAnim {
|
|||||||
@Override
|
@Override
|
||||||
protected void doPlay(VsmxBaseObject object) {
|
protected void doPlay(VsmxBaseObject object) {
|
||||||
applyTo(ref, target -> {
|
applyTo(ref, target -> {
|
||||||
target.animX = x.getFloatValue();
|
float tx = x.getFloatValue();
|
||||||
target.animY = y.getFloatValue();
|
float ty = y.getFloatValue();
|
||||||
target.animZ = z.getFloatValue();
|
float tz = z.getFloatValue();
|
||||||
target.onDisplayUpdated();
|
long millis = (long) duration.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
target.animX = tx;
|
||||||
|
target.animY = ty;
|
||||||
|
target.animZ = tz;
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float sx = target.animX;
|
||||||
|
float sy = target.animY;
|
||||||
|
float sz = target.animZ;
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
target.animX = sx + (tx - sx) * p;
|
||||||
|
target.animY = sy + (ty - sy) * p;
|
||||||
|
target.animZ = sz + (tz - sz) * p;
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,30 @@ public class RecolourAnim extends BaseAnim {
|
|||||||
@Override
|
@Override
|
||||||
protected void doPlay(VsmxBaseObject object) {
|
protected void doPlay(VsmxBaseObject object) {
|
||||||
applyTo(ref, target -> {
|
applyTo(ref, target -> {
|
||||||
target.redScale.setFloatValue(red.getFloatValue());
|
float tr = red.getFloatValue();
|
||||||
target.greenScale.setFloatValue(green.getFloatValue());
|
float tg = green.getFloatValue();
|
||||||
target.blueScale.setFloatValue(blue.getFloatValue());
|
float tb = blue.getFloatValue();
|
||||||
target.alphaScale.setFloatValue(alpha.getFloatValue());
|
float ta = alpha.getFloatValue();
|
||||||
target.onDisplayUpdated();
|
long millis = (long) duration.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
target.redScale.setFloatValue(tr);
|
||||||
|
target.greenScale.setFloatValue(tg);
|
||||||
|
target.blueScale.setFloatValue(tb);
|
||||||
|
target.alphaScale.setFloatValue(ta);
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float sr = target.redScale.getFloatValue();
|
||||||
|
float sg = target.greenScale.getFloatValue();
|
||||||
|
float sb = target.blueScale.getFloatValue();
|
||||||
|
float sa = target.alphaScale.getFloatValue();
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
target.redScale.setFloatValue(sr + (tr - sr) * p);
|
||||||
|
target.greenScale.setFloatValue(sg + (tg - sg) * p);
|
||||||
|
target.blueScale.setFloatValue(sb + (tb - sb) * p);
|
||||||
|
target.alphaScale.setFloatValue(sa + (ta - sa) * p);
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,26 @@ public class ResizeAnim extends BaseAnim {
|
|||||||
@Override
|
@Override
|
||||||
protected void doPlay(VsmxBaseObject object) {
|
protected void doPlay(VsmxBaseObject object) {
|
||||||
applyTo(ref, target -> {
|
applyTo(ref, target -> {
|
||||||
target.scaleWidth.setFloatValue(width.getFloatValue());
|
float tw = width.getFloatValue();
|
||||||
target.scaleHeight.setFloatValue(height.getFloatValue());
|
float th = height.getFloatValue();
|
||||||
target.scaleDepth.setFloatValue(depth.getFloatValue());
|
float td = depth.getFloatValue();
|
||||||
target.onDisplayUpdated();
|
long millis = (long) duration.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
target.scaleWidth.setFloatValue(tw);
|
||||||
|
target.scaleHeight.setFloatValue(th);
|
||||||
|
target.scaleDepth.setFloatValue(td);
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float sw = target.scaleWidth.getFloatValue();
|
||||||
|
float sh = target.scaleHeight.getFloatValue();
|
||||||
|
float sd = target.scaleDepth.getFloatValue();
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
target.scaleWidth.setFloatValue(sw + (tw - sw) * p);
|
||||||
|
target.scaleHeight.setFloatValue(sh + (th - sh) * p);
|
||||||
|
target.scaleDepth.setFloatValue(sd + (td - sd) * p);
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import dev.quoije.umd.rco.ObjectField;
|
|||||||
import dev.quoije.umd.rco.type.FloatType;
|
import dev.quoije.umd.rco.type.FloatType;
|
||||||
import dev.quoije.umd.rco.type.IntType;
|
import dev.quoije.umd.rco.type.IntType;
|
||||||
import dev.quoije.umd.rco.type.ObjectType;
|
import dev.quoije.umd.rco.type.ObjectType;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
|
||||||
/** Rotates the referenced object (type 4). */
|
/** Rotates the referenced object (type 4). */
|
||||||
public class RotateAnim extends BaseAnim {
|
public class RotateAnim extends BaseAnim {
|
||||||
@@ -15,4 +16,30 @@ public class RotateAnim extends BaseAnim {
|
|||||||
@ObjectField(order = 4) public FloatType x;
|
@ObjectField(order = 4) public FloatType x;
|
||||||
@ObjectField(order = 5) public FloatType y;
|
@ObjectField(order = 5) public FloatType y;
|
||||||
@ObjectField(order = 6) public FloatType z;
|
@ObjectField(order = 6) public FloatType z;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPlay(VsmxBaseObject object) {
|
||||||
|
applyTo(ref, target -> {
|
||||||
|
float tx = x.getFloatValue();
|
||||||
|
float ty = y.getFloatValue();
|
||||||
|
float tz = z.getFloatValue();
|
||||||
|
long millis = (long) duration.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
target.rotX = tx;
|
||||||
|
target.rotY = ty;
|
||||||
|
target.rotZ = tz;
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float sx = target.rotX;
|
||||||
|
float sy = target.rotY;
|
||||||
|
float sz = target.rotZ;
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
target.rotX = sx + (tx - sx) * p;
|
||||||
|
target.rotY = sy + (ty - sy) * p;
|
||||||
|
target.rotZ = sz + (tz - sz) * p;
|
||||||
|
target.onDisplayUpdated();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.anim;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single interpolation from 0..1 over a duration in milliseconds. The
|
||||||
|
* callback receives linear progress each frame until completion.
|
||||||
|
*/
|
||||||
|
public final class Tween {
|
||||||
|
|
||||||
|
/** Receives progress 0..1 on every animation tick. */
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ProgressCallback {
|
||||||
|
void apply(float progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final long startNanos;
|
||||||
|
private final long durationNanos;
|
||||||
|
private final ProgressCallback callback;
|
||||||
|
private boolean finished;
|
||||||
|
|
||||||
|
public Tween(long durationMillis, ProgressCallback callback) {
|
||||||
|
this.startNanos = System.nanoTime();
|
||||||
|
this.durationNanos = Math.max(1L, durationMillis) * 1_000_000L;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Advances the tween and reports whether it has finished. */
|
||||||
|
boolean step(long nowNanos) {
|
||||||
|
float progress = (nowNanos - startNanos) / (float) durationNanos;
|
||||||
|
if (progress >= 1f) {
|
||||||
|
progress = 1f;
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
callback.apply(progress);
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
package dev.quoije.umd.rco.object;
|
package dev.quoije.umd.rco.object;
|
||||||
|
|
||||||
import dev.quoije.umd.rco.ObjectField;
|
import dev.quoije.umd.rco.ObjectField;
|
||||||
|
import dev.quoije.umd.rco.anim.Animator;
|
||||||
|
import dev.quoije.umd.rco.anim.Tween;
|
||||||
import dev.quoije.umd.rco.type.EventType;
|
import dev.quoije.umd.rco.type.EventType;
|
||||||
import dev.quoije.umd.rco.type.FloatType;
|
import dev.quoije.umd.rco.type.FloatType;
|
||||||
import dev.quoije.umd.rco.type.IntType;
|
import dev.quoije.umd.rco.type.IntType;
|
||||||
@@ -37,6 +39,11 @@ public class BasePositionObject extends BaseObject {
|
|||||||
public float animY;
|
public float animY;
|
||||||
public float animZ;
|
public float animZ;
|
||||||
|
|
||||||
|
/** Rotation angles in radians (the renderer applies rotZ). */
|
||||||
|
public float rotX;
|
||||||
|
public float rotY;
|
||||||
|
public float rotZ;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getX() {
|
public int getX() {
|
||||||
int parentX = 0;
|
int parentX = 0;
|
||||||
@@ -133,6 +140,9 @@ public class BasePositionObject extends BaseObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setScale(VsmxBaseObject object, VsmxBaseObject w, VsmxBaseObject h, VsmxBaseObject d) {
|
public void setScale(VsmxBaseObject object, VsmxBaseObject w, VsmxBaseObject h, VsmxBaseObject d) {
|
||||||
|
scaleWidth.setFloatValue(w.getFloatValue());
|
||||||
|
scaleHeight.setFloatValue(h.getFloatValue());
|
||||||
|
scaleDepth.setFloatValue(d.getFloatValue());
|
||||||
onDisplayUpdated();
|
onDisplayUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,22 +165,92 @@ public class BasePositionObject extends BaseObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setRotate(VsmxBaseObject object, VsmxBaseObject x, VsmxBaseObject y, VsmxBaseObject rotationRads) {
|
public void setRotate(VsmxBaseObject object, VsmxBaseObject x, VsmxBaseObject y, VsmxBaseObject rotationRads) {
|
||||||
|
rotX = x.getFloatValue();
|
||||||
|
rotY = y.getFloatValue();
|
||||||
|
rotZ = rotationRads.getFloatValue();
|
||||||
onDisplayUpdated();
|
onDisplayUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void animColor(VsmxBaseObject object, VsmxBaseObject red, VsmxBaseObject green, VsmxBaseObject blue, VsmxBaseObject alpha, VsmxBaseObject duration) {
|
public void animColor(VsmxBaseObject object, VsmxBaseObject red, VsmxBaseObject green, VsmxBaseObject blue, VsmxBaseObject alpha, VsmxBaseObject duration) {
|
||||||
setColor(object, red, green, blue, alpha);
|
long millis = duration.getIntValue();
|
||||||
|
float sr = redScale.getFloatValue();
|
||||||
|
float sg = greenScale.getFloatValue();
|
||||||
|
float sb = blueScale.getFloatValue();
|
||||||
|
float sa = alphaScale.getFloatValue();
|
||||||
|
float tr = red.getFloatValue();
|
||||||
|
float tg = green.getFloatValue();
|
||||||
|
float tb = blue.getFloatValue();
|
||||||
|
float ta = alpha.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
setColor(object, red, green, blue, alpha);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
redScale.setFloatValue(sr + (tr - sr) * p);
|
||||||
|
greenScale.setFloatValue(sg + (tg - sg) * p);
|
||||||
|
blueScale.setFloatValue(sb + (tb - sb) * p);
|
||||||
|
alphaScale.setFloatValue(sa + (ta - sa) * p);
|
||||||
|
onDisplayUpdated();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void animScale(VsmxBaseObject object, VsmxBaseObject w, VsmxBaseObject h, VsmxBaseObject d, VsmxBaseObject duration) {
|
public void animScale(VsmxBaseObject object, VsmxBaseObject w, VsmxBaseObject h, VsmxBaseObject d, VsmxBaseObject duration) {
|
||||||
setScale(object, w, h, d);
|
long millis = duration.getIntValue();
|
||||||
|
float sw = scaleWidth.getFloatValue();
|
||||||
|
float sh = scaleHeight.getFloatValue();
|
||||||
|
float sd = scaleDepth.getFloatValue();
|
||||||
|
float tw = w.getFloatValue();
|
||||||
|
float th = h.getFloatValue();
|
||||||
|
float td = d.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
setScale(object, w, h, d);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
scaleWidth.setFloatValue(sw + (tw - sw) * p);
|
||||||
|
scaleHeight.setFloatValue(sh + (th - sh) * p);
|
||||||
|
scaleDepth.setFloatValue(sd + (td - sd) * p);
|
||||||
|
onDisplayUpdated();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void animPos(VsmxBaseObject object, VsmxBaseObject x, VsmxBaseObject y, VsmxBaseObject z, VsmxBaseObject duration) {
|
public void animPos(VsmxBaseObject object, VsmxBaseObject x, VsmxBaseObject y, VsmxBaseObject z, VsmxBaseObject duration) {
|
||||||
setPos(object, x, y, z);
|
long millis = duration.getIntValue();
|
||||||
|
float sx = posX.getFloatValue();
|
||||||
|
float sy = posY.getFloatValue();
|
||||||
|
float sz = posZ.getFloatValue();
|
||||||
|
float tx = x.getFloatValue();
|
||||||
|
float ty = y.getFloatValue();
|
||||||
|
float tz = z.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
setPos(object, x, y, z);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
posX.setFloatValue(sx + (tx - sx) * p);
|
||||||
|
posY.setFloatValue(sy + (ty - sy) * p);
|
||||||
|
posZ.setFloatValue(sz + (tz - sz) * p);
|
||||||
|
onDisplayUpdated();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void animRotate(VsmxBaseObject object, VsmxBaseObject x, VsmxBaseObject y, VsmxBaseObject rotationRads, VsmxBaseObject duration) {
|
public void animRotate(VsmxBaseObject object, VsmxBaseObject x, VsmxBaseObject y, VsmxBaseObject rotationRads, VsmxBaseObject duration) {
|
||||||
setRotate(object, x, y, rotationRads);
|
long millis = duration.getIntValue();
|
||||||
|
float sx = rotX;
|
||||||
|
float sy = rotY;
|
||||||
|
float sz = rotZ;
|
||||||
|
float tx = x.getFloatValue();
|
||||||
|
float ty = y.getFloatValue();
|
||||||
|
float tz = rotationRads.getFloatValue();
|
||||||
|
if (millis <= 0) {
|
||||||
|
setRotate(object, x, y, rotationRads);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Animator.getInstance().add(new Tween(millis, p -> {
|
||||||
|
rotX = sx + (tx - sx) * p;
|
||||||
|
rotY = sy + (ty - sy) * p;
|
||||||
|
rotZ = sz + (tz - sz) * p;
|
||||||
|
onDisplayUpdated();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ public final class VsmxString extends VsmxBaseObject {
|
|||||||
return super.getPropertyValue(name);
|
return super.getPropertyValue(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject substring(VsmxBaseObject object, VsmxBaseObject beginIndex) {
|
||||||
|
int begin = Math.max(0, Math.min(beginIndex.getIntValue(), value.length()));
|
||||||
|
return new VsmxString(interpreter, value.substring(begin));
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject substring(VsmxBaseObject object, VsmxBaseObject beginIndex, VsmxBaseObject endIndex) {
|
||||||
|
int begin = Math.max(0, Math.min(beginIndex.getIntValue(), value.length()));
|
||||||
|
int end = Math.max(begin, Math.min(endIndex.getIntValue(), value.length()));
|
||||||
|
return new VsmxString(interpreter, value.substring(begin, end));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(VsmxBaseObject value) {
|
public boolean equals(VsmxBaseObject value) {
|
||||||
return getStringValue().equals(value.getStringValue());
|
return getStringValue().equals(value.getStringValue());
|
||||||
|
|||||||
Reference in New Issue
Block a user