A GUI library for games that's so small you won't even know its there.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
lel-guecs/README.md

14 KiB

LEL & GUECS

This project is two small components that make up a minimalist GUI library for game development. The purpose is to provide only a simple layout engine and an ECS (Entity Component System) structure for you put your existing graphical elements into. It's currently working for SFML but should be easy to retarget or recreate. You can also use only LEL or GUECS depending on your needs.

LEL stands for Layout Expression Language and is a layout engine that uses a simple "wiki style" language for specifying a GUI's layout grid. Rather than use nested containers or similar tree-like code structures, a LEL layout is just a string that looks like this:

[col1_row1|col2_row1]
[col1_row2|col2_row2|cheese_doodles]

The LEL parser will read this, and based on the dimensions of its space, determine the size of each cell here. In this case it will create 4 cells, dividing the space into 4 quadrants. You can then access these cells by their names "col1_row1" and place your own GUI elements there. The LEL language can create ragged rows, spans, and most anything you need for a layout (to a point).

You'll also notice that you can name these cells almost anything. The last row has cheese_doodles rather than a column/row identifier.

GUECS (Graphical User Entity Component System) is a very simple ECS that lets you quickly build your GUI inside a LEL layout. It works like most ECS systems whereby there are no classes like Button or Input but instead you use components to create these. For example, a button is simply:

gui.set<guecs::Rectangle>(id, {});
gui.set<guecs::Label>(id, {L"Click Me"});
gui.set<guecs::Clickable>(id, {
  [](auto, auto){ handle_click(); }
});

This creates a rectangle with a label that when clicked call the handle_click() function. This makes it very easy for you to target your own graphics libraries since you only need to write your own components and toss them into the guecs::UI class like this.

What is it NOT?

LEL does not try to create deeply nested complex layouts. It can create reasonably complex two dimensional layouts, but if you need very complex nested layouts then its best to create multiple components with their own LEL expressions.

LEL also doesn't try to do automatic rebalancing and recalculating of its layout. Since every game framework (and every game?) starts off with fixed size screens it doesn't make sense to create a layout engine that can handle the equivalent of a web browser HTML/CSS engine. If you change the dimensions of your screen, then simply re-initialize the LEL layouts. You most likely have to do this anyway in your game engine.

That being said, LEL's engine is reasonably fast so recalculating the layout won't be expensive. Just don't expect it to rebalance some douchebag swinging a window corner resize around at 200 FPS.

GUECS also doesn't include many ready-made components. It has basic building blocks for creating your own components, but it's assumed that you're probably interested in creating your own stylized UI components to match your game's design and your game engine's functionality. Many times game developers end up creating all of their own UI elements so just do that but let GUECS help you keep it all organized.

Building

