2012-03-01

#4 - libgdx Tutorial: TableLayout

Libgdx version used on this post: 0.9.2 (download)
It's time to start coding our great menu screen. Before digging into it, let's have a look at some important changes I made to the source code.
As of libgdx 0.9.6 some of the classes used on this post were modified or removed. You may want to read the post #13 after reading this post, which covers those changes.

Important changes

  • Updated the version of libgdx to the latest version available. All I had to do was download the latest build and replace the JARs and .so files on both projects (tyrian-android and tyrian-game). We should get used to this procedure.
  • Changes on AbstractScreen:
    • The collaborators (BitmapFont, SpriteBatch, Skin) are now lazily loaded.
    • The show() method redirects the input processing to our Stage object through: Gdx.input.setInputProcessor( stage );
    • The hide() method calls dispose(), or the dispose() method would never be called at all.

TWL - Themable Widget Library

Libgdx includes a library called TWL. This is the description copied from their official web-site: TWL is a graphical user interface library for Java built on top of OpenGL. It provides a rich set of standard widgets including labels, edit fields, tables, popups, tooltips, frames and a lot more. Different layout container are available to create even the most advanced user interfaces.

When compared to libgdx's scene2d's UI abstractions, TWL is way more powerful. It contains even an HTML renderer capable of processing CSS configuration. You can fully customize your widgets using a tool called TWL Theme Editor (Java Web-Start link). For the sake of simplicity we'll stick to scene2d, but in the future we can move up to TWL.

Some examples of TWL GUIs:


Creating the Menu Screen with scene2d

Using the TextButton (com.badlogic.gdx.scenes.scene2d.ui.TextButton) actor that comes with libgdx we could create a simple menu screen with three buttons: "Start game", "Options" and "Hall of Fame". Let's remember the screen flow published on a previous post:


We'll use the following screen layout:
  • A welcome message should be displayed at the top.
  • The three buttons should appear sequentially.
  • All components should be vertically aligned on the center of the screen.
I know, a designer would be welcome here. But let's focus on the code. :)
The most straightforward way of implementing this would be to assign absolute values to the (x,y) coordinates of each actor. If we did that, we'd come up with the following code:
package com.blogspot.steigert.tyrian.screens;

import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.ClickListener;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.blogspot.steigert.tyrian.Tyrian;

public class MenuScreen
    extends
        AbstractScreen
{
    // setup the dimensions of the menu buttons
    private static final float BUTTON_WIDTH = 300f;
    private static final float BUTTON_HEIGHT = 60f;
    private static final float BUTTON_SPACING = 10f;

    public MenuScreen(
        Tyrian game )
    {
        super( game );
    }

    @Override
    public void resize(
        int width,
        int height )
    {
        super.resize( width, height );
        final float buttonX = ( width - BUTTON_WIDTH ) / 2;
        float currentY = 280f;

        // label "welcome"
        Label welcomeLabel = new Label( "Welcome to Tyrian for Android!", getSkin() );
        welcomeLabel.x = ( ( width - welcomeLabel.width ) / 2 );
        welcomeLabel.y = ( currentY + 100 );
        stage.addActor( welcomeLabel );

        // button "start game"
        TextButton startGameButton = new TextButton( "Start game", getSkin() );
        startGameButton.x = buttonX;
        startGameButton.y = currentY;
        startGameButton.width = BUTTON_WIDTH;
        startGameButton.height = BUTTON_HEIGHT;
        stage.addActor( startGameButton );

        // button "options"
        TextButton optionsButton = new TextButton( "Options", getSkin() );
        optionsButton.x = buttonX;
        optionsButton.y = ( currentY -= BUTTON_HEIGHT + BUTTON_SPACING );
        optionsButton.width = BUTTON_WIDTH;
        optionsButton.height = BUTTON_HEIGHT;
        stage.addActor( optionsButton );

        // button "hall of fame"
        TextButton hallOfFameButton = new TextButton( "Hall of Fame", getSkin() );
        hallOfFameButton.x = buttonX;
        hallOfFameButton.y = ( currentY -= BUTTON_HEIGHT + BUTTON_SPACING );
        hallOfFameButton.width = BUTTON_WIDTH;
        hallOfFameButton.height = BUTTON_HEIGHT;
        stage.addActor( hallOfFameButton );
    }
}
And the following result:


All right, it does the job. But is this code easy to read and maintain? It contains many mathematical calculations just to come up with screen coordinates for a simple design. Let's try something better.

Introducing the TabletLayout

