Allegro.cc - Online Community

Allegro.cc Forums » The Depot » lgui: a new gui for Allegro 5

This thread is locked; no one can reply to it. rss feed Print
 1   2 
lgui: a new gui for Allegro 5
frank.n
Member #16,879
July 2018

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
Major Reynaldo
May 2007
avatar

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
Member #16,879
July 2018

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
Major Reynaldo
May 2007
avatar

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
Member #3,861
September 2003
avatar

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.

"Code is like shit - it only smells if it is not yours"
Allegro Wiki, full of examples and articles !!

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

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
Member #3,861
September 2003
avatar

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

EDIT: Hello here BTW ^^ ;D

"Code is like shit - it only smells if it is not yours"
Allegro Wiki, full of examples and articles !!

jmasterx
Member #11,410
October 2009

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

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

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
Member #934
January 2001
avatar

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
Major Reynaldo
May 2007
avatar

frank.n
Member #16,879
July 2018

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
Major Reynaldo
May 2007
avatar

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
Member #1,136
March 2001

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
Member #12,293
October 2010

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
Major Reynaldo
May 2007
avatar

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
Member #16,879
July 2018

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
Major Reynaldo
May 2007
avatar

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
Member #16,879
July 2018

How old are you? >:(

Edit: Never mind. :-X

Cheers, everyone. ;D

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

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
Member #16,879
July 2018

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
Major Reynaldo
May 2007
avatar

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
Member #11,410
October 2009

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
Major Reynaldo
May 2007
avatar

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
Member #16,879
July 2018

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

 1   2 


Go to: