scene2d Table forked up

Getting a bit cozy are you, what with your scene2d application compiling smugly? Say no more, I have a treat for you!

As you know, TableLayout is one of my (Nate’s) OSS projects. The core performs UI toolkit agnostic layout and only a small bridge needs to be written to layout for the toolkit of your choice. The UI toolkits it supports are Swing, Android, TWL and of course libgdx’s scene2d.ui. Sadly, few outside of libgdx have seen the blessed layouting light and continue to plod along with suboptimal tools. Anyway, the bridge makes for some slight API awkwardness and extra indirection under the covers. Since TableLayout is mostly used for scene2d and given that I certainly don’t want to (or plan to) subject myself to the pain of using other UI toolkits, I figured we might as well fork TableLayout and integrate it properly into libgdx.

Rest assured, everything is now completely forked. What is left are two classes: Table and Cell. They work mostly as they did before, but you’ll need to organize your imports since they are now in the scene2d.ui package. The API has changed slightly:

  • Cell#getWidget -> Cell#getActor
  • Cell#setWidget -> Cell#setActor
  • Other methods with “widget” in name -> “actor”, since they take Actor, not Widget.
  • cell.getLayout().getTable() -> cell.getTable()
  • Cell#ignore is gone. This was never used AFAIK mostly on account of not being very useful.
  • TableLayout and Toolkit are gone. Table does it all.

In addition, Value has changed a bit. It is simpler and no longer specific to Table. I plan to use it elsewhere soon, eg in Container.

So there you have it. API breakage should be easy to fix and the new Table works as it did before.

Stage API changes: Viewport added

noone had a great idea, to provide a Viewport class for determining how world coordinates are scaled to screen coordinates. This was already possible using Camera of course, but it was not noob friendly. Viewport is standalone and just manages a Camera. This means any app that uses Camera can use Viewport to control the strategy for scaling the world to the screen. For example, maybe you want to stretch the world to the screen, or scale it up without changing the aspect ratio by using black bars.

After a couple iterations we’ve now integrated the Viewport class. It’s quite slick, you just pick from one of the ready made Viewport implementations and you’re good to go. See the nice wiki page for docs.

Here’s where I tell you about the pain you are in for! It makes sense for Stage to have a Viewport instead of a Camera (remember Viewport has a Camera). This makes controlling scaling for scene2d super easy, but the bad part is Stage had to change. Instead of telling the Stage the viewport size, you give it a Viewport instance that does what you want.

If your old Stage construction code looked like this:

stage = new Stage(width, height, false); // OR
stage.setViewport(width, height, false);

Then you need to change it to:

stage = new Stage(new StretchViewport(width, height)); // OR
stage.setViewport(new StretchViewport(width, height));

If your old code looked like this:

stage = new Stage(width, height, true); // OR
stage.setViewport(width, height, true);

Then you need to change it to:

stage = new Stage(new ExtendViewport(width, height)); // OR
stage.setViewport(new ExtendViewport(width, height));

There is another important change, resize used to look like:

public void resize (int width, int height) {
    stage.setViewport(width, height);
}

Now you’ll do this:

public void resize (int width, int height) {
    stage.getViewport().update(width, height, true);
}

The boolean centers the camera if true (only really needed for UIs). It can be omitted, which is the same as false.

The other changes that may affect you are where you are using the Stage’s Camera. You can use stage.getViewport().getCamera(), but note that Viewport has project/unproject methods that should be used instead, since they take the viewport into account.

Not too bad for you guys I hope!

scene2d.ui Selection

scene2d.ui’s List has never been terribly feature-packed, mostly because it only really sees use in SelectBox. I ended up needing it for skinning in Spine, so it got some love.

First, it can now draw a background which makes it easier to use outside of a ScrollPane. I figured even a Label has a background in its style, so why not.

Next I needed multiple selection, so I set off implementing it. While this seems like such a simple task, just managing a list of selected objects, it turns a proper implementation is quite a chunk of code: more than 250 lines. This is literally larger than the rest of the List implementation! SelectBox and Tree also have selection, and their implementations aren’t really complete. Given this, I decided to create a class called Selection which handles that, and List, SelectBox, and Tree can use it.

Selection turned out pretty nice. At its core it is an ObjectSet with set, add, remove, clear methods. The choose method handles the logic for changing the selection based on user interaction and has features like multiple selection, holding ctrl, toggle, etc. When the selection is changed a ChangeEvent is fired on an actor. If the event is cancelled, the change to the selection is reverted.

Now the bad news, List, SelectBox, and Tree had to change to accomodate the new class. Here are the API changes that may impact you:

  • List and SelectBox previously used strings for the items and you had to correlate the string or selected index back to your list of actual objects. Now they use generics, so you can store your actual objects in the widget, eg List. To get the old behavior, use List or SelectBox.
  • List and SelectBox no longer take items in their constructor. Instead, call setItems after construction. There is now a setItems that takes varargs, which is convenient.
  • Most selection methods having moved to Selection, which you get from getSelection(), but a few stayed behind:
    • getSelection() -> getSelected() or getSelection().first()
    • setSelection() -> setSelected() or getSelection().set()
    • getSelectionIndex() -> getSelectedIndex()

As always, sorry for the API breakage! This effort and your pain is for the greater good. I had to fix up my 50k+ LOC scene2d.ui application so, as your dad probably used to say, this hurts me more than it hurts you. ;)