First, you'll need to install meson to run the build. One MASSIVE warning is that meson will run each dependency's build, which will require you to have dependencies installed in some OS (like Linux), but then my build will completely ignore your broke ass hacked up bullshit packages. I'm serious, nothing on your computer is trusted and I download everything. If you build against your versions of the packages then you're doing it wrong (I'm looking at you Fedora and Debian).

Easiest way to try the build is with this:

git clone https://git.learnjsthehardway.com/learn-code-the-hard-way/lel-guecs.git
cd lel-guecs
make reset
make run

That should kick off the build attempt, and then you'll be told what's missing for the build to continue, BUT this is platform dependent as I said before. For example, on Windows it just builds by downloading everything, OSX already has most things, and Linux is...well...Linux.

Using LEL

To use LEL with GUECS you first initialize a guecs::UI class with its position and size. Here's an example taken from the demos/calc.cpp example:

$gui.position(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

Then you configure a layout with the LEL formatting language:

$gui.layout(
    "[*%(400)stack  |_|_|_]"
    "[*%(400)readout|_|_|_]"
    "[push|pop|clear|eq]"
    "[add |sub|mul  |div]"
    "[btn7|btn8|btn9]"
    "[btn4|btn5|btn6]"
    "[btn1|btn2|btn3]"
    "[neg |btn0|_   ]");

This creates a simple RPN calculator UI with buttons for numbers, readouts for the results and stack, and basic math operators. For people from other languages, this is actually one big string, but C++ (like C) allows you to "concatenate" strings together that are next to each other, so I just put them on separate lines so they look more like the grid they represent.

Once you have that you can give your panel a background and render a debug layout:

  void render(sf::RenderWindow& window) {
    $gui.render(window);
    $gui.debug_layout(window);
  }

Since this first version works with SFML this render only takes a sf::RenderWindow and renders the gui to it. With these two calls you'll get red lines showing you the grid specified in layout().

This lets you refine the layout grid without requiring any components. Keep working the LEL layout until the grid looks good, then add some rectangles and labels:

for(auto& [name, cell] : $gui.cells()) {
  auto id = $gui.entity(name);
  $gui.set<Rectangle>(id, {});
  $gui.set<Label>(id, { guecs::to_wstring(name) });
}

$gui.init(); // must be called when done

You'll notice I have guecs::to_wstring(name) which uses the forbidden unicode conversion codecvt from C++. Don't tell Microsoft. They'll be really angry and that one manager that's trying to get Unicode removed from C++ won't get the bonus he needs to buy that houseboat.

With that working you can now use the components in sfml/components.hpp to add shader Effect, Clickable interactions, Sound, and other things. You can also easily write your own. Look in sfml/components.cpp to see how I do it. They're very simple.

The LEL language has quite a few features in a small package, so study the grammar below and try working on a layout in different ways to learn it.

LEL Full Grammar

[
startrow -- Starts a new row.
]
endrow -- Ends the row.
|
column -- Separates (starts a new) columns.
^ or .
valign -- Simple vertical alignment. ^ aligns to the top and . aligns to the bottom. This is only useful if you use () to change the size so the cell is smaller than it normally is. Then use this to move it up/down.
< or >
halign -- Simple horizontal alignment. Again, only useful if you change the size so that it's less than its default size.
*
expand -- This causes a cell to "break" out of its default cell and expand into neighbors. This is mostly used with _ (empty) cells next to it for larger components, but you can also use this to create layered cells for special effects.
=
center -- Centers the cell inside its default cell. Again this is only useful if you use () to make it smaller than its default.
%
percent -- Normally the numbers inside () are actual pixel sizes, but adding this before will turn them into percentages as whole numbers. This means "10 percent" is given as 10 and "150 percent" is given as 150. DO NOT write 0.1 or 10%. For example, if you want a cell to be 2 cells wide and 4 cells tall then write *%(200, 400). You need * (expand) when expanding it.
digit+
numbers -- Anywhere in the grammar when you can type a number, these are them. They're numbers. Why am I explaining numbers?
(number (, number)?)
setw -- Sets the width (or height) of the cell. The (, number)? means that the second term is optional, and if it's missing it's assumed you want to only specify the width. So (200) is 200 pixels wide while (200, 340) is 200 wide and 340 tall. Combine with * (expand) and % (percent) to make the cell expand into other cells (which can be empty using _).
(percent | center | expand | valign | halign | setw)
modifiers -- In the grammar modifiers are all of these things before the cell ID (name). So if I want a cell named launch_rocket that is 250% wider, 130% taller, I would write *%(250, 130)launch_rocket. For this to make sense you'd need to set the neighbor cells to _ so they're empty like this: [*%(250, 130)launch_rocket|_][_|_].
((alpha | '_')+ (alnum | '_')*)
id -- This is the cell id format, and it's basically what you get with most programming language: It can start with any alpha character or _, and contain any alphanumeric character or _. You can also just have _ on its own which will mean "empty" and be left as empty space.
modifiers* id
cell -- A cell is any of the above modifiers (or none) followed by a cell id.
[ cell (| cell )* ]
row -- A row starts with [, contains any number of cells separated by | (column) and terminated with a ].

LEL Examples

Coming Soon...

LEL Tricks

  1. You can use _ to create an empty cell, then use *%(200) to expand the previous cell into it.
  2. You can also do this to rows below a cell to make them expand down. Simply set the cells below to _ and use *%(X,Y) to expand by X=width and Y=height percentage.
  3. You can use the names of cells to set an initial character as the event trigger. Just make each cell start (or end) with a unique relevant character, then grab it to create a number for that event.

Using GUECS

WARNING: LEL can function on its own but GUECS is very is currently only working with SFML. I also need to refine how the guecs::Clickable component works as its more complex than it needs to be. This will probably change in the future.

The GUECS system is mostly defined in these four files:

guecs.hpp
This defines the base guecs::UI class which is a simple mostly template class that handles everything. Most of the template functions are used to perform the ECS style operations like set<TYPE>(id, VALUE) to set a specific component TYPE to a VALUE at the given cell id from LEL.
guecs.cpp
Implementation for guecs::UI that aren't template based. Main functions here are guecs::UI::init() and guecs::UI::render() which do the majority of the heavy lifting. If you're looking to retarget GUECS to your game engine or graphics system then this is where to start.
sfml/components.hpp
This is where the SFML based components live. This is where you find Rectangle, Textual, Label, and Clickable among other things. If you're looking to retarget, then this is what you have to write for your system.
sfml/components.cpp
The implementations for the components, which is mostly just init, update, and render methods.

GUECS Design Theme

The overall design theme for the guecs::UI is this:

  1. Since there's not too many components it's easier to simply init() them in the correct order in one method. You usually only call init() once when that UI starts up, and then change or re-initialize individual components as needed during operation.
  2. The render() method is the same, choosing to render each component in the correct order manually rather than trying to troll through everything and figure it out on the fly. This works well since usually you want to render Background, then Rectangle, then Label in order. If you find that's not the right ordering then this is where you go to fix it.
  3. Adding a new component involves creating the class/struct in sfml/components.hpp and then adding it to UI::init() and UI::render().
  4. Mouse interactions are handled by UI::mouse() and mostly just figure out what cell was clicked on then run any Clickable configured for that cell. It will also return true if something hit so you can do more processing after calling it.
  5. If you want to have immediate mode functionality then you'd mostly just make a component that has that and add it to init() and render().

Making Your Own

I believe that these two systems are simple enough that anyone can recreate them in their preferred language for their preferred system. I'll provide a guide here that explains how to do this, and encourage you to create your own rather than use mine. The key things to realize are:

  1. It's easier to describe an irregular 2D grid than it is to mangle a tree of objects withing objects within trees within objects.
  2. It's easier to process a 2D grid, and easier to target its elements by name rather than trolling through a tree of objects within trees within objects.
  3. It's easier to construct the controls you need through an ECS style set of primitives than it is to use an existing GUI component...but really only in video game development. In a desktop app it's probably better to use that OS's stock components.
  4. Something like LEL or GEUCS is fairly easy to understand and implement and does not require insane knowledge of dark corners of C++. Maybe just like...slightly dim corners.

Give it a shot and soon I'll have a guide on how to do it.

Fun Features

  1. Look at guecs::Sound (in sfml/components.hpp) to see how you can add sound feedback to your UI.
  2. Look at guecs::Effect (in sfml/components.hpp) to see how to apply shaders to your UI. Keep in mind that I don't know a damn thing about shaders.

RPN Calculator Demo

For a mostly complete example look at demos/calc.cpp for a simple RPN calculator. Also check the following projects where I'm using it:

Contact Me

You can email me at help@learncodethehardway.com and I also stream my development of this (and other fun stuff) two times a day at 10AM and 10PM EST (Miami/NYC) time. Feel free to stop by and talk to me about it and have me fix things you find. It's even better if you shoot me a bug report by email then come by and ask me about the email. That way you can send me copy-paste error outputs or possible patches (if you have them).