Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: lxgui - a Lua and XML Graphical User Interface  (Read 20630 times)

0 Members and 1 Guest are viewing this topic.

Kalith

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
lxgui - a Lua and XML Graphical User Interface
« on: May 06, 2012, 04:29:20 pm »
Hi,

After more than 5 years of spare time coding, I am finally ready to present my biggest project ever : lxgui.


Screenshot of the gui in action (sample provided in the package).

I am aware that there are plenty of different GUI libraries out there, but they all have something that makes them unique. This is also the case of lxgui. It's main advantages are :
  • platform independence : it is coded in standard C++ (using C++11 features). Platform dependent concepts, such as rendering or input, are handled by front-end plugins (for rendering : only pure OpenGL for now, for input : SFML and OIS).
  • fully extensible : except for the base GUI components (gui::frame), every widget is designed to be used as a plugin : gui::texture, gui::font_string, gui::button, gui::edit_box, ...
  • fully documented : every class in the library is documented. Doxygen documentation is included (and available online here).
  • GUI data from XML and Lua files : it can use a combination of XML files (for GUI structure) and Lua scripts (for event handling, etc) to construct a fully functional GUI. One can also create everything in C++ code if needed.
  • a familiar API... : the XML and Lua API are directly inspired from World of Warcraft's successful GUI system. It is not a plain copy, but most of the important features are there (virtual widgets, inheritance, ...).
  • caching : the whole GUI can be cached into screen size render targets, so that interfaces with lots of widgets render extremely fast (provided it is not animated, and mostly event driven : the sample screenshot renders at 1080 FPS with caching enabled, for example).
I have tried to make use of as few external libraries as possible, so compiling it is rather easy (projects files are included for Code::Blocks, Visual Studio 2010 and CMake). The GUI library in itself only depends on Lua 5.1 (not 5.2 !) through the "luapp" C++ wrapper that I wrote (included in the source package). XML parsing is done by a library of my own (also included in the package).
The only rendering front end available uses OpenGL. It depends on Freetype for font loading and rendering, and libpng for texture loading (hence, only PNG textures are supported).
For the input front end, you can use SFML2 or OIS. Though, since SFML doesn't provide an API for keyboard layout independent input, the edit_box widget will not function properly (among other things) (edit: fixed in 1.1.0!).

Here is a brief list of the available widgets (but nothing prevents you from creating more without touching the lxgui library !) :
  • uiobject (abstract) : the very base of every GUI widget. Can be placed on screen.
  • layered_region (abstract) : can be rendered on the screen.
  • frame : can contain layered_regions (sorted by layer) and other frames.
  • texture : can render a texture file, a gradient, or a plain color.
  • font_string : can render text.
  • button : a clickable frame with several states : normal, pushed, highlight
  • check_button : a button with a check box
  • slider : a frame that has a texture that can be moved vertically or horizontally
  • status_bar : a frame that uses a texture that grows depending on some value (typical use : health bars, ...)
  • edit_box : an editable text box (multiline edit_boxes are not yet fully supported)
  • scroll_frame : a frame that has scrollable content
Setting up the GUI in C++ is rather straight forward (code updated for 1.2.0) :
// Create an SFML window for example
sf::Window mWindow;

// Create an input handler (mouse, keyboard, ...)
utils::refptr<input::handler_impl> pSFMLHandler(new input::sfml_handler(mWindow));

// Initialize the gui
gui::manager mManager(
    // Provide an input handler
    pSFMLHandler,
    // The language that will be used by the interface
    // (purely informative: it's always up to each addon to localize
    // itself according to this value)
    "enGB",
    // Dimensions of the render window
    mWindow.getSize().x, mWindow.getSize().y,
    // The OpenGL implementation of the gui
    utils::refptr<gui::manager_impl>(new gui::gl::manager())
);

// Load files :
//  - first set the directory in which the interface is located
mManager.add_addon_directory("interface");
//  - create the lua::state
mManager.create_lua([&mManager](){
    // This code might be called again later on, for example when one
    // reloads the GUI (the lua state is destroyed and created again).
    //  - register the needed widgets
    mManager.register_region_type<gui::texture>();
    mManager.register_region_type<gui::font_string>();
    mManager.register_frame_type<gui::button>();
    mManager.register_frame_type<gui::slider>();
    mManager.register_frame_type<gui::edit_box>();
    mManager.register_frame_type<gui::scroll_frame>();
    mManager.register_frame_type<gui::status_bar>();
    //  - register additional lua "glue" functions if needed
    // ...
});

