[RFC] BigBanana: mouseless navigation

I’ve started working on a “big picture” navigation library. It is useful when the game is supposed to be played with a joypad and you don’t have a mouse for navigating menus.

The idea is that the library takes over the input, and sends event to the game-specific appstate.
At any given time only one appstate will then receive events from BigBanana.
For now I’ve hacked the mouse picking lemur to have it navigable with keys. Eventually, the Spatial components will be grouped in a navigable layout.

6 Likes

I wonder if you can take advantage of the joypad navigation I’ve already added to Lemur or if this is somehow different enough for that not to matter.

Or are you actually controlling a cursor on screen?

I’ve committed a proto-API with a very basic yet working implementation.

The “focusable” component must implement FocusHandler, like this:

https://github.com/Pesegato/BigBanana/blob/master/src/main/java/com/pesegato/bigbanana/sampleapp/TestCube.java

Then these component are initialized for example on a AppState:

https://github.com/Pesegato/BigBanana/blob/master/src/main/java/com/pesegato/bigbanana/sampleapp/BananizedAppState.java

And this is the sample application:

I’ve also added my way of loading input mapping from a file

Looks like I’ve missed some of your recent commits on Lemur :expressionless:

However, the scope of BigBanana might still be different from Lemur… must check.

OK, it looks like BigBanana is somewhat redundant, however:

Does Lemur requires a layout manager for navigation? BigBanana allows to put Spatial on screen with absolute coordinates and just declare the focus order.

Lemur navigation works only for Lemur component (and as such inherit the styling etc), while BigBanana decouples the navigation from visual rapresentation: you have to come up with method that change the look of your component.

Finally BigBanana aims to be a solution for application-wide mouseless. The swing loading screen will eventually be disabled and all the input taken over by BigBanana, which in turn deliver the events to the appstates. There will be a button ‘A’ to “confirm” and another ‘B’ to “cancel” and so on.

Basically your game will play like a crappy console port… oh wait

Lemur’s focus works with any Spatial that has a FocusTraveral implementing control. GuiControl happens to implement this so Lemur standard GUI elements get it for free… but it works with anything.

The FocusNavigationState looks up the hierarchy of the currently focused Spatial for anything with a FocusTraveral control… then will use that to decide where to shift focus based on the standard focus traversal functions. (Left, Right, Up, Down, Home, End, Next, Previous, etc.)

Can I try it? Lemur’s BasicDemo don’t seem to respond to focus changes…

If you are building from source then you would need to run something that sets the initial focus. There is no built in “root level” support for navigating to the initial focus. It’s on my to-do list.

You can try the sim-ethereal example… it makes sure to set focus to the initial panel. (Pretty sure that sim-eth-basic does but if not then sim-eth-es definitely does.)

1 Like

Back to this. Yes, it works. :slight_smile:

But I have a few non-Lemur components (spatials) that are placed on the scenegraph with absolute coordinates and therefore without a real parent or layoutmanager (for some reasons).

According to the javadoc, if I want the Lemur navigation I need GuiLayout and apply a TraversalDirection.
What do you suggest? Add a invisibile container and manage the focus from there or is there some better solution?

Besides: if you have 2 containers on screen the navigation works inside the container, but for going on the next container you have to use the mouse…

I would have to reload all of this into my brain to answer intelligently so I will try to leave some bread crumbs.

I believe at the lowest level there is some app state or something that is managing the “where to go next” based on some InputMapper functions mapping calling the methods. The traversal within Lemur standard GUI elements is just default behavior built into those classes. If you track down how that code decides where to go next then you might figure out how to insert your own stuff. The layouts just have default behavior within this focus handling system but the intent was the focus management should be decoupled from the default GUI elements.

At least that’s my memory on the design.

I’ve checked the sources, and I see that it leverages heavily the Container paradigm, and it also assumes that it is either horizontal or vertical.

I want it to be layout-agnostic, so this is what I suggest (and what I’m going to implement):

The focusable components must be assigned an area on the virtual “focus plane”. Think of it like a spreadsheet where each component take at least one cell (but might also take multiple cells in a rectangular shape).
When you add a focusable you must specify x coordinate, y coordinate, width and height. This way, from every component you can “move” in all 4 directions (when there’s a focusable component on that direction).
Thoughts?

Ok, so you are going to make me take time to look at the source…

As you can see by this code:

The only thing necessary to participate in focus traversal is a FocusTraversal control attached to your spatial.

It tells the FocusNavigationState how to get to the next spatial. You could even put one of these on the root node if you want.

Never a Lemur GUI element in sight.

2 Likes

I made this

    class BBFocusTraversal extends AbstractControl implements FocusTraversal {

    @Override
    public Spatial getDefaultFocus() {
        return db1;
    }

    @Override
    public Spatial getRelativeFocus(Spatial sptl, FocusTraversal.TraversalDirection td) {
        System.out.println("FOCUS CHANGE: "+sptl+" "+td);
        return db1;
    }

    @Override
    public boolean isFocusRoot() {
        //not sure???
        return false;
    }

    @Override
    protected void controlUpdate(float tpf) {
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
    }

}

and

    db1.addControl(new BBFocusTraversal());

and

@Override
protected void onEnable() {
    getApplication().getInputManager().setCursorVisible(true);
    GuiGlobals.getInstance().requestFocus(db1.getParent());
}

Then I hit the focus move that works on the lemur, but here I don’t see the FOCUS CHANGE println… What did I miss?
Thanks!

I assume that GuiGlobals has been initialized so that the navigation state was attached.

If so, the easiest way to figure out what’s going on is to set a breakpoint in FocusNavigationState and see what’s going on.

I’m a bad Java-developer and didn’t put logging (trace or otherwise) into this class yet.

Edit: or if you build Lemur from source, you can uncomment the System.out.println() in that class to see if the handler is even getting called when you hit tab or whatever. :slight_smile:

Uncommented the println and indeed the handler is called. But my FocusTraversal above is never called.

I should add that when the game starts I show a Lemur menu appstate that is navigable. Then I detach the menu appstate, and I attach my non-Lemur components that should be managed with my FocusTraversal.

As I said, I get the println, and if I track down the execution I go into the FocusTraversal path. Tried to understand what’s going on but without docs is a bit difficult.

So… requestChangeFocus() is called?

Does getFocusContainer(spatial) return null then?

All that’s doing is checking the currently focused spatial for a FocusTraversal control and then each of its parents.

I assume you’ve already started the focus somewhere with request focus so that it has something to find.

Edit: clarified what “current spatial” was… “currently focused spatial”

Yes, requestChangeFocus is called, and yes, getFocusContainer(spatial) return null.

But, as I said, I’ve done

db1.addControl(new BBFocusTraversal());

and

@Override
protected void onEnable() {
    getApplication().getInputManager().setCursorVisible(true);
    GuiGlobals.getInstance().requestFocus(db1.getParent());
}

I’ve also tried with

GuiGlobals.getInstance().requestFocus(db1);

But with no success.

From what I understand: I add a FocusTraversal to my component, then I request focus to it, then I fire a focus change but db1 is ignored…?

Ooops. I think you have found a bug. (Congrats?) :slight_smile:

This method:

Is skipping the current focused spatial… which I think is wrong. Which means the comments are wrong and the for loop is wrong. I’m not sure if it breaks anything to fix it but you could try changing the loop to:
for( Spatial s = spatial; s != null; s = s.getParent() ) {

:tada::tada::tada::tada::tada: achievement unlocked :sunglasses::sunglasses::sunglasses::sunglasses:

1 Like

While we’re at it, there’s also this