Selectable State Machine

Can someone explain to me how the Selectable State Machine works? I have not been able to reverse engineer it and I can't find documentation on it.

I stripped down the sample Selectable sample code so there's only one check box and I stripped down the onSelectable function to just the following.

function onSelectable(event) {
Sys.println("Instance " + event.getInstance());
Sys.println("PrevState " + event.getPreviousState());
Sys.println("GetState " + event.getInstance().getState());
return true;
}


When I click on the checkbox, it seems to call onSelectable 3 times and advance the state each time. I get the following output from a single click on the check box (in the simulator)

Instance Obj: 42
PrevState symbol (8389897)
GetState symbol (8389898)
Instance Obj: 42
PrevState symbol (8389898)
GetState symbol (8389899)
Instance Obj: 42
PrevState symbol (8389899)
GetState symbol (8389897)


Why does it seem to cycle through 3 of the 4 defined states automatically from a single click? I think I've cut out anything that could be manually altering the state in the rest of the code.
  • The state transitions you show above are stateDefault => stateHighlighted => stateSelected => stateDefault.

    This is just a guess since I've not experimented with the selectable stuff... It seems that this might just be part of the Ui animation. Have you tried instrumenting CheckBoxView.onUpdate() to see if the view is being redrawn between the state transitions?

    Travis
  • I've instrumented the code a bit better, and here is what I see happening on a touch device like the edge_1000...

    // initial update
    onUpdate

    // press on a checkbox, but do not release
    onSelectable(instance=Obj: 64 prev=Default next=Highlighted)
    onUpdate

    // release
    onSelectable(instance=Obj: 64 prev=Highlighted next=Selected)
    onSelectable(instance=Obj: 64 prev=Selected next=Default)
    onUpdate
    onUpdate


    When you press down, the state changes to highlighted to indicate focus. You can raise your finger off of the highlighted selectable (complete the select), or you can slide your finger off of the selectable and release (abort the select).

    If you complete the select, the state changes from highlighted to selected (you've completed an interaction with the selectable, it is now selected), and then it changes from selected to default (the selectable no longer has the focus and is no longer selected). You can respond to the state changes however you want, include adding additional state (like the CheckBox class does in the sample)

    Travis
  • Thanks Travis.

    Have you been able to discern an underlying method to the state sequences? It acts differently when I try to set a state. Sometimes OnSelectable gets called 3 times, sometimes 2 and sometimes 4 times.

    I haven't even been able to make a simple bit of code that goes from Default to Selected and back with consecutive touches.

    This code produces the results like you showed.

    function onSelectable(event) {
    Sys.println("Instance " + event.getInstance());
    Sys.println("PrevState " + symbol_table[event.getPreviousState()]);
    Sys.println("GetState " + symbol_table[event.getInstance().getState()]);

    return true;
    }


    Produces

    Instance Obj: 50
    PrevState :stateDefault
    GetState :stateHighlighted
    Instance Obj: 50
    PrevState :stateHighlighted
    GetState :stateSelected
    Instance Obj: 50
    PrevState :stateSelected
    GetState :stateDefault


    But this code:

    function onSelectable(event) {
    Sys.println("Instance " + event.getInstance());
    Sys.println("PrevState " + symbol_table[event.getPreviousState()]);
    Sys.println("GetState " + symbol_table[event.getInstance().getState()]);

    if (event.getPreviousState() == :stateSelected && event.getInstance().getState() == :stateDefault) {
    event.getInstance().setState(:stateSelected);
    }

    return true;
    }


    Produces this after the first press (and changes the look of the selectable to "Selected")

    Instance Obj: 50
    PrevState :stateDefault
    GetState :stateHighlighted
    Instance Obj: 50
    PrevState :stateHighlighted
    GetState :stateSelected
    Instance Obj: 50
    PrevState :stateSelected
    GetState :stateDefault
    Instance Obj: 50
    PrevState :stateSelected
    GetState :stateDefault


    And produces this after the second and subsequest presses (and leaves the look as "Selected")
    Instance Obj: 50
    PrevState :stateSelected
    GetState :stateHighlighted
    Instance Obj: 50
    PrevState :stateHighlighted
    GetState :stateSelected
    Instance Obj: 50
    PrevState :stateSelected
    GetState :stateDefault
    Instance Obj: 50
    PrevState :stateSelected
    GetState :stateDefault


    Then if I change the code to this:
    function onSelectable(event) {
    Sys.println("Instance " + event.getInstance());
    Sys.println("PrevState " + symbol_table[event.getPreviousState()]);
    Sys.println("GetState " + symbol_table[event.getInstance().getState()]);

    event.getInstance().setState(:stateDefault);

    return true;
    }


    I get only two calls to onSelectable and the selectable stays drawn as "Default"

    Instance Obj: 50
    PrevState :stateDefault
    GetState :stateHighlighted
    Instance Obj: 50
    PrevState :stateDefault
    GetState :stateSelected


    I'm always just clicking the selectable. I'm not doing the press and slide off thing because that's one more layer of complexity.

    Thanks for any help in decoding what seems like an enigma machine to me.
  • Have you been able to discern an underlying method to the state sequences?

    I haven't really tried until now, but it seems pretty simple. The highlighted state indicates that a selectable is currently being interacted with (it has the focus). The selected state indicates that a selectable has been selected (user has completed a tap or has pressed the start/stop key while the selectable had the focus).

    With a touch-enabled device, you give focus to a selectable by touching it. When this occurs, a state change will transition the selectable from the default state to highlighted. When you release, the interaction is terminated. A transition to the selected state is generated if you release over the selectable. Whether or not the selectable is selected, a transition to the default state will occur.

    With a non-touch device, things are a tiny bit different. You give a selectable focus by by switching to interaction mode or by pressing the up/down keys. When a selectable gets the focus, it will get transition to the highlighted state. When it loses focus, it will get a transition to the default state. If you press start/stop when a selectable has the focus, it will receive a selected event.

    I haven't even been able to make a simple bit of code that goes from Default to Selected and back with consecutive touches.

    Isn't this exactly what the CheckBox and CheckBoxList classes try to do? I think you might be trying to overthink this (it looks like the author of the sample program may have done so as well). The following code implements the CheckBox without the need for the CheckBoxList class to do any event handling.

    class Checkbox extends Ui.Selectable {

    var stateHighlightedSelected;

    function initialize(options) {
    Selectable.initialize(options);

    // Set each state value to a Drawable, color/number, or null
    stateHighlightedSelected = options.get(:stateHighlightedSelected);
    }

    function handleEvent(previousState, nextState) {

    if (previousState == :stateSelected) {

    if (nextState == :stateHighlighted) {
    setState(:stateHighlightedSelected);
    }

    else if (nextState == :stateSelected) {
    Sys.println("???");
    setState(:stateDefault);
    }

    else if (nextState == :stateDefault) {
    setState(:stateSelected);
    Sys.println("Checked!");
    }
    }
    else if (previousState == :stateHighlighted) {

    if (nextState == :stateSelected) {
    setState(:stateHighlightedSelected);
    }
    }
    else if (previousState == :stateHighlightedSelected) {

    if (nextState == :stateDefault) {
    setState(:stateSelected);
    }
    else if(nextState == :stateSelected) {
    setState(:stateHighlighted);
    Sys.println("Unchecked!");
    }
    }
    }
    }


    Here is my CheckBoxDelegate.onSelectableEvent() code.

    function onSelectable(event) {
    var instance = event.getInstance();

    var previousState = event.getPreviousState();
    var nextState = instance.getState();

    instance.handleEvent(previousState, nextState);

    return true;
    }


    With this, the global currentView can go away, as can the CheckBoxView.checkBoxes member and the CheckBoxView.handleEvent() method.

    Travis