//  - and eventually load all files
mManager.read_files();

// Start the main loop
while (bRunning)
{
    // Retrieve the window events
    sf::Event mEvent;
    while (mWindow.pollEvent(mEvent))
    {
        // ...

        // Send these to the input manager
        pSFMLHandler->on_sfml_event(mEvent);
    }

    // Update the GUI
    mManager.update(fDeltaTime);

    // Render the GUI
    mManager.render_ui();
}

With these few lines of code, you can then create as many "interface addons" in XML and Lua as you wish. Let's consider a very simple example : we want to create an FPS counter at the bottom right corner of the screen.
First create a new addon, by going to the "interface" folder, and creating a new folder "FPSCounter". In this folder, we create a "table of content" file which lists all the .xml and .lua files this addons uses, and some other informations (addon author, GUI version, saved variables, ...). It has to be called "FPSCounter.toc" :
Code: [Select]
## Interface: 0001
## Title: A neat FPS counter
## Version: 1.0
## Author: Kalith
## SavedVariables:

addon.xml

As you can see, we will only require a single .xml file : "addon.xml". Let's create it in the same folder. Every XML file must contain a <Ui> tag :
<Ui>
</Ui>

Then, within this tag, we need to create a frame (which is more or less a GUI container) :
    <Frame name="FPSCounter">
        <Size>
            <RelDimension x="1.0" y="1.0"/>
        </Size>
        <Anchors>
            <Anchor point="CENTER"/>
        </Anchors>
    </Frame>
This creates a Frame named "FPSCounter" that fills the whole screen : the <Size> tag gives it relative size of "1.0" (relative to the parent frame, but since there is none, it is relative to the screen size), and the <Anchor> tag positions the frame in the middle of the screen.
Now, within the Frame, we create a FontString object, that can render text :
    <Frame name="FPSCounter">
        <Size>
            <RelDimension x="1.0" y="1.0"/>
        </Size>
        <Anchors>
            <Anchor point="CENTER"/>
        </Anchors>
        <Layers><Layer>
            <FontString name="$parentText" font="interface/fonts/main.ttf" text="" fontHeight="12" justifyH="RIGHT" justifyV="BOTTOM" outline="NORMAL">
                <Anchors>
                    <Anchor point="BOTTOMRIGHT">
                        <Offset>
                            <AbsDimension x="-5" y="-5"/>
                        </Offset>
                    </Anchor>
                </Anchors>
                <Color r="0" g="1" b="0"/>
            </FontString>
        </Layer></Layers>
    </Frame>
We've named our FontString "$parentText" : "$parent" gets replaced by it's parent name, so in the end it is called "FPSCounterText". Intuitively, the "font" attribute specifies which font file to use for rendering (can be a .ttf or .otf file), "fontHeight" the size of the font, "justifyH" and "justifyV" gives the horizontal and vertical justification, and "outline" creates a black border around the letters, so that it is readable regardless of the background content. We anchor it at the bottom right corner of its parent frame, with a small offset, and give it a green color.

Now that the GUI structure is in place, we still need to display the number of frame per second. To do so, we will define two "scripts" for the "FPSCounter" Frame :
        <Scripts>
            <OnLoad>
                -- This is Lua code !
                self.update_time = 0.5;
                self.timer = 1.0;
                self.frames = 0;
            </OnLoad>
            <OnUpdate>
                -- This is Lua code !
                self.timer = self.timer + arg1;
                self.frames = self.frames + 1;

                if (self.timer > self.update_time) then
                    local fps = self.frames/self.timer;
                    self.Text:set_text("FPS : "..fps);
               
                    self.timer = 0.0;
                    self.frames = 0;
                end
            </OnUpdate>
        </Scripts>

The "OnLoad" script gets executed only once, when the Frame is created. It is used here to initialize some variables. The "OnUpdate" script is called every frame (use it carefully...). It provides the time elapsed since last update in the "arg1" variable.  We use it to record the number of frames that are rendered, and update the FPS counter every half seconds.
The "self" variable in Lua is the equivalent of "this" in C++ : it is a reference to the "FPSCounter" Frame. Note that, since we've called the FontString "$parentText", we can use the handy shortcut "self.Text" instead of the full name "FPSCounterText" to reference the FontString object in Lua.

Once this is done, we have the full .xml file :
<Ui>
    <Frame name="FPSCounter">
        <Size>
            <RelDimension x="1.0" y="1.0"/>
        </Size>
        <Anchors>
            <Anchor point="CENTER"/>
        </Anchors>
        <Layers><Layer>
            <FontString name="$parentText" font="interface/fonts/main.ttf" text="" fontHeight="12" justifyH="RIGHT" justifyV="BOTTOM" outline="NORMAL">
                <Anchors>
                    <Anchor point="BOTTOMRIGHT">
                        <Offset>
                            <AbsDimension x="-5" y="-5"/>
                        </Offset>
                    </Anchor>
                </Anchors>
                <Color r="0" g="1" b="0"/>
            </FontString>
        </Layer></Layers>
        <Scripts>
            <OnLoad>
                -- This is Lua code !
                self.update_time = 0.5;
                self.timer = 1.0;
                self.frames = 0;
            </OnLoad>
            <OnUpdate>
                -- This is Lua code !
                self.timer = self.timer + arg1;
                self.frames = self.frames + 1;

                if (self.timer > self.update_time) then
                    local fps = self.frames/self.timer;
                    self.Text:set_text("FPS : "..math.floor(fps));
               
                    self.timer = 0.0;
                    self.frames = 0;
                end
            </OnUpdate>
        </Scripts>
    </Frame>
</Ui>

... and a working GUI addon !
One last thing to do before being able to see it in your program is to go to the "interface" folder, and create a file called "addons.txt". It will contain the list of addons that you want to load. In our case just write :
Code: [Select]
FPSCounter:1The "1" means "load". If you put a "0" or remove that line, your addon will not be loaded.

Doing the very same thing in C++ would give the following code :
// Create the Frame
gui::frame* pFrame = mManager.create_frame<gui::frame>("FPSCounter");
pFrame->set_rel_dimensins(1.0f, 1.0f);
pFrame->set_abs_point(gui::ANCHOR_CENTER, "", gui::ANCHOR_CENTER);

// Create the FontString
gui::font_string* pFont = pFrame->create_region<gui::font_string>(gui::LAYER_ARTWORK, "$parentText");
pFont->set_abs_point(gui::ANCHOR_BOTTOMRIGHT, "$parent", gui::ANCHOR_BOTTOMRIGHT, -5, -5);
pFont->set_font("interface/fonts/main.ttf", 12);
pFont->set_justify_v(gui::text::ALIGN_BOTTOM);
pFont->set_justify_h(gui::text::ALIGN_RIGHT);
pFont->set_outlined(true);
pFont->set_text_color(gui::color::GREEN);
pFont->notify_loaded();

// Create the scripts in C++ (one can also provide a string containing some Lua code)
float update_time = 0.5f, timer = 1.0f;
int frames = 0;
pFrame->define_script("OnUpdate",
    [&](gui::frame* self, gui::event* event) {
        float delta = event->get<float>(0);
        timer += delta;
        ++frames;

        if (timer > update_time)
        {
            gui::font_string* text = self->get_region<gui::font_string>("Text");
            text->set_text("FPS : "+utils::to_string(floor(frames/timer)));
           
            timer = 0.0f;
            frames = 0;
        }
    }
);

// Tell the Frame is has been fully loaded.
pFrame->notify_loaded();

As you can see from the screenshot above, this system can be used to create very complex GUIs (the "File selector" frame is actually a working file explorer !). This is mainly due to a powerful inheritance system : you can create a "template" frame (making it "virtual"), that contains many object, many properties, and then instantiate several other frames that will use this "template" ("inherit" from it). This reduces the necessary code, and can help you make consistent GUIs : for example, you can create a "ButtonTemplate", and use it for all the buttons of your GUI.

I think I will close here this presentation. There are no official tutorials and no API for the Lua functions, but you can rely on some World of Warcraft sites for the moment (such as WoWWiki). I hope that some of you will find this library useful !

Included in the source package (in the "gui/test" directory) is a test program that should compile and work fine if you have installed the whole thing properly. It is supposed to render exactly as the sample screenshot above. It can also serve as a demo program, and you can see for yourself what the XML and Lua code looks like for larger scale GUIs.

Download :
The project has its sourceforge page : here.
You will find direct download of the source in either .zip or .7z format, or an SVN repository.

Note : lxgui and all the provided libraries are released under the GNU LGPL 3 license (you can use the libraries in your project without having to make the source code public, but if you ever modify these, you have to show the changes you made. See "gnu.txt" for more information).
« Last Edit: January 25, 2013, 09:25:35 pm by Kalith »
Kal.

Kalith

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #1 on: January 02, 2013, 12:47:12 am »
A quick post to announce the 1.1.0 version of lxgui.

This new version is mostly composed of bug fixes and a few enhancements (on code quality and/or performance), but sadly its API is slightly different than the one of the 1.0.0 version. In other words: one will have to do small adjustments to ones code in order to use this new version. In particular, the gui::locale class (which used to take care of localizing the keyboard keys for different layouts, such as AZERTY or QWERTY) is gone, its work now being delegated to the SFML. This little change induces a modification of the way one initializes the GUI, so here is the updated sample code for 1.1.0 :
// Create an SFML window
sf::Window mWindow(...);

// Create an input handler (mouse, keyboard, ...)
utils::refptr<input::handler_impl> pSFMLHandler(new input::sfml_handler(mWindow));

// Initialize the gui
gui::manager mManager(
    // Provide an input handler
    pSFMLHandler,
    // The language that will be used by the interface (purely informative: it's always up to each addon to localize itself according to this value)
    "enGB",
    // Dimensions of the render window
    mWindow.getSize().x, mWindow.getSize().y,
    // The OpenGL implementation of the gui
    utils::refptr<gui::manager_impl>(new gui::gl::manager())
);

// Load files :
//  - first set the directory in which the interface is located
mManager.add_addon_directory("interface");
//  - create the lua::state
mManager.create_lua([&mManager](){
    // This code might be called again later on, for example when one
    // reloads the GUI (the lua state is destroyed and created again).
    //  - register the needed widgets
    mManager.register_region_type<gui::texture>("Texture");
    mManager.register_region_type<gui::font_string>("FontString");
    mManager.register_frame_type<gui::button>("Button");
    mManager.register_frame_type<gui::slider>("Slider");
    mManager.register_frame_type<gui::edit_box>("EditBox");
    mManager.register_frame_type<gui::scroll_frame>("ScrollFrame");
    mManager.register_frame_type<gui::status_bar>("StatusBar");
    //  - register additional lua glues functions if needed
    // ...
});

//  - and eventually load all files
mManager.read_files();

// Start the main loop
while (bRunning)
{
    // Retrieve the window events
    sf::Event mEvent;
    while (mWindow.pollEvent(mEvent))
    {
        // ...

        // Send these to the input manager
        pSFMLHandler->on_sfml_event(mEvent);
    }

    // ...

    // Update the GUI
    mManager.update(fDeltaTime);

    // ...

    // Render the GUI
    mManager.render_ui();

    // ...
}

Apart from this change, everything works exactly the same way.
Among the few novelties, the following are probably the most interesting:
  • typing text in an edit_box is now fully functional when using the SFML as input source
  • added CMake scripts for the compilation (makes it much easier to install on linux)
  • updated the SFML input handler to fit the latest snapshot of the 2.0 RC
  • added an input handler for GLFW (a windowing library for OpenGL)
  • it is now possible to use std::functions (or functors or lambdas) to define "scripts".
A simplistic example that blindly mimics the Lua scripts of my first post:
float update_time = 0.5f, timer = 1.0f;
int frames = 0;

pFrame->define_script("OnUpdate",
    [&](gui::frame* self, gui::event* event) {
        float delta = event->get(0)->get<float>();
        timer += delta;
        ++frames;

        if (timer > update_time) {
            gui::layered_region* reg = self->get_region("FPSCounterText");
            gui::font_string* text = dynamic_cast<gui::font_string*>(reg);

            text->set_text("FPS : "+utils::to_string(floor(frames/timer)));
           
            timer = 0.0f;
            frames = 0;
        }
    }
);
It's a little bit less "user-friendly" than Lua code, and also less flexible since one has to re-compile the program to modify the script, but it will always be faster and more robust (for example, it is not possible to call a method that doesn't exist on an object). The other advantage of using std::functions is that one can really use anything as a script (as long as it has the proper signature). For example, if one wants the "OnUpdate" event to call my_on_update_function(), then all one has to do is to call:
pFrame->define_script("OnUpdate", &my_on_update_function);

