lgui: a new gui for Allegro 5
frank.n

So I've written a GUI using Allegro 5 (C++ 11/14).
Aiming for a lightweight approach at first, it has grown a bit over time.

Features

  • easy to use signals & slots to connect widgets (just register something callable)

  • two pass layout system, Android style; separate widgets and layouts, nestable layouts

  • drawing done in separate style classes

  • internal drag and drop

  • modal widgets, GUI stacks

  • event filters

  • 9-patch images

Widgets implemented

  • Widget, Frame

  • BasicContainer, PaddedContainer, Container, WrapWidget

  • TextField, TextBox, Spinner

  • Slider

  • DropDown

  • ScrollArea, ScrollBar

  • PushButton, CheckBox, RadioButton, ImageButton

  • TextLabel, WordWrapTextLabel, ImageLabel

  • ListBox, StringListView

  • TabWidget, Tab, TabBar

Layouts implemented

  • HBoxLayout, VBoxLayout

  • FlowLayout, SortedFlowLayout

  • RelativeLayout

  • SimpleTableLayout

Test program
There is also a test program demonstrating most of the widgets - if you give it a try, do not forget to resize the window 8-)
I've managed to get the test program to run on Android, but there is no special mobile code (yet).

Appearance
There is currently a dark-themed and a bright-themed default style drawn with Allegro 5 primitives.
A skinned style has been in the works, but will not be finished any time soon (I'm not any good at making good looking skins).

Screenshots:
{"name":"611662","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/0\/3\/030cbee0edeee88d24cac49314c4e043.png","w":1024,"h":575,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/0\/3\/030cbee0edeee88d24cac49314c4e043"}611662
{"name":"611661","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/9\/2\/9290352b727cbe011a847ffe47d4c150.png","w":1024,"h":576,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/9\/2\/9290352b727cbe011a847ffe47d4c150"}611661

Documentation
Doxygen generated documentation is available including a rather verbose main page explaining some of the intricacies. The documentation is not formally complete, but I hope most of the less-obvious stuff is documented.

Get and build
https://github.com/frank256/lgui

Since this is developed for another project, there currently is no proper library distribution, I just import the code into my project, sharing some of the platform stuff. The API is still subject to change.

Build with CMake. I'm not much of an expert with that, but I've successfully built it with g++ and/or clang on Linux, macOS, and Windows.

Code sample
This is what the code looks like:

#SelectExpand
1// Have a TextLabel display what is typed into a TextField. 2// Also use a layout to arrange them vertically into a container. 3class MyWidget : public lgui::Container { 4 private: 5 lgui::TextField input; 6 lgui::TextLabel output; 7 lgui::VBoxLayout layout; 8 9 public: 10 MyWidget() { 11 input.on_text_changed.connect(&TextField::set_text, output); 12 // or: input.on_text_changed.connect([this](const std::string& str) { output.set_text(str); }); 13 layout.add_item(input); 14 layout.add_item(output); 15 set_layout(&layout); 16 } 17}; 18// (there is no need to derive from lgui::Container here, just for organisation) 19 20/////////////////////////////////////////////////////////////////// 21 22// Allegro 5 main-loop snippet 23 24// .. set default style.. 25 26lgui::GUI gui; 27lgui::Graphics gfx; 28 29MyWidget my_widget; 30 31// The widget at the top needs a size: 32my_widget.set_size(my_widget.measure_max(0.5*display_width, 0.5*display_height)); 33 34gui.push_top_widget(my_widget); 35 36lgui::ExternalEvent event; 37ALLEGRO_EVENT a5_event; 38 39while(!quit) { 40 al_wait_for_event(event_queue, &a5_event); 41 42 switch(a5_event.type) { 43 // ... 44 default: 45 if (lgui::convert_a5_event(a5_event, event)) 46 gui.push_external_event(event); 47 break; 48 } 49 50 if(redraw && al_is_event_queue_empty(event_queue)) { 51 gui.draw_widgets(gfx); 52 al_flip_display(); 53 redraw = false; 54 } 55} 56gui.pop_top_widget();

Todo

  • proper touch support

  • API stabilization / bugfixes

  • build as a library

  • skinned style

  • undo for text input widgets

  • more widgets (especially mobile-specific)

  • animations

Comments, questions, feedback are appreciated.
Thank you for your attention. :)

Edgar Reynaldo

Very interesting. I develop a GUI of my own, Eagle.

The difference between our widget headers is quite sharp.

https://github.com/frank256/lgui/blob/master/src/lgui/widget.h

https://github.com/EdgarReynaldo/EagleGUI/blob/master/include/Eagle/Gui/WidgetBase.hpp

And my current redesign of the same header :

https://github.com/EdgarReynaldo/EagleGUI/blob/core/include/Eagle/Gui2/WidgetBase2.hpp

I'm going for a much more minimalist design in my latest refactor.

frank.n

Ah, interesting. The first thing I see in your header are the sub-widgets / children. That's exactly one of the things I have kept out of the base class. :)

The widget class has been very small at the beginning, but grown out of convenience. The number of methods far exceeds the number of data members, though, which I've been rather reluctant to add to. The methods could use better grouping; I've just moved things around a bit after breakfast. ;)

I've seen your other thread about GUI core design, but cannot reply to it. lgui's documentation main page contains some stuff about lgui's design if you are interested. The docs are an extra target: make doc.

Edgar Reynaldo
frank256 said:

The widget class has been very small at the beginning, but grown out of convenience. The number of methods far exceeds the number of data members, though, which I've been rather reluctant to add to. The methods could use better grouping; I've just moved things around a bit after breakfast. ;)

That's just what I've been trying to avoid with my latest redesign. Method proliferation prevention. ;) That's why I'm delegating duties to sub objects, and I have callbacks you can overload when changing or upon change to the object.

We can discuss Eagle here, but I don't want to distract from your GUI, so a new separate thread might be called for if you have something you want to discuss. Threads lock after a week of inactivity here, two if you're lucky and you pick the right board.

I'll clone and try to build your gui here soon.

EDIT
I tried building your project, but it doesn't know where to find allegro. I don't install libraries in my compiler directory, so it will never work. I tried cmaking a code blocks project, but it was unconfigurable.

I had to make a new project from scratch and add everything to it, but I got it working.

It's a good little demo, you've got quite a lot done, congrats.

GullRaDriel

I'm SO tired of these 'I made a new C++ GUI'.

While you stick to C++ you forbid the vast majority of C users to use your library in their code.

On the opposite, making it in C would allow everyone to use it.

/EndOfPersonnalRant

Whatever, congrats on making something on your own.

Edgar Reynaldo

I'm SO tired of these 'I made a new C++ GUI'.

While you stick to C++ you forbid the vast majority of C users to use your library in their code.

I'm SO tired of these "Why didn't you make your GUI in C so I can use it?" answers. If you want a C interface, there's nothing stopping you from doing it.

#SelectExpand
1class Foo { 2public : 3 void DoBarf() {printf("BARF\n");} 4 virtual void DoBazzle(int baz) {printf("%d" , baz*baz);} 5}; 6 7class FooFighter : public Foo { 8public : 9 virtual void DoBazzle(int baz) {printf("%d" , baz*baz*baz);} 10}; 11 12extern "C" { 13 14void foo_do_barf(Foo* foo) { 15 foo->DoBarf(); 16} 17void foo_do_bazzle0(Foo* foo , int baz) { 18 foo->DoBazzle(baz); 19} 20};

Gotcha there Gully. ;)

GullRaDriel

This will not compile with gcc only. I said C, not a silly C++ to C wrapper :-^p

EDIT: Hello here BTW ^^ ;D

jmasterx

Neat :)
Now I feel old having now made my mandatory C++ gui api nearly 8 years ago :o

Edgar Reynaldo

Let's see, what's the tally now?

TGUI - Trent Gamblin
ALGUI - axilmar
AGUI - jmasterx
LGUI - frank.n
EAGLE - Edgar Reynaldo
WidgetZ - SiegeLord

Who else have I forgotten?

The obligatory "why haven't we all been working on the same GUI for the last 10 years?" question is open.

DanielH
Slimy said:

The obligatory "why haven't we all been working on the same GUI for the last 10 years?" question is open.

I started mine for a few reasons. The main one was learning experience. The one I built is xml based. It was educational to build the parser, translator, etc.

The second was because I didn't like the syntax the other ones were using.

Edgar Reynaldo

I was (s)trolling through Online Users and I stumbled across this old gui thread of yours, which answers my question from above.

https://www.allegro.cc/forums/thread/613874

Sorry for derailing your thread with gui talk. xD ;D

frank.n

I did not want to roll my own at first, but after surveying the GUI-scape, I thought there simply were none that had all the features I needed, mainly:

Loose coupling between widgets
I did not want things like having to derive from a button listener just to be able to react to a button click. I find that quite intrusive. lgui uses a very simple signals & slots implementation, you typically just do something like my_button.on_clicked.connect([]() { printf("Button clicked!"); });

Arguments are forwarded; when using
my_text_field.on_text_changed.connect(&MyLabel::set_text, my_label);
for example, the on_text_changed signal emits a const std::string& which is automatically passed to my_label.set_text(const std::string&);
Signals look like that:
Signal<const std::string& > on_text_changed;
You can "connect" anything callable returning void.

Drag and drop
I absolutely needed that one. In lgui, you can spawn drags by overriding the mouse_dragged event handler which allows you to return a new drag representation which is something you can draw yourself. When moved around over widgets, it will generate drag_enter, drag_leave, dropped events; each widget can decide whether it wants to accept the drag, etc. It's currently limited to lgui itself, i.e. there is no interaction with the OS or windowing system (I just don't need that).

Layouts
This took me quite a long time, really. I've studied almost every GUI layout system I could find. My conclusion was that you had to have a 2-pass system to come close to getting it right: one measuring pass, one layout pass so that laying out one widget cannot lead to changes in the other again affecting the one you've just laid out. The Android style layout system struck me as the simplest: constraints that have to be respected are passed down in the measuring pass, measurements are passed up, then laying out takes place in a second pass after all widgets down to the leaves of the hierarchy already had their chance to contribute in the measuring pass. I further took the approach of deferring the layout, as it's quite expensive. The interaction between children with layout and parents without was quite a challenge, as was getting the damn ScrollArea to behave correctly under all (most) circumstances.
There are some convenient things, for example, you can implicitly set a margin for every widget when adding it to a layout. Some examples of using RelativeLayout:

