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
|
||||
protected void doPlay(VsmxBaseObject object) {
|
||||
applyTo(ref, target -> {
|
||||
target.alphaScale.setFloatValue(transparency.getFloatValue());
|
||||
target.onDisplayUpdated();
|
||||
float ta = transparency.getFloatValue();
|
||||
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
|
||||
protected void doPlay(VsmxBaseObject object) {
|
||||
applyTo(ref, target -> {
|
||||
target.animX = x.getFloatValue();
|
||||
target.animY = y.getFloatValue();
|
||||
target.animZ = z.getFloatValue();
|
||||
target.onDisplayUpdated();
|
||||
float tx = x.getFloatValue();
|
||||
float ty = y.getFloatValue();
|
||||
float tz = z.getFloatValue();
|
||||
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
|
||||
protected void doPlay(VsmxBaseObject object) {
|
||||
applyTo(ref, target -> {
|
||||
target.redScale.setFloatValue(red.getFloatValue());
|
||||
target.greenScale.setFloatValue(green.getFloatValue());
|
||||
target.blueScale.setFloatValue(blue.getFloatValue());
|
||||
target.alphaScale.setFloatValue(alpha.getFloatValue());
|
||||
target.onDisplayUpdated();
|
||||
float tr = red.getFloatValue();
|
||||
float tg = green.getFloatValue();
|
||||
float tb = blue.getFloatValue();
|
||||
float ta = alpha.getFloatValue();
|
||||
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
|
||||
protected void doPlay(VsmxBaseObject object) {
|
||||
applyTo(ref, target -> {
|
||||
target.scaleWidth.setFloatValue(width.getFloatValue());
|
||||
target.scaleHeight.setFloatValue(height.getFloatValue());
|
||||
target.scaleDepth.setFloatValue(depth.getFloatValue());
|
||||
target.onDisplayUpdated();
|
||||
float tw = width.getFloatValue();
|
||||
float th = height.getFloatValue();
|
||||
float td = depth.getFloatValue();
|
||||
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.IntType;
|
||||
import dev.quoije.umd.rco.type.ObjectType;
|
||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||
|
||||
/** Rotates the referenced object (type 4). */
|
||||
public class RotateAnim extends BaseAnim {
|
||||
@@ -15,4 +16,30 @@ public class RotateAnim extends BaseAnim {
|
||||
@ObjectField(order = 4) public FloatType x;
|
||||
@ObjectField(order = 5) public FloatType y;
|
||||
@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;
|
||||
|
||||
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.FloatType;
|
||||
import dev.quoije.umd.rco.type.IntType;
|
||||
@@ -37,6 +39,11 @@ public class BasePositionObject extends BaseObject {
|
||||
public float animY;
|
||||
public float animZ;
|
||||
|
||||
/** Rotation angles in radians (the renderer applies rotZ). */
|
||||
public float rotX;
|
||||
public float rotY;
|
||||
public float rotZ;
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
int parentX = 0;
|
||||
@@ -133,6 +140,9 @@ public class BasePositionObject extends BaseObject {
|
||||
}
|
||||
|
||||
public void setScale(VsmxBaseObject object, VsmxBaseObject w, VsmxBaseObject h, VsmxBaseObject d) {
|
||||
scaleWidth.setFloatValue(w.getFloatValue());
|
||||
scaleHeight.setFloatValue(h.getFloatValue());
|
||||
scaleDepth.setFloatValue(d.getFloatValue());
|
||||
onDisplayUpdated();
|
||||
}
|
||||
|
||||
@@ -155,22 +165,92 @@ public class BasePositionObject extends BaseObject {
|
||||
}
|
||||
|
||||
public void setRotate(VsmxBaseObject object, VsmxBaseObject x, VsmxBaseObject y, VsmxBaseObject rotationRads) {
|
||||
rotX = x.getFloatValue();
|
||||
rotY = y.getFloatValue();
|
||||
rotZ = rotationRads.getFloatValue();
|
||||
onDisplayUpdated();
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
public boolean equals(VsmxBaseObject value) {
|
||||
return getStringValue().equals(value.getStringValue());
|
||||
|
||||
Reference in New Issue
Block a user