For more detailed information, please refer to the changelog.
« Last Edit: January 05, 2013, 05:28:41 pm by Kalith »
Kal.

FRex

  • Hero Member
  • *****
  • Posts: 1848
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #2 on: January 02, 2013, 01:29:56 am »
I myself am a happy SFG user but I have to say: this looks amazing. :o
Back to C++ gamedev with SFML in May 2023

netrick

  • Full Member
  • ***
  • Posts: 174
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #3 on: January 02, 2013, 03:36:07 pm »
Though, since SFML doesn't provide an API for keyboard layout independent input, the edit_box widget will not function properly (among other things).

But why? SFML has text entered event which works for great for polish and french layout and I think every unicode character, so what is the problem?

Kalith

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #4 on: January 02, 2013, 03:45:08 pm »
If you had read my previous post you would have seen that this is no longer an issue, and that I am now using the SFML event! ;) (still, I edited my post to make it clearer)
« Last Edit: January 03, 2013, 08:31:42 pm by Kalith »
Kal.

eigenbom

  • Full Member
  • ***
  • Posts: 228
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #5 on: January 03, 2013, 03:02:50 am »
this looks very special. i might check it out! :)

Kalith

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #6 on: January 05, 2013, 02:58:44 pm »
I've added a binary (Win32 only) version of the test application provided with the library (what was used to generate the screenshot of the first post). You can download it [here], hopefully no DLL is missing.
Kal.

tannen

  • Newbie
  • *
  • Posts: 2
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #7 on: January 05, 2013, 09:08:40 pm »
It looks really good, but i always get errors when i try to build it (1.1.0).
The first error is in file "utils_refptr.hpp", if i remove the explicit at some template iirc this file works, but then there will be other errors in other files.
I'm using MSVC 10 and 12.

Kalith

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #8 on: January 06, 2013, 02:25:49 pm »
I have never tried to compile it with MSVC to be honest. The errors you're getting are due to the poor C++11 support of the Microsoft compiler (explicit conversion operators in this particular case). I will add a clean workaround for it to compile nonetheless.

I'll see if there are other features that are not supported by MCVS (I can only test the 2010 version since I'm using Windows XP) and keep you updated whenever (/if) I manage to compile everything.
Kal.

Kalith

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #9 on: January 07, 2013, 01:47:35 am »
Took me the whole day, but the library now compiles fine and warning free with MSVC 2010 (/W4, useless warnings disabled), and the test program renders exactly as with GCC.

I had to remove some of the C++11 code I was using when it didn't really matter (range based for loops and initializer lists), and use conditional compilation for the other cases (define NO_CPP11_EXPLICIT_CONV, NO_CPP11_DELETE_FUNCTION, NO_CPP11_FUNCTION_TEMPLATE_DEFAULT, NO_CPP11_CONSTEXPR and NO_CPP11_UNICODE_LITTERAL). Thanks to the different implementation of Microsoft's compiler, I also fixed some undefined behaviors that I couldn't notice because they were harmless in GCC (like calling str[0] on an empty std::string, or some uninitialized variables).

I have updated the CMake scripts to be able to compile with MSVC, even though I couldn't run them because they fail at finding the dependencies (I'm still learning CMake...). In any case, I also added project files for Visual C++ (.sln and .vcxproj) and updated the Code::Blocks projects to supply MSVC targets. I'm not very familiar with MSVC, so maybe the configuration can be improved. Please tell me if you find anything wrong.

I guess this is a good time to switch to version 1.2.0 (as always, see sourceforge.net for the files).

Apart from this, I have made the usage of the library in C++ easier by providing more shortcut functions, and potentially improved the performances. It is now also possible to specify colors using the HTML syntax (i.e. "#RRGGBBAA") in XML and Lua code. Finally, one can also select the filtering to apply to the textures that are displayed by the GUI (no filter, of linear filter). Again, see the changelog for the whole details.
Kal.

tannen

  • Newbie
  • *
  • Posts: 2
    • View Profile
Re: lxgui - a Lua and XML Graphical User Interface
« Reply #10 on: January 08, 2013, 01:22:14 pm »
Thanks for your work, got it running on my end.