We could use the TableLayout project, which: is a lightweight library for describing a hierarchy of GUI widgets and laying them out using rows and columns. Assembling GUI widgets in code is clumsy and obfuscates the widget hierarchy. TableLayout provides its own simple and concise language for expressing the GUI. TableLayout cleanly describes an entire object graph of GUI widgets, leaving the Java code clean and uncluttered.

That's exactly what we want, and these are the steps to follow:
  1. Open the TableLayout Editor application (Java Web-Start link).
  2. Write the layout we want following the editor's language.
  3. Save the layout descriptor to a text file inside our resources folder.
  4. On the MenuScreen class, load this layout descriptor and make final configurations.
With the following layout descriptor:
debug
* spacing:10 padding:0 align:center
'Welcome to Tyrian for Android!' spacingBottom:20
---
[startGameButton] width:300 height:60
---
[optionsButton] width:300 height:60
---
[hallOfFameButton] width:300 height:60
I got the following preview:


Now we should save this layout descriptor inside our resources folder. I saved it under: tyrian-android/assets/layout-descriptors/menu-screen.txt
The final step is to modify our MenuScreen class to read this file, as follows:
package com.blogspot.steigert.tyrian.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.ClickListener;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.Table;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.TableLayout;
import com.blogspot.steigert.tyrian.Tyrian;

public class MenuScreen
    extends
        AbstractScreen
{
    public MenuScreen(
        Tyrian game )
    {
        super( game );
    }

    @Override
    public void resize(
        int width,
        int height )
    {
        super.resize( width, height );

        // retrieve the skin (created on the AbstractScreen class)
        Skin skin = super.getSkin();

        // create the table actor
        Table table = new Table( getSkin() );
        table.width = width;
        table.height = height;

        // add the table to the stage and retrieve its layout
        stage.addActor( table );
        TableLayout layout = table.getTableLayout();

        // [edit] this section is not needed
        // register the label "welcome"
        // Label welcomeLabel = new Label( "Welcome to Tyrian for Android!", skin );
        // layout.register( "welcomeMessage", welcomeLabel );

        // register the button "start game"
        TextButton startGameButton = new TextButton( "Start game", skin );
        startGameButton.setClickListener( new ClickListener() {
            @Override
            public void click(
                Actor actor,
                float x,
                float y )
            {
                game.setScreen( game.getStartGameScreen() );
            }
        } );
        layout.register( "startGameButton", startGameButton );

        // register the button "options"
        TextButton optionsButton = new TextButton( "Options", skin );
        optionsButton.setClickListener( new ClickListener() {
            @Override
            public void click(
                Actor actor,
                float x,
                float y )
            {
                game.setScreen( game.getOptionsScreen() );
            }
        } );
        layout.register( "optionsButton", optionsButton );

        // register the button "hall of fame"
        TextButton hallOfFameButton = new TextButton( "Hall of Fame", skin );
        hallOfFameButton.setClickListener( new ClickListener() {
            @Override
            public void click(
                Actor actor,
                float x,
                float y )
            {
                game.setScreen( game.getHallOfFameScreen() );
            }
        } );
        layout.register( "hallOfFameButton", hallOfFameButton );

        // finally, parse the layout descriptor
        layout.parse( Gdx.files.internal( "layout-descriptors/menu-screen.txt" ).readString() );
    }
}
And that's it! All mathematical calculations are gone. I also set the listeners to the buttons, so that they move the player to other screens. Note that I'm just adding the table to the stage. The other actors are registered on the table's layout, using the same IDs contained in the layout descriptor.

Conclusion

In this post we saw how to implement a screen layout separating the presentation (layout descriptor) from the controller (MenuScreen) code using TableLayout. Now we can easily read it and modify it when necessary. It took us some effort to learn a new component, but this effort will certainly pay off as we create and maintain the other screens. I didn't detail the Skin object we used because it's not so important at this moment, and I would need an entire post for it. This is the tag on Subversion for this post. Thanks!

