Gui I: Learning about Signals/Slots

Published October 17, 2014
Advertisement
In order to make it easier for the reader, I decided to split the different modules up into multiple posts. This should also make it easier for me to write them when I got a few minutes to spare, too.

Getting started:

So at the beginning of the engine I wanted to make a 2d level editor. I already made a simple, horrible level editor for my mario world clone project. I tried to copy the style of handling GUI interaction from there. I don't have any actual code from then at hand, but it looked something like this:class TilemapController{public: // is called every tick void Update(void) { Vector2 vScrollDrag; if(m_pScrollbar->IsDragging(vScrollDrag)) SetOffset(vScrollDrag); else { Vector2 vDrag; if(m_pImage->IsDragging(vDrag)) DragMap(vDrag); else if(m_pImage->Clicked()) PaintTile(); else { Vector2 vPos; if(m_pImage->MouseMoved(vPos)) HighlightTile(vPos); } } } gui::Image* m_pImage; gui::Scrollbar* m_pScrollbar;}
For every widget and every interaction, I would have to put it at the exactly right spot in an update method, and check the interactions with if-conditions. Its extremly horrible, once you get more than very basic interaction.

Getting some advice:

Trust me, the real code back then was even more horrible. I just didn't know any better, but for some reason I started a topic on Gamedev.net on something relating the GUI. I don't recall the exact topic anymore, but I pretty quickly got adviced to drop this rigid structure, and instead got suggested using some sore of indirect message passing.

I started to look at how other GUI libaries handle this, and found that the QT-way of connecting signals/slots together, I quite liked. I didn't like that it required a heck ton of macros, so I searched for an easy and lightweight signal implementation. I found the "FastDelegate"-implementation on CodeProject (check it out here), and a signal-implementation based on that.

Cleaning things up:

With this 2-header-file signal implementation, I was able to drastically redesign and clean up my GUI code. First, thats how the signals are declared and used:class GuiWidget{ // this is called by the GUI input-handler void OnClick() { SigClicked(); } Signal0<> SigClicked; Signal1 SigDrag;}
And then, how I use it:class TilemapController{public: TilemapController(void) { m_pScrollbar->SigDrag.Connect(this, &TilemapController::SetOffset); m_pImage->SigDrag.Connect(this, &TilemapController::DragMap); m_pImage->SigClicked.Connect(this, &TilemapController::PaintTile); m_pImage->SigMouseMove.Connect(this, &TilemapController::HighlightTile); } gui::Image* m_pImage; gui::Scrollbar* m_pScrollbar;}
Now thats much nicer, right? Now I can just connect signals in the initialization code, and the rest happens "magically" in the background. This also changed my whole outlook and programming. I realized that I don't have to write tons of nested if-statements for everything that is going to happen, and that started my longing for taking more care about code design, and not just blindly typing everything out as I go.

The final touch:

One thing you might have noticed is that each Signal needs a specifier of how many arguments it has, as each argument number is its own seperate templated class. This also limits the number of maximum arguments you can have, as in the default libary this was only implemented to 8.
Since I happen to use C++11 and already make use of varidic templates, I modified the Delegate/Signal implementation to use variadic templates, thus getting rid of much duplicated code, also eliminating the explicit specification of the number of arguments:Signal<> SigTest;Signal SigTest2;SigTest();SigTest2(0.0f, 1.0f);
Also, I sometimes used to have classes which just forwarded signals to another signal, which resulted in some tedious boilerplate-code, which I also reduced by adding helper-functions:// first, I used to do it like thisclass Foo{ Foo() { bar.Signal.Connect(this, Foo::OnSignal); } void OnSignal(int, float, float) { SigForward(int, float, float); } Signal SigForward; private: Bar bar;};// then, I noticed you could just connect the signal to the next one. The syntax was ugly and redundant, though:bar.Signal.Connect(SigForward, Signal::operator());// but it was quite straightfoward to put into a helper-functions:connectSignals(bar.Signal, SigForward);
I've attached the modified files to this thread.

Signals11.zip

The original Signals-Libary comes from this repo and is under the MIT licence:

https://github.com/pbhogan/Signals
http://opensource.org/licenses/mit-license.php

Thanks for reading. Next time, I'm going to talk about the actual widget implementation.
3 likes 4 comments

Comments

Endurion

Very nice!

I'm also using fastdelegate for my GUI framework. Do you have a link to the Signal library you used on top?

October 17, 2014 12:12 PM
Juliean

Sure, I've edited the information to the bottom of the entry. In case you happen to use C++11, I've also posted a modification of the libary using variadic templates to eliminate the specific "DelegateX/SignalX" number of arguments in the classes, getting rid of a lot of "duplicated" code as well as making it easier to use. And I also included helper functions for chaining signals (connectSignals/disconnectSignals).

October 17, 2014 12:33 PM
Endurion

Thanks a lot! I'll keep an eye on your journal :)

October 17, 2014 12:47 PM
Juliean

Thank you, most appreciated :)

October 17, 2014 12:58 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement