Fix Scenes freeze and Languages up/down navigation
- VsmxInterpreter.interpretFunction: save/restore pc/exit/stack/callStates so re-entrant event firing (FireEventAnim, timers, movie callbacks) no longer truncates the outer script. This is what left ss_btn_mm without focus (Scenes appeared frozen: no selection, input ignored while audio continued). - Add default geometric focus traversal for buttons with no direction handler: Controller.moveFocus picks the nearest visible focusable object. The PSP does this natively; English/Francais buttons have no onUp/onDown events in the RCO and relied on it. - Track page visibility (BaseObject.displayed, set by PageObject.open/close) so traversal skips closed pages and collapsed (alpha 0) columns. - Make BaseObject.trigger null-safe.
This commit is contained in:
@@ -30,6 +30,7 @@ public abstract class BaseObject extends BaseNativeObject implements IDisplay {
|
||||
protected Controller controller;
|
||||
private String name;
|
||||
private BaseNativeObject parent;
|
||||
private boolean displayed;
|
||||
|
||||
private Field[] getSortedFields() {
|
||||
Field[] fields = getClass().getFields();
|
||||
@@ -140,8 +141,17 @@ public abstract class BaseObject extends BaseNativeObject implements IDisplay {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** True while this object is part of an opened page's display list. */
|
||||
public boolean isDisplayed() {
|
||||
return displayed;
|
||||
}
|
||||
|
||||
public void setDisplayed(boolean displayed) {
|
||||
this.displayed = displayed;
|
||||
}
|
||||
|
||||
protected void trigger(EventType event) {
|
||||
if (event.getEvent() != null && controller != null) {
|
||||
if (event != null && event.getEvent() != null && controller != null) {
|
||||
controller.getInterpreter().interpretScript(getObject(), event.getEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import dev.quoije.umd.rco.type.ImageType;
|
||||
import dev.quoije.umd.rco.type.IntType;
|
||||
import dev.quoije.umd.rco.type.TextType;
|
||||
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
|
||||
import dev.quoije.umd.rco.vsmx.objects.Controller;
|
||||
|
||||
/** A button object. */
|
||||
public class ButtonObject extends BasePositionObject {
|
||||
@@ -49,6 +50,14 @@ public class ButtonObject extends BasePositionObject {
|
||||
return focus.getImage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setController(Controller controller) {
|
||||
super.setController(controller);
|
||||
if (controller != null) {
|
||||
controller.registerFocusable(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocus() {
|
||||
trigger(onFocusIn);
|
||||
@@ -68,21 +77,37 @@ public class ButtonObject extends BasePositionObject {
|
||||
|
||||
@Override
|
||||
public void onUp() {
|
||||
if (onFocusUp != null && onFocusUp.getEvent() != null) {
|
||||
trigger(onFocusUp);
|
||||
} else if (controller != null) {
|
||||
controller.moveFocus(this, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDown() {
|
||||
if (onFocusDown != null && onFocusDown.getEvent() != null) {
|
||||
trigger(onFocusDown);
|
||||
} else if (controller != null) {
|
||||
controller.moveFocus(this, 0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeft() {
|
||||
if (onFocusLeft != null && onFocusLeft.getEvent() != null) {
|
||||
trigger(onFocusLeft);
|
||||
} else if (controller != null) {
|
||||
controller.moveFocus(this, -1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRight() {
|
||||
if (onFocusRight != null && onFocusRight.getEvent() != null) {
|
||||
trigger(onFocusRight);
|
||||
} else if (controller != null) {
|
||||
controller.moveFocus(this, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public class PageObject extends BaseObject {
|
||||
VsmxBaseObject children = object.getPropertyValue(Resource.childrenName);
|
||||
for (String childName : children.getPropertyNames()) {
|
||||
VsmxBaseObject child = children.getPropertyValue(childName);
|
||||
setDisplayed(child, true);
|
||||
display.add(child);
|
||||
display(child);
|
||||
}
|
||||
@@ -47,11 +48,18 @@ public class PageObject extends BaseObject {
|
||||
VsmxBaseObject children = object.getPropertyValue(Resource.childrenName);
|
||||
for (String childName : children.getPropertyNames()) {
|
||||
VsmxBaseObject child = children.getPropertyValue(childName);
|
||||
setDisplayed(child, false);
|
||||
display.remove(child);
|
||||
hide(child);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setDisplayed(VsmxBaseObject object, boolean displayed) {
|
||||
if (object instanceof VsmxNativeObject nativeObject && nativeObject.getObject() instanceof BaseObject baseObject) {
|
||||
baseObject.setDisplayed(displayed);
|
||||
}
|
||||
}
|
||||
|
||||
private BasePositionObject getFirstButton(VsmxBaseObject object) {
|
||||
if (!object.hasPropertyValue(Resource.childrenName)) {
|
||||
return null;
|
||||
|
||||
@@ -6,6 +6,7 @@ import dev.quoije.umd.rco.type.EventType;
|
||||
import dev.quoije.umd.rco.type.ImageType;
|
||||
import dev.quoije.umd.rco.type.IntType;
|
||||
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
|
||||
import dev.quoije.umd.rco.vsmx.objects.Controller;
|
||||
|
||||
/** A user button object (navigation with onLeft/onRight/onUp/onDown events). */
|
||||
public class UButtonObject extends BasePositionObject {
|
||||
@@ -39,6 +40,14 @@ public class UButtonObject extends BasePositionObject {
|
||||
onDisplayUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setController(Controller controller) {
|
||||
super.setController(controller);
|
||||
if (controller != null) {
|
||||
controller.registerFocusable(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocus() {
|
||||
trigger(onFocusIn);
|
||||
@@ -58,21 +67,37 @@ public class UButtonObject extends BasePositionObject {
|
||||
|
||||
@Override
|
||||
public void onUp() {
|
||||
if (onUp != null && onUp.getEvent() != null) {
|
||||
trigger(onUp);
|
||||
} else if (controller != null) {
|
||||
controller.moveFocus(this, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDown() {
|
||||
if (onDown != null && onDown.getEvent() != null) {
|
||||
trigger(onDown);
|
||||
} else if (controller != null) {
|
||||
controller.moveFocus(this, 0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeft() {
|
||||
if (onLeft != null && onLeft.getEvent() != null) {
|
||||
trigger(onLeft);
|
||||
} else if (controller != null) {
|
||||
controller.moveFocus(this, -1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRight() {
|
||||
if (onRight != null && onRight.getEvent() != null) {
|
||||
trigger(onRight);
|
||||
} else if (controller != null) {
|
||||
controller.moveFocus(this, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,6 +498,19 @@ public final class VsmxInterpreter {
|
||||
|
||||
/** Interprets a function body in a fresh call frame. */
|
||||
public synchronized void interpretFunction(VsmxFunction function, VsmxBaseObject object, VsmxBaseObject[] arguments) {
|
||||
// This entry point can be invoked re-entrantly (FireEventAnim, timers,
|
||||
// movie callbacks all run from inside an already-running script), so the
|
||||
// outer execution state must be preserved and restored. Without this,
|
||||
// the inner RETURN would set {@code exit} and truncate the outer script.
|
||||
int savedPc = pc;
|
||||
boolean savedExit = exit;
|
||||
Deque<VsmxBaseObject> savedStack = stack;
|
||||
Deque<VsmxCallState> savedCallStates = callStates;
|
||||
VsmxCallState savedCallState = callState;
|
||||
|
||||
callStates = new ArrayDeque<>();
|
||||
stack = null;
|
||||
callState = null;
|
||||
pushCallState(object, function.getLocalVars(), false, true);
|
||||
for (int i = 1; i <= function.getArgs(); i++) {
|
||||
if (arguments == null || i > arguments.length) {
|
||||
@@ -508,6 +521,12 @@ public final class VsmxInterpreter {
|
||||
}
|
||||
pc = function.getStartLine();
|
||||
interpret();
|
||||
|
||||
pc = savedPc;
|
||||
exit = savedExit;
|
||||
stack = savedStack;
|
||||
callStates = savedCallStates;
|
||||
callState = savedCallState;
|
||||
}
|
||||
|
||||
/** Interprets a script event handler by name (e.g. "script:/main/onPush"). */
|
||||
|
||||
@@ -10,6 +10,9 @@ import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNull;
|
||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxString;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** The controller native object: input focus and resource management. */
|
||||
public class Controller extends BaseNativeObject {
|
||||
|
||||
@@ -20,6 +23,7 @@ public class Controller extends BaseNativeObject {
|
||||
private VsmxBaseObject userData;
|
||||
private String resource;
|
||||
private BasePositionObject focus;
|
||||
private final List<BasePositionObject> focusables = new ArrayList<>();
|
||||
|
||||
public static VsmxNativeObject create(VsmxInterpreter interpreter, MenuHost menuHost, String resource) {
|
||||
Controller controller = new Controller(interpreter, menuHost);
|
||||
@@ -79,6 +83,63 @@ public class Controller extends BaseNativeObject {
|
||||
return focus;
|
||||
}
|
||||
|
||||
/** Registers a focusable object so default (geometric) focus traversal can reach it. */
|
||||
public void registerFocusable(BasePositionObject object) {
|
||||
synchronized (focusables) {
|
||||
if (!focusables.contains(object)) {
|
||||
focusables.add(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves focus to the nearest visible, focusable object in the given direction
|
||||
* when a button has no script handler for that direction. {@code dx}/{@code dy}
|
||||
* use the framebuffer coordinate system (+X right, +Y up).
|
||||
*/
|
||||
public void moveFocus(BasePositionObject from, int dx, int dy) {
|
||||
int fromX = from.getX() + from.getWidth() / 2;
|
||||
int fromY = from.getY() + from.getHeight() / 2;
|
||||
|
||||
BasePositionObject best = null;
|
||||
double bestScore = Double.MAX_VALUE;
|
||||
synchronized (focusables) {
|
||||
for (BasePositionObject candidate : focusables) {
|
||||
if (candidate == from || !candidate.isDisplayed() || candidate.getAlpha() <= 0f
|
||||
|| candidate.getImage() == null) {
|
||||
continue;
|
||||
}
|
||||
int candidateX = candidate.getX() + candidate.getWidth() / 2;
|
||||
int candidateY = candidate.getY() + candidate.getHeight() / 2;
|
||||
int offsetX = candidateX - fromX;
|
||||
int offsetY = candidateY - fromY;
|
||||
if (dy < 0 && offsetY >= 0) {
|
||||
continue;
|
||||
}
|
||||
if (dy > 0 && offsetY <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (dx < 0 && offsetX >= 0) {
|
||||
continue;
|
||||
}
|
||||
if (dx > 0 && offsetX <= 0) {
|
||||
continue;
|
||||
}
|
||||
double primary = dx != 0 ? java.lang.Math.abs(offsetX) : java.lang.Math.abs(offsetY);
|
||||
double secondary = dx != 0 ? java.lang.Math.abs(offsetY) : java.lang.Math.abs(offsetX);
|
||||
double score = primary * 10 + secondary;
|
||||
if (score < bestScore) {
|
||||
bestScore = score;
|
||||
best = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best != null) {
|
||||
best.setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
public void onUp() {
|
||||
if (focus != null) {
|
||||
focus.onUp();
|
||||
|
||||
Reference in New Issue
Block a user