layout.add_item_lt(label2, 0.5, 0.0); // left: 50%, top: 0%
layout.add_item(drop_down2, { {lgui::RelativeLayout::Constraint::AlignLeft, label2 },
                              {lgui::RelativeLayout::Constraint::Below, label2}});

// To fix it to 40% to 60% of the parent's width and 30% to 70% of the parent's
// height with a margin of 10 px on all sides:
layout.add_item_ltrb({ label2, { 10 } }, 0.4, 0.3, 0.6, 0.7);

// The same with a margin of top, left, right, bottom: 10, 5, 20, 30:
layout.add_item_ltrb({ label2, { 10, 5, 20, 30 } }, 0.4, 0.3, 0.6, 0.7);

As you can see, RelativeLayout is also a kind of PercentLayout. Maybe that could go into its own layout class, too, without all the complex, really "relative" stuff.

Memory management
To me, the very point of using C/C++ is to manage memory myself. I could use a unique_ptr in one place where ownership is transferred, but otherwise, lgui assumes you know how to manage memory and that you want to do it yourself. Wrapping everything in a shared_ptr is IMHO pure evil in C/C++ (use a garbage collected language if you want to do that :P).

Separate style classes
I did not want to make new subclasses just for changing the appearance, so I introduced separate style classes. These have become rather large and unwieldy and are among the things that would be in for a refactor. I didn't enjoy fiddling around with all the drawing parameters too much.

Coding style
I simply like _underscores_ better :)
These seem to be scarce in the GUI world.

I've also learned a lot in the process.

I tried building your project, but it doesn't know where to find allegro. I don't install libraries in my compiler directory, so it will never work. I tried cmaking a code blocks project, but it was unconfigurable.

I had to make a new project from scratch and add everything to it, but I got it working.

It's a good little demo, you've got quite a lot done, congrats.

Thank you! :)
I suppose there is an easy way to pass a library dir to cmake.

Edgar Reynaldo
frank.n said:

Loose coupling between widgets
I did not want things like having to derive from a button listener just to be able to react to a button click. I find that quite intrusive. lgui uses a very simple signals & slots implementation, you typically just do something like my_button.on_clicked.connect([]() { printf("Button clicked!"); });

Java did it all wrong. A thousand different listener classes is just dumb. In my GUI, widgets emit events, as a WidgetMsg to a parent, as an EagleEventSource to an EagleEventListener, or as a derived class callback. I always felt signals and slots was intrusive and just plain bad design. So if a widget has children, its parents get the message and react accordingly, like if a scroll bar gets a message from one of its scroll button children, or from the scroller child. WidgetMessages also get queued to the top level gui, for later retrieval and processing. A generic event is also emitted, which any listener class (such as the EagleEventHandler, which is an event queue), can deal with. Beyond that, if you need to override something that happens to a widget such as an specific event like when text is entered or a mouse gets clicked, then you override the WidgetBase::PrivateHandleEvent function. You can intercept any and all events that the widget receives and totally change its behavior.

frank.n said:

Drag and drop
I absolutely needed that one. In lgui, you can spawn drags by overriding the mouse_dragged event handler which allows you to return a new drag representation which is something you can draw yourself. When moved around over widgets, it will generate drag_enter, drag_leave, dropped events; each widget can decide whether it wants to accept the drag, etc. It's currently limited to lgui itself, i.e. there is no interaction with the OS or windowing system (I just don't need that).

I never saw the need for drag and drop. And besides, in my GUI it could be easily implemented by making a top most widget that is invisible that monitors mouse clicks and the widgets underneath the mouse at the time, along with any dragging events and drop events. But none of my widgets support 'dropping' anything onto them, but they could do it easily with a virtual HandleDrop(WidgetBase* widget_dropped) function.

frank.n.stein said:

Layouts
This took me quite a long time, really. I've studied almost every GUI layout system I could find. My conclusion was that you had to have a 2-pass system to come close to getting it right: one measuring pass, one layout pass so that laying out one widget cannot lead to changes in the other again affecting the one you've just laid out.

I don't know about that. I find that my layouts are pretty simple. Whenever you want to change the area of a widget, you call its SetWidgetArea function, which checks with its layout owner to see if it is an acceptable space to place the widget and returns the layout specified position for the widget. Any time the widget area is changed, a virtual member callback is executed, allowing you to reform your contents based on the new area. Widgets don't get to decide their actual position and dimensions, it's all handled by the layout. I suppose though, that a FlowLayout (which I haven't implemented yet) would need something like a preferred size to function properly. All my other layouts work great though. I have a dumb layout, a pin layout, a relative layout, a grid layout (which also works for the menu layout), splitter layouts, and I've experimented with animated layouts as well, which is really easy with my animation class. No more 3 column CSS Hell, where you can't get a damn table layout to work right ever. Table layout will be implemented, with variable size rows and columns, along with empty cells and spanning cells.

frank.n.beans said:

lgui::RelativeLayout::Constraint::AlignLeft

Really? You can't pick a shorter name than a 4 namespace deep constant? I just use enums, and they work fine. ;)

frank.n.limas said:

Memory management
To me, the very point of using C/C++ is to manage memory myself. I could use a unique_ptr in one place where ownership is transferred, but otherwise, lgui assumes you know how to manage memory and that you want to do it yourself. Wrapping everything in a shared_ptr is IMHO pure evil in C/C++ (use a garbage collected language if you want to do that :P).

Manually managing memory is one of the single greatest sources of bugs in code today. Memory leaks, dangling pointers, oh my! It's very simple to make your gui use shared pointers that can work for objects on the heap or the stack. Take for instance my "Eagle/SharedMemory.hpp" file :

https://github.com/EdgarReynaldo/EagleGUI/blob/core/include/Eagle/SharedMemory.hpp

It wraps a shared pointer and custom deleter so that stack and heap objects can be used together in a single object. Later on I have things like

typedef SHAREDOBJECT<WidgetBase> SHAREDWIDGET;

Which makes it an easily usable, easily copyable object that is safe to use for both stack and heap objects. No more worrying about memory. Less bugs FTW.

frank.n said:

Separate style classes
I did not want to make new subclasses just for changing the appearance, so I introduced separate style classes. These have become rather large and unwieldy and are among the things that would be in for a refactor. I didn't enjoy fiddling around with all the drawing parameters too much.

It's really not that much trouble to subclass a widget and override a single draw function or maybe two in case you need to handle events differently. I think external style classes are extranneous but I would need to look at your code to see what you're really doing. Mind pointing out a good example or where to look for the style code?

frank.n said:

Coding style
I simply like underscores better :)
These seem to be scarce in the GUI world.

Ugh. I hate stretching my pinky_up_to_the_dang_underscore_key_all_the_time. It introduces typos. I follow a different convention. Named constants are uppercase generally if exposed. Classes and methods are CamelCase. Member variables are lower case, avoiding underscores if possible. No damn m_pHungarian junk I can't stand that it's not necessary and just makes code ugly and hard to type.

frank.n said:

I suppose there is an easy way to pass a library dir to cmake.

You need to create a cmake variable in your CMakeLists.txt and set it to some default directory to search for allegro. Then I can specify the directory in cmake-gui and then reconfigure the project to look for allegro there.

EDIT
Here are some libraries you probably DON'T want to use anymore : https://www.gamedev.net/blogs/entry/2256533-most-gui-libraries-are-terrible/

GWEN looked so promising from here : https://www.allegro.cc/forums/thread/608563/933616#target
but it's dead and gone without a trace.

Peter Hull

Edgar,
GWEN went over to github but seems to have been abandoned since then.
https://github.com/garrynewman/GWEN
It has 100 forks though, one of those might be active?
Pete

[edit]: actually the github page is cited in the gamedev.net link you gave, so that wasn't news. Sorry.

Polybios

Whoa, Edgar, man, he now sees he must have done it all wrong. ::)

There are obviously different opinions about how to design a GUI...

Edgar Reynaldo
Polybios said:

Whoa, Edgar, man, he now sees he must have done it all wrong. ::)

Hey hey hey. I wasn't trying to say he did it wrong, merely explaining my design choices as a logical follow up to his. If it works for him, good enough. I just wanted to show that it wasn't necessarily the only good way to do it.

frank.n

I always felt signals and slots was intrusive and just plain bad design.

How can it be intrusive that the callee does not even have to know about the caller? I can hardly think of anything less intrusive than the way lgui does signals and slots. You can just register any function to be called by a signal, it does not even have to know about the fact it is called by a signal, you do not have to use signals yourself - it is actually not intrusive at all. ???

Quote:

It's really not that much trouble to subclass a widget and override a single draw function

You can do that even when you have styles. The idea behind styles is that you want to change the look completely rather than have to override every draw function. Usually, you want some consistency in the appearance. Plus, it keeps the inheritance hierarchy clean.

Quote:

Really? You can't pick a shorter name than a 4 namespace deep constant?

It is an enum, you can import it with using if need be, it is actually imported there, too, I think.

Quote:

Manually managing memory is one of the single greatest sources of bugs in code today.

True. Well, I certainly do not mean to use new / delete manually all the time. I usually keep my widgets as member variables of classes that are ultimately created on the stack or in some container of an object residing on the stack. There are actually no "owning" pointers and almost no new / delete calls anywhere in my code, I think (I'd rather use make_unique). So no problems with that, plus I don't force anyone to use shared pointers: not intrusive.

I think there is middle ground between using C style pointers plus manual new/delete calls and using the bulldozer of wrapping everything in shared pointers. I don't like having no control plus I think it leads to bad code because you do not have to think about ownership anymore.

Quote:

It's very simple to make your gui use shared pointers that can work for objects on the heap or the stack.

Shared pointers to objects on the stack? I don't think that is a good idea.

Quote:

frank.n.stein said

Now I doubt you were even serious. Drunk? :)

I took a look at GWEN, too (and an active fork called GWORK, I think), but found the code to be pretty unreadable.

Edgar Reynaldo
frank.n said:

I always felt signals and slots was intrusive and just plain bad design.

How can it be intrusive that the callee does not even have to know about the caller? I can hardly think of anything less intrusive than the way lgui does signals and slots. You can just register any function to be called by a signal, it does not even have to know about the fact it is called by a signal, you do not have to use signals yourself - it is actually not intrusive at all. ???

How did you solve the method signature typing problem? virtual base class with operator()? When being called, how does the callback know who called it? The source of the call often has information vital to the functioning of the callback. How do you solve scoping? How do you solve forwarding? What about return values? Heard of boost? It's a template nightmare to try and deal with these kinds of issues. I'd like to see some sample code of yours that deals with binding values and functions to signals and slots. I don't have any of these problems with my event system and widget messaging. In my gui you can listen to any widget and get every event it ever emits at the time it is emitted. You can query the gui for messages, use an event queue, or listen to the widget directly. With signals and slots, you have to have a slot for every message you want to receive. This means you need to add a signal to every slot if you want all the messages. Multiply that by 50 widgets and it's a nightmare. With an event and listener system you can listen to every event you want at once or one at a time with very little overhead. With signals and slots you have to worry about threading issues, where with an event queue it's already thread safe. To me, it's not worth all the hassle.

frank.n said:

It's really not that much trouble to subclass a widget and override a single draw function

You can do that even when you have styles. The idea behind styles is that you want to change the look completely rather than have to override every draw function. Usually, you want some consistency in the appearance. Plus, it keeps the inheritance hierarchy clean.

Like I said, kindly point out where to look for your styling code. I would like to see how you implemented it.

frank.n said:

True. Well, I certainly do not mean to use new / delete manually all the time. I usually keep my widgets as member variables of classes that are ultimately created on the stack or in some container of an object residing on the stack. There are actually no "owning" pointers and almost no new / delete calls anywhere in my code, I think (I'd rather use make_unique). So no problems with that, plus I don't force anyone to use shared pointers: not intrusive.

I think there is middle ground between using C style pointers plus manual new/delete calls and using the bulldozer of wrapping everything in shared pointers. I don't like having no control plus I think it leads to bad code because you do not have to think about ownership anymore.

It's very simple to make your gui use shared pointers that can work for objects on the heap or the stack.

Shared pointers to objects on the stack? I don't think that is a good idea.

I came to this place because manually managing memory was a tiresome buggy chore. Forcing your users to put everything on the stack or manage the memory themself sucks. I finally decided I was sick of trying to decide who was responsible for managing the memory. At one point I even had layouts managing memory and that was a terrible idea which I canned and traded in for shared pointers.

frank.n.furter said:

Edgar Reynaldo said:

frank.n.stein

Now I doubt you were even serious. Drunk? :)

I was just joking around. ;) I couldn't resist. Your name was just too convenient.

frank.n

How old are you? >:(

Edit: Never mind. :-X

Cheers, everyone. ;D

Edgar Reynaldo

Learn how to take a joke. ::)

If you can't provide a simple code example of how to use your signals and slots, I'm not going to bother reading your source or docs, especially when they don't build with the build system you provided.

As for shared objects on the stack they work just like any other object on the stack. If you give out a pointer that lives beyond their scope it's a dangling pointer. No safer or more dangerous than a regular object. This way there is a common interface. If I automatically 'owned' new widgets passed into a gui or layout, then they better be on the heap, but you can't guarantee that. So I made it so that to create a SHAREDOBJECT, you need to call a template function called HeapObject or StackObject, and pass it a pointer. It is now explicit and clear whether or not the object should be destroyed when its last reference goes out of scope.

As to who's being a grownup, you're the one refusing to answer questions. :P I've asked several times if you could point out your styling code or if you would give an example of your signals and slots.

frank.n

Never mind. :-X I thought you were trolling.

In fact, I have provided several examples of how to use the signals in this thread already: :)

lgui::TextField input;   
lgui::TextLabel output;
input.on_text_changed.connect([&output](const std::string& str) { output.set_text(str); });

Here, the label will mirror the text of the text field through connecting the TextField::set_text to the signal. In fact, the signal will call the provided lambda which could do whatever it pleases.

I usually have this as part of a class inheriting from lgui::BasicContainer or lgui::Container and capture this which automagically gives me access to every member variable.

There is also a convenience-overload of the connect-method which is passed a pointer-to-member-function and a reference to an object instance. So an alternative formulation would be this:

lgui::TextField input;   
lgui::TextLabel output;
input.on_text_changed.connect(&TextField::set_text, output);

The slots are just std::function (anything callable). The signals are usually public members prefixed with on_ and use the signal template. Declaring a signal looks like this:

lgui::Signal <const std::string&> on_text_changed;

The template parameters represent the signature of the functionals that can be connected. You can always connect functionals taking less parameters, but no functionals taking more parameters.

To emit a signal, the widget just calls on_text_changed.emit(text);

If you need the source of the call, you could use separate lambdas per source to also forward the source (or use std::bind). It would also be easy to extend the signal template to always forward a source reference. I had thought about doing that, but came to the impression that keeping it a one-way street (=simpler) would be better. Well, it's probably just not there because I haven't needed it so far. ;)

Signal header can be found here:
https://github.com/frank256/lgui/blob/master/src/lgui/signal.h

It basically uses variadic templates and perfect forwarding. Most of the complexity comes from handling connecting and disconnecting to a signal even while the signal is being emitted. I've never actually used the stuff to disconnect individually, so maybe it will have to go in the future.

I know boost and I really don't like it. ;)

Styles can be found here:
https://github.com/frank256/lgui/tree/master/src/lgui/style

Edgar Reynaldo

Actually, I think I've decided to put the kabosh on the shared pointer business. They really don't make sense semantically. Because now no one owns the memory, everybody does. And it messes with the destruction order. I thought they were such a good idea at first and I always get advised to use shared pointers now in the modern day and age.