52 comments:

  1. I'm new to Android and this just excellent! Thanks so much for sharing.

    ReplyDelete
  2. Very nice. Your articles help.

    ReplyDelete
  3. I was using tablelayout in a project of mine which is different than this articles menu screen. but a problem I had was creating the actors inside the resize method which kept duplicating actors. So i created the actors inside the create(or show) method then in the resize method I reassign width and height to table, then redid the layout (e.g. layout.reset, register all the actors, then parse the layout)

    ReplyDelete
  4. That's true. Like create, resize is an initialization method and executing it repeatedly is not desired. I believe that's because of a design bug in libgdx. In order to avoid this problem, I do two things:

    1) When setting the initial screen in the Tyrian class, I do it inside the resize method after super.resize() completed.
    2) In the resize method of AbstractScreen, I call "stage.clear()", removing any actors on the stage.

    But your solution is more elegant. Thanks for the contribution!

    ReplyDelete
  5. Thank you very much! I am working hard on this, I was performing a similar, window based ui's but this is really interesting, I will try it out!

    ReplyDelete
  6. Has the bug with the resize being called twice been fixed? Because the resize method is not being called twice anymore.

    Also, are you part of the LibGDX project?

    ReplyDelete
    Replies
    1. Yeah, it has been fixed. Check out the introduction on post #5. I used the above suggestion from xryz. And no, I'm have no connection with the libgdx project. ;)

      Delete
  7. :@ Do we have to use table layout? can we not pass in texture and render the damn button

    ReplyDelete
    Replies
    1. Using TableLayout is completely optional. The advantage of using it, is that positioning your elements on the screen gets easier. It's also possible to use a custom skin to show beautiful UI widgets.

      Delete
  8. Love the tutorial, it's very helpful on a lot of points. Looking over your skin file, I went and expanded the image to explain what pieces are used where, and better understand what the components are called. Feel free to use the image if you like, but this helped me get a better understanding of the skin terminology.

    Expanded Skin Atlas

    ReplyDelete
    Replies
    1. The Skin feature of libgdx is somewhat complicated. It lacks documentation, so we have to end up understanding the code. :(
      I thought of writing about it, but I read somewhere that the guys of libgdx will refactor this feature (alongside with the scene2d package), so let's wait and see!
      Thanks for reading!

      Delete
  9. How can we download the source code for each part to test and play with it in our own ?

    ReplyDelete
    Replies
    1. You can checkout the tag related to each post by using Subversion.
      https://code.google.com/p/steigert-libgdx/

      Delete
  10. why you delete my comment ?????

    ReplyDelete
    Replies
    1. Hi Rawan,
      I'm not deleting your comments, and I don't know why they're not appearing here.

      But I received your comment in my email:
      hi ,, i try to create a menu for my fame so i test your code but i faces problem with it ,,:(
      eclips told me that there a problem "Error reading file: uiskin.json"
      "Error loading font file: default.fnt" because it's invalid
      can you help me and told me what i can do to solve this problem, please ???

      I have no idea! Have you tried the libgdx forum?
      http://www.badlogicgames.com/forum/

      Regards

      Delete
  11. Thanks you very much, Gustavo, for this tutorial!

    I'm working hard on this, but when i launch the desktop application the splash image appears and then, nothing but black screen, i obtain the following error, and i don't know how
    to fix it :

    A fatal error has been detected by the Java Runtime Environment:
    #
    # SIGSEGV (0xb) at pc=0xb75eb78f, pid=20024, tid=1789393728
    #
    # JRE version: 6.0_24-b24
    # Java VM: OpenJDK Server VM (20.0-b12 mixed mode linux-x86 )
    # Derivative: IcedTea6 1.11.1
    # Distribution: Ubuntu 12.04 LTS, package 6b24-1.11.1-4ubuntu3
    # Problematic frame:
    # C [libc.so.6+0x7578f]

    ReplyDelete
    Replies
    1. Hi Makavelli,
      Yeah, I had some similar problems too. Check out the comments on the next post.
      It seems that if you comment the "stage.dispose();" call in AbstractScreen#dispose, the problem goes away. Could you please test that?
      Thanks!

      Delete
    2. Hey guys,

      It worked for me too. Is this going to have a big impact on the game?
      I do have another question though, how would you show an background image underneath the table layout? It looks like it's not showing any image for me, I just have a text button that is not clickable for some reason and acts as a label with no action.

      Delete
  12. Hi Gustavo,

    I have just tested it, and the problem disappeared ;)

    Thanks a lot for your help!!

    ReplyDelete
  13. Hi!
    I try to do this layout. but i encounter a problem with the uiskin files. I tried like 6 versions of uiskin.json and png and fonts, but it just wont work. Now it says uiskin.png is missing from the folder, when its clearly not (assets/data/uiskin.png. I even cleaned my project and it still isnt working.
    It may be an eclipse bug because after closing eclipse, the whole IDE wont start, its generating an error log with some thread issue (!?)...

    Anyways...Could you please send me the assets folder you got?

    Thx for the tutorials, they help a lot!

    ReplyDelete
    Replies
    1. In this new version of libgdx the Skin class was redesigned. Try using the skin files at: http://code.google.com/p/libgdx/source/browse/#svn%2Ftrunk%2Ftests%2Fgdx-tests-lwjgl%2Fassets%2Fui

      If you have a custom Skin, you'll have to read the new Skin documentation and update it accordingly.

      And regarding the assets of this tutorial, you can get all the files at: https://code.google.com/p/steigert-libgdx/source/browse/#svn%2Ftrunk
      Tip: use a Subversion client to check-out the trunk.

      Delete
    2. I still cant get it work! I put the files in the assets/ui/ folder and now it says this:
      http://pastebin.com/tYiN42xP

      However if i change this:
      com.badlogic.gdx.graphics.g2d.BitmapFont: {
      default-font: "default.fnt"
      },
      to this (like it is in the documentation):
      com.badlogic.gdx.graphics.g2d.BitmapFont: {
      default-font: {file: default.fnt"}
      },

      i will get the following error:
      Caused by: java.lang.ClassNotFoundException: com.badlogic.gdx.scenes.scene2d.ui.ImageToggleButton$ImageToggleButtonStyle

      I'm using the latest stable version of libGDX (0.9.6).

      Delete
    3. Using the version 0.9.6 of libgdx, I got it to work with these files:
      https://code.google.com/p/steigert-libgdx/source/browse/#svn%2Ftags%2Fpost-20120709%2Ftyrian-android%2Fassets%2Fskin

      I suggest you try and use them.. If it doesn't help, try posting a question in the libgdx official forum at: http://badlogicgames.com/forum/

      Sorry I doesn't have enough time to help you out!

      Delete
    4. If anyone else has problems reading uiskin.json, the new files are the answer, but they (and all other code for LibGDX) have moved from Google Code to the Github repository here: https://github.com/libgdx/libgdx/tree/master/tests/gdx-tests-android/assets/data

      Delete
  14. I think i actually solved it and the code in the MenuScreen will finally run. However now, i get this:
    http://bit.ly/LOUpEg

    ReplyDelete
    Replies
    1. Uh...sorry for this. I have just seen the comments above mine! Thanks for the help! :)

      Delete
  15. Hi,
    I wish to ask,in latest version,because I'm using portrait with 320 x 480 size of screen.

    But seen the menu button look too big,how do I adjust it?

    Sorry about this just learning libgdx.

    ReplyDelete
    Replies
    1. In the post #9 you'll find some answers! :)

      Delete
  16. Hi,

    I am getting following error related to reading of menu-screen.txt:
    Exception in thread "LWJGL Application" com.esotericsoftware.tablelayout.ParseException: Error parsing layout on line 5:2 near: !DOCTYPE html>

    What can be the reason?
    Google search doesn't give much on this issue.

    Manish

    ReplyDelete
    Replies
    1. It seems like an HTML tag! The menu-screen.txt should not contain it. Have a look at the original menu-screen.txt file at: https://code.google.com/p/steigert-libgdx/source/browse/tags/post-20120301/tyrian-android/assets/layout-descriptors/menu-screen.txt

      Also, make sure you're using libgdx 0.9.2 for using the examples in this post. Notice that I added a "Libgdx version used on this post: x.y.z" at the beggining of all posts..

      Delete
  17. Thank you so much for this tutorial. However i am not able to move forward because i am getting errors in import: ui.ClickListener, ui.tablelayout.Table, ui.tablelayout.TableLayout. It says it cannot be resolved. Because of these import errors the whole file is littered with errors everywhere.

    ReplyDelete
    Replies
    1. Hi Pritam,

      It looks like they rewrote the Event system in libgdx recently to be more general and flexible (read: better), as Gustavo explains in his tutorial #13. Instead of ui.ClickListener, I think actor elements have their own addListener() method in which you can supply a handler method.

      Maybe Gustavo can correct me if I am wrong about this.

      Delete
    2. You're correct, Michael. The refactorings of libgdx created some confusion. That's why I put below the post's title the libgdx version I used to write it. Also, I think I will wait for libgdx 1.0 before moving on with the tutorial.

      Delete
  18. This comment has been removed by the author.

    ReplyDelete
  19. Hello,
    I don't know why but 19 out of 20 times, I giot the following error when I launch the game :
    Tyrian: Creating game on Desktop
    Tyrian: Resizing game to: 800 x 480
    Tyrian: Showing screen: SplashScreen
    Tyrian: Resizing screen: SplashScreen to: 800 x 480
    Tyrian: Setting screen: SplashScreen
    FPSLogger: fps: 0
    FPSLogger: fps: 64
    FPSLogger: fps: 63
    Tyrian: Hiding screen: SplashScreen
    Tyrian: Disposing screen: SplashScreen
    Tyrian: Showing screen: MenuScreen
    Tyrian: Resizing screen: MenuScreen to: 800 x 480
    Tyrian: Setting screen: MenuScreen
    #
    # A fatal error has been detected by the Java Runtime Environment:
    #
    # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000005f5510f0, pid=5436, tid=4772
    #
    # JRE version: 7.0_06-b24
    # Java VM: Java HotSpot(TM) 64-Bit Server VM (23.2-b09 mixed mode windows-amd64 compressed oops)
    # Problematic frame:
    # C [atio6axx.dll+0x2210f0]

    I'm using lbgdx 0.9.6 eclipse juno. I tried to integrate the files of your subversion but the error continues.
    Can you help me ?

    ReplyDelete
    Replies
    1. I found the problem and I fixed it by commenting out the line stage.dispose() in the AbstractScreen class. But I don't know why

      Delete
  20. I think I figured out the issue with disposing of the stage. If you look at the code for the Stage class, you'll see that the dispose() method only disposes of its SpriteBatch object, if it owns it. The clear() method does most of the clean up. I call the clear() method before dispose(), and I haven't seen the error, yet.

    ReplyDelete
    Replies
    1. It works for me too: libgdx 0.9.5

      Isn't it a libgdx bug?

      Delete
  21. Hi I'm having problems how to create or get hold of a skin file, could you point me in the right direction please

    ReplyDelete
    Replies
    1. It's in the android project under assets folder:
      https://code.google.com/p/steigert-libgdx/source/browse/#svn%2Ftags%2Fpost-20120301%2Ftyrian-android%2Fassets%253Fstate%253Dclosed

      Delete
  22. Hi Gustavo,

    I played around with the TWL demos and it looks really neat. But, it sounds like they say it isn't ready to run on android yet...

    "TWL is also supported on Android but to use it an Android compatible renderer needs to be written"

    Has this "renderer" been built since that was written? If not, do you plan on writing one?

    Thanks,
    Michael

    ReplyDelete
  23. Hi is there a link to data/uiskin.png I don't have it so it's causing an error.
    I checked
    Source path: svn/ trunk/ tyrian-android/ assets/ skin/ uiskin.png
    but only this came up
    This file is not plain text (only UTF-8 and Latin-1 text encodings are currently supported).

    ReplyDelete
    Replies
    1. Go to https://code.google.com/p/steigert-libgdx/source/browse/tags/post-20120301/tyrian-android/assets/uiskin.png and then click "View raw file" on the right. You can download the file that comes up.

      Delete
  24. TableLayout layout = table.getTableLayout();
    is not working for libgdx 0.9.8. I think they have made some changes in their API. can you suggest me the method like above for 0.9.8 version. I can create the UI without using above UI management trick but then my code would look clumsy and your other examples are also based on TableLayout editor code. So, please help in figuring out how to use TableLayout like thing in 0.9.8 version.

    Thanks

    ReplyDelete
  25. Can I download the source code and run it on EClipse... i am a newbie and find your tutorial the best among others... getting the project ...playing it and then begin to learn will be a great help.

    ReplyDelete
  26. I'm trying to implement this with the latest version of GDX.
    I have the screen with several buttons on it. Every button is added to a Stage and the stage is set as the input processor.
    So I have 2 questions:
    1. In order to implement the input methods, do I have to create my own class that extends Stage and override the touchUp event?
    2. How do I know which button was pressed? Do I have to manually check the x/y coordinates? Or is there a nicer way?

    ReplyDelete
  27. You have to set the screen's stage to listen for inputs like so: Gdx.input.setInputProcessor(stage);

    Then you must override BOTH the touchDown and touchUp events in order for them to register correctly:

    TextButton startGameButton = new TextButton("Start game", getSkin());

    startGameButton.addListener(new InputListener() {
    @Override
    public boolean touchDown(InputEvent event, float x, float y,
    int pointer, int button) {
    return true;
    }
    @Override
    public void touchUp(InputEvent event, float x, float y,
    int pointer, int button) {
    }
    });

    Make sure you also register the Table obj. as an actor of the stage, too!

    ReplyDelete
  28. For the ones who have problem with the skin files......

    replace all your skin files with here:
    https://github.com/libgdx/libgdx/tree/master/tests/gdx-tests-android/assets/data

    ReplyDelete
  29. Hey,

    I'm having a little trouble with this. It seems like the buttons are being rendered on screen without any trouble but the place the are responding to clicks is actually closer to the top of the screen. The X axis is fine buy the Y is off.

    It seems like the amount that the Y amount is off by increases when the button is further up the screen. I can't figure it out.

    Does anyone have any idea what the trouble might be?

    ReplyDelete