Instead, I decided to give widgets a single owner, the gui factory. And The system would own all instances of gui factories, and so the system is responsible for destroying widgets on close. Likewise, an Image owns all of its sub images, and a window owns all of its images.

The user is still free to allocate anything on the stack, or use new and delete, or use the interface provided for widget / image / window creation. Ultimately the system will own everything. You can free anything upon request though, to save memory.

Regarding signals / slots, am I right in thinking that each widget has its own predefined signature type for each event it might emit? If so, the user has to rely on documentation to get the type right, and if it varies for every widget and every event, then it seems to be over complicating things.

I've never seen so many abstract virtual functions before... :o That's a very complex interface for styles.

frank.n said:

lgui::TextField input;   
lgui::TextLabel output;
input.on_text_changed.connect([&output](const std::string& str) { output.set_text(str); });

Okay, now this I like. I'm still not very good with lambda functions. Are you capturing a reference to the TextLabel 'output'? How does that work to bind the output object?

jmasterx

It looks like an anonymous function that captures a reference to the output widget.
So when the signal triggers that function is executed and it pushes the captured output as an argument.

However,if output is allocated on the stack and it disappears then the reference points to garbage and things crash.

You could also capture output by value but in this situation that is not too helpful.

Edgar Reynaldo

I could just never wrap my head around things like std::bind and std::mem_func and the like.

So frank.n, does each widget get its own set of signals? With different types?

I like the immediacy of signals, but they are very complex..

frank.n

So frank.n, does each widget get its own set of signals? With different types?

Yeah, they are declared individually per widget, the widget base class has none. Currently, it's usually just one per widget with one distinct signature each, mostly emitting one value only. They are intended for the semantically interesting stuff that is specific for the widget.

jmasterx said:

However,if output is allocated on the stack and it disappears then the reference points to garbage and things crash.

Right. That's the price to pay for the loose coupling. Mostly, widgets connected with each other are like siblings, ownership-wise, so they usually end up as members of the same class in my use cases. This makes the problem mostly a no-issue for me (I actually thought it was more problematic).

Another downside of the current approach is that signals cannot be overloaded (except that you can always choose to ignore parameters, i.e. you can always connect a functional taking no arguments even if the signal emitted many of them).

Because now no one owns the memory, everybody does. And it messes with the destruction order.

Exactly.

Quote:

That's a very complex interface for styles.

Yes. I'm not entirely satisfied with it, especially as every new widget added (to the lgui "core", that is) will introduce additional methods. Maybe I should have an abstract style class for each widget, with the style bundling them together and some generic method to get the appropriate style object instances. Or find some other useful abstraction over things to draw. But the original idea was to keep things simple, so I'll go with that existing solution for now. :P

Edgar Reynaldo
frank.n said:

Yeah, they are declared individually per widget, the widget base class has none. Currently, it's usually just one per widget with one distinct signature each, mostly emitting one value only. They are intended for the semantically interesting stuff that is specific for the widget.

That seems problematic if you want a clean interface. I like the way my WidgetMsg class works. It wraps a WidgetBase*, an unsigned int for the topic, and an unsigned int for the message.

Then when you want to connect functionality, you check the gui queue for a WidgetMsg matching your source and topic and message.

while (gui.HasMessages()) {
   WidgetMsg wmsg = gui.TakeNextMessage();
   if (wmsg == WidgetMsg(&mybutton , TOPIC_BUTTON_WIDGET , BUTTON_CLICKED)) {
      /// user pressed our special button, do something
   }
}

This actually gives me an idea to combine widget messaging with signals.

Say you have the following structure :

std::map<WidgetMsg , WidgetCallbackFunctionList> dispatcher;

then you could do the following to connect a signal :

dispatcher[WidgetMsg(&mybutton , TOPIC_DIALOG , BUTTON_CLICKED)] = [&gui](){gui.Close();};

And you could execute it with :

dispatcher.execute_callbacks(gui.TakeNextMessage());

And all the slots would be signalled automagically.

That's the one drawback of my system, the potential for long message processing code.

frank.n said:

That's a very complex interface for styles.

Yes. I'm not entirely satisfied with it, especially as every new widget added (to the lgui "core", that is) will introduce additional methods. Maybe I should have an abstract style class for each widget, with the style bundling them together and some generic method to get the appropriate style object instances. Or find some other useful abstraction over things to draw. But the original idea was to keep things simple, so I'll go with that existing solution for now. :P

I applaud your attempt to keep code organized and clean, but the way it is, its pretty messy. My attempts to do this in my own gui have met with moderate success. I have abstracted away background and style into a WidgetPainter class. Every widget has a reference to a WidgetPainter and it takes care of drawing its basics like the padding, border, and margin, and any background it might have.

It's a good idea to separate out 'generic' drawing functions, so that your widgets can use them in combination to draw themselves. I appreciate what you want to do style wise, but I think it would be good to reconsider how to go about doing that.

I just made a major commit to Eagle core branch. I finished reworking my gui classes to use the new WidgetBase class. It should simplify things greatly. Now I need to do some serious testing. Still got a shit ton of TODO items. I wonder if I'm ever going to release Eagle. I've been working on this for so long now, almost 10 years. I think after I get done stress testing stuff I'm going to make a feature freeze and start work on the build system and documentation. Gotta make a release. >:(

frank.n

That seems problematic if you want a clean interface.

It's interesting how much of this seems to depend on taste, really. Here, I prefer placing things down the hierarchy if they are specific. However, with the styles, our conclusions seem to be just the other way round.

In my mind, the signals are sort of the output layer and represent the state of the widget as a virtual thing the user can interact with. The GUI has already (centrally) delivered the "raw" events (mouse button pressed, key down etc.) to them and it is the job of the widgets to aggregate and interpret them. Each widget does that in its own way. That's why I think of this as being specific and decentralized. An advantage of that is that the signals can emit the appropriate values directly. A disadvantage is that you cannot / should not do certain things in a signal handler deep down the GUI call stack.

Quote:

I wonder if I'm ever going to release Eagle.

Yeah, all the refactoring, optimizing, wanting to perfect things really gets in the way of that. ;D

That's why I postponed refactoring of the styles, I'd have to see how it works with a skinned style first. Plus, some mobile support would be more important for me. I still haven't thought of how to handle things like scrolling / swiping on mobile. Drag and drop seems to be one of the (few) things to translate rather well.

Edgar Reynaldo
frank.n said:

I still haven't thought of how to handle things like scrolling / swiping on mobile. Drag and drop seems to be one of the (few) things to translate rather well.

Swipes should be fairly easy. Implement a camera that you can scroll the view of, and then listen for touch events and move the camera. Nothing needs to change position except for the view. You then have to account for the view offset when sending touch or mouse events to the widgets though. In my GUI I allow for middle mouse button drag scrolling of the view. Just have your top level widget monitor for swipes, set the view, and offset the event positions by the view.

frank.n said:

https://github.com/frank256/lgui/blob/master/src/lgui/signal.h

...

In my mind, the signals are sort of the output layer and represent the state of the widget as a virtual thing the user can interact with. The GUI has already (centrally) delivered the "raw" events (mouse button pressed, key down etc.) to them and it is the job of the widgets to aggregate and interpret them. Each widget does that in its own way. That's why I think of this as being specific and decentralized. An advantage of that is that the signals can emit the appropriate values directly. A disadvantage is that you cannot / should not do certain things in a signal handler deep down the GUI call stack.

I've been reading your signal.h header and I seem to have a basic grasp of what is going on. Not sure what is going on with delayed emitting. Tell me, you used variadic templates to pass a variable set of arguments, correct? And I see you did some kind of overloading for const and non const member functions, but I don't follow it very well. Is that C++ 14? My library is still C++11, are things like that possible in C++11?

frank.n said:

Yeah, all the refactoring, optimizing, wanting to perfect things really gets in the way of that. ;D

I've had a bad case of feature creep over the last few years. It's not a bad thing in and of itself, but it means you're constantly in development phase, and not release mode.

So far my GUI has gone through 3 major phases. First version worked with Allegro 4 only. This was during the period when Allegro 4 was being phased out in favor of the new Allegro 5 branch. I decided I needed to remove any direct dependencies on third party libraries through abstracting away all the interfaces and hiding Allegro inside an Eagle system driver. My latest phase is a total redo of the WidgetBase class and everything that goes with it. I've made things much simpler, much more extensible, and much more flexible.

I've been reading quite a few of this forums old gui threads, and man. Everybody gets all excited - "Hey let's make a GUI!" and then there are like 5 pages of arguments about C vs C++ and how best to design the library. axilmar and I have gone at it a few times about gui design. He's got extensive experience with guis and a GUI library of his own. I believe it's been long abandoned though. Let me check. Yep, ALGUI is gone. axilmar took it down off github.

Long story short, the Ultimate GUI for Allegro has been an elusive target.

axilmar said:

Quote:

It's unfortunate as the Allegro GUI just isn't good enough for me.

And for me, also. I imagine for many others, as well.

So, guys, Richard, Thomas, Steve Terry, Miran, Spellcaster, I have a question for you: why don't we join forces and produce the Allegro's ultimate GUI? we can have a short period discussing the main issues, then each one of us can take the responsibility for producing a number of widgets. Since widgets are independent of each other, each one of us can take his/her own time and do as good as job as needed. I can make the framework classes for you to get started with!!!

frank.n

Not sure what is going on with delayed emitting. Tell me, you used variadic templates to pass a variable set of arguments, correct? And I see you did some kind of overloading for const and non const member functions, but I don't follow it very well. Is that C++ 14? My library is still C++11, are things like that possible in C++11?

Yes. The const and non-const member functions are just convenience overloads to be able to directly pass a pointer-to-member-function and an object instance as described above. I had to do it for const and non-const member functions.
Delayed emitting is there for the case when a slot connects the signal to another slot while being called by the same signal. Without precautions, the newly connected slot would be emitted right away.

I used a std::list here in order to not invalidate iterators of a std::vector when something is connected while emitting. It could probably be improved by picking another container, maybe a std::vector is possible anyway.

Edgar Reynaldo

There should be a predefined time when your slots get signaled. I don't see the need for making sure you don't add a slot when it's being signaled. For example in my gui, the only time a widget can raise an event is during HandleEvent or Update. Makes everything so much simpler and easier than worrying about contention.

Niunio

Let's see, what's the tally now?

TGUI - Trent Gamblin
ALGUI - axilmar
AGUI - jmasterx
LGUI - frank.n
EAGLE - Edgar Reynaldo
WidgetZ - SiegeLord

Who else have I forgotten?

The obligatory "why haven't we all been working on the same GUI for the last 10 years?" question is open.

Yep: MinGRo not actually a GUI and no officially published yet, but there it is.

And I've working on it because nobody else seems to use Object Pascal there. ::)

Edgar Reynaldo

I just found EGG_DIALOG, Evert's old A4 style gui for Allegro 5. Neat.

frank.n

There should be a predefined time when your slots get signaled. I don't see the need for making sure you don't add a slot when it's being signaled.

It's there to handle a situation where connections of a signal are added / changed in a slot connected to the same signal. If the callee / slot / handler decides to alter the signal's connections while it is being emitted, bad things could happen.

Quote:

For example in my gui, the only time a widget can raise an event is during HandleEvent or Update.

It's the same with lgui actually. Usually, signals are emitted because of an external event having been processed by the GUI and delivered to the widget.

Edgar Reynaldo

I've finished my main upgrade and refactor on the core branch. Almost ready to merge back in with master.

But now I've got two dilemmas.

1) Most layouts do not automatically size a widget. Their size must be set according to the rules of the layout.

2) All widgets must be drawn within their parent's inner areas. This causes problems when I want to make a widget with separate parts, like a drop down list. I can add the list box and the drop down box separately no problem, but what area should the parent have? It doesn't make sense. So I need a way to add just the children when the parent is tracked by the widget handler (gui).

How do you deal with these problems in your gui frank.n?

frank.n

Most layouts do not automatically size a widget. Their size must be set according to the rules of the layout.

I don't know exactly what you mean but I guess that's probably why I finally switched to the 2-pass layout. In order to be able to set the size of a widget, the layouts need to know how large the children want to become (in some cases). In order to know that, the children need to know how large the parent would be (in some cases). This can become kind of a chicken-and-egg situation.

I've documented the layout system in a bit more detail here:
https://github.com/frank256/lgui/blob/master/src/doc/lgui.dox

You should be able to see this rendered when you do a "make doc" in the CMake dir if you have Doxygen installed, I hope. :)

Quote:

All widgets must be drawn within their parent's inner areas. This causes problems when I want to make a widget with separate parts, like a drop down list. I can add the list box and the drop down box separately no problem, but what area should the parent have? It doesn't make sense. So I need a way to add just the children when the parent is tracked by the widget handler (gui).

I've solved the drop-down case by introducing a "modal widget". This lives above anything else and receives all events as long as it is active. It may constitute the top of a separate hierarchy.

Edgar Reynaldo

make doc didn't work for me last time but mysteriously it did this time.

frank.n said:

Most layouts do not automatically size a widget. Their size must be set according to the rules of the layout.

I don't know exactly what you mean but I guess that's probably why I finally switched to the 2-pass layout. In order to be able to set the size of a widget, the layouts need to know how large the children want to become (in some cases). In order to know that, the children need to know how large the parent would be (in some cases). This can become kind of a chicken-and-egg situation.

A better explanation would be, each layout has a set of areas defined by the layout's rules. For example, a Pin layout stores a position and an alignment, and never resizes a widget. Another example is the Relative layout, which stores areas as fractional areas of the parent. The Grid layout stores grid positions, etc.... But in each case they store their own kind of layout position, which needs to be set before a widget attached to that position can receive an area.

In my gui, all children explicitly receive their position and dimensions from their parent, which is either another widget, or a layout.

Some layouts allow reposition, some allow resize, some allow both. But none of them know how to set their widget's area until 1) Their area is set, and 2) Their layout position is set.

axilmar and I got into a big semantics argument over the difference between a minimum size and a preferred size, but that's another story.

I've come up with an idea, and that is for my layouts to store elements of a layout position base class. Then I can derive the sub class from the base layout element, and add dynamic layout positions to a standard layout class. Move the inheritance from the Layout class to the LayoutElement class. This would also allow me to mix and match layout elements. I think it could be very powerful.

But of course, I've got lots of other things to do. I need to work on my scripting interface, I need more widgets, want to finish my resource manager, etc...

frank.n said:

I've solved the drop-down case by introducing a "modal widget". This lives above anything else and receives all events as long as it is active. It may constitute the top of a separate hierarchy.

In my gui, a 'modal' widget is as simple as a widget that always consumes the input as long as it has the focus. I can return DIALOG_INPUT_USED from my HandleEvent function, and that has the same effect as making it a modal widget. Don't be greedy with the input if you don't have focus though!!

EDIT
Question; How long did it take you to make custom docs with doxygen? I plan on using doxygen when I'm ready to make docs, but I don't have any experience with customizing the output. Also, it threw tons of errors during the make process, but managed to complete anyway, so I guess they were more warnings...

frank.n

Hmm, our approaches to layout seem to be quite different.

How long did it take you to make custom docs with doxygen?

There's nothing especially custom about it, I've only altered some of the values in the config file, I think. Took a short look at how to customize it further but decided it was not worth the effort.

Quote:

Also, it threw tons of errors during the make process, but managed to complete anyway, so I guess they were more warnings...

Maybe it's due to a missing dependency for creating the inheritance diagrams? I remember getting lots of warnings instead of those once on Windows, but it's been a while since I tried.

Edgar Reynaldo

I was referring to your .dox file for the layout. The format seems simple enough.

Thread #617507. Printed from Allegro.cc