Allegro.cc - Online Community

Allegro.cc Forums » The Depot » Official algui thread.

This thread is locked; no one can reply to it. rss feed Print
Official algui thread.
bamccaig
Member #7,536
July 2006
avatar

Updated the Makefile to use Allegro 5.0. Also added a generic rule to build object files from source files in the src/ directory. Don't know though how to abstract the mapping of object files to the respective output so ${LIBOBJS} is still static (and updated).

** APPEND **

Just pulled the UTF-8 updates in. Now the program doesn't seem to respond to the close button or ESC key. :-X I don't know if it's a bug in the program or in the build system, but naturally I blame the program. ;)

axilmar
Member #1,204
April 2001

Thanks Bam.

@Edgar Reynaldo:

With events in the event queue, you cannot do composite widgets, like a scroll bar with buttons, where the button click event is connected to the scroll bar's value change event.

This is a very big problem with the event queue.

EDIT:

I am sure it's the program. However, it works fine on Windows, and I don't have Linux to test it.

Remove the skinning (_main.c, lines 63-64) and see if it works.

EDIT2:

I've pulled Bam's makefile.

EDIT3:

Bam, I've replaced al_ustr_empty_string() with al_ustr_new(""), because I think the string returned by al_ustr_empty_string() must not be freed. Can you try it and tell me if it works for you?

bamccaig
Member #7,536
July 2006
avatar

axilmar said:

I've pulled Bam's makefile.

Unfortunately, between the two of us we borked it. It should be fixed again though on my side. You seemed to have replaced tabs with spaces when you merged. That's generally a bad idea because Makefile's use tabs as part of their syntax. AFAIK, those tabs are necessary (I use spaces for indentation too, but have Vim configured to use tabs in Makefiles). I also broke the Makefile while trying to rearrange things, but I think I have it fixed again. I've added a branch on my end for maintaining the Makefile. A script counts the objects in the library and compares it to the source files in src/. Not the cleanest way to go about things, but it gives me peace of mind that I have them all. :P Hopefully I don't screw up and merge it into master. :P

axilmar said:

Bam, I've replaced al_ustr_empty_string() with al_ustr_new(""), because I think the string returned by al_ustr_empty_string() must not be freed. Can you try it and tell me if it works for you?

I updated again, but when I run it all I see is a background image. It doesn't seem to respond to input though (ESC, 1, 2, and 3 do nothing). I can't even close the window with the close button any more. I need to ^C from the terminal I launched it from to stop it. :(

axilmar
Member #1,204
April 2001

Do I have to wait for your pull request or can I pull the makefile now?

bamccaig
Member #7,536
July 2006
avatar

axilmar said:

Do I have to wait for your pull request or can I pull the makefile now?

You can pull it any time you want. I've been pulling from you without even checking GitHub. ;D As a general rule, a pull request is just a way to communicate to somebody that you have something you'd like them to merge with. If you know somebody else has something that you want to merge with though you can just do it.

MiquelFire
Member #3,110
January 2003
avatar

Pull Request = PM that effectively says "Pull from my fork please"

In a way, it's almost as if there's pull requests embedded in this thread :P

---
Febreze (and other air fresheners actually) is just below perfumes/colognes, and that's just below dead skunks in terms of smells that offend my nose.
MiquelFire.red | +Me
Windows 8 is a toned, stylish, polished professional athlete. But it’s wearing clown makeup, and that creates a serious image problem. ~PCWorld Article

AMCerasoli
Member #11,955
May 2010
avatar

person said:

The Makefile was written in Linux (Fedora 13). It's not very robust and not platform-independent. The good news is that it's open source so go ahead and fix it and contribute your fixes. Or if you don't care about a build system and just want to build it, compile it manually; on the command-line or using your favorite IDE. The pkg-config system is used in Linux to fill in the link libraries in a pretty way. Allegro 4 did something similar with allegro-config, but again, I think that was Linux only. Have you considered switching to Linux?

If I had the knowledge to do my own makefile I would do it, I'm actually reading a tutorial about it.

When I try to compile it with my IDE I just get the object files (.o). shouldn't be a library files (.a)? inside the lib folder?

But now I'm trying to compile the new version and I'm getting: "Makefile:33: *** missing separator. Stop." (I just read what might be happening)

Yes I use Linux too (Trisquel) but when I distribute my software I would like to say:
"Windows Version"
"Linux Version"
Why to learn one OS if I can learn two? ;D

EDIT:

And now I'm trying to compile it with my IDE and I get:

Assertion failed: skin, file C:\axilmal\src\algui_widget.c, line 2230

EDITED AGAIN:

I got it!! I got it!!

And why there is a font file?

EDITED AGAIN:

In the file:"algui_skin.h" you wrote "an algui skin is nothing more an an Allegro config file with resource strings and a path." just if you want to change it...

axilmar
Member #1,204
April 2001

I hope I copied Bamcaig's makefile correctly this time :-).

And why there is a font file?

It's for the widgets with text.

Edgar Reynaldo
Member #8,592
May 2007
avatar

axilmar said:

Edgar, C is a different language than C++. We were talking about C++. Whatever applies to C++ does not apply to C and vice versa.

Well, really we were talking about C, because that is what you are writing your library in.

axilmar said:

For now, I am thinking about a simple system: callbacks. Each widget will have one or more callbacks for its 'events'.

This leads right back to the extra code I posted before. Making arbitrary structs just to reference data, making arbitrary un-reuseable functions, and making the user set callbacks if he wants to do anything useful with your GUI's widgets.

Signals and slots may be a more powerful and versatile mechanism in C++, but I still think events are more useful than callbacks in C. It is rather rare that you need to have reaction code immediately follow a widget event, and thus you rarely need to have callbacks. I think that you are just making things more complicated than they should be, but it's your choice.

axilmar said:

With events in the event queue, you cannot do composite widgets, like a scroll bar with buttons, where the button click event is connected to the scroll bar's value change event.

This is a very big problem with the event queue.

This is not true, and it is also not a problem.

If you have composite widgets, then set the contained widgets parent pointer to the container address. When QueueUserMessage is called from the contained widgets, their parent(the container) gets QueueUserMessage called and it can check the messages before passing them on to its parent. This way, a scrollbar can check a scroll button's messages and set the slider widget's value accordingly.

The only requirement is to set a parent pointer, and to monitor the child's messages with QueueUserMessage. It works very well in my GUI.

Trezker
Member #1,739
December 2001
avatar

Events queue is the simplest system IMHO.

I think it's a good idea to have event queue in place as the first and simplest option to handle gui events. After that you add other methods of handling events for choice. But only after you have a good working GUI system in place.

With the event queue as I designed it. When a widget has a new event it just needs to call a function and it's done. Push_event(Event(this, "I got sick"));

I think a callback system clutters up your widgets.

axilmar
Member #1,204
April 2001

Well, really we were talking about C, because that is what you are writing your library in.

Then why do you post c++ examples and comments on how you would do it in c++?

Let's not mention c++ again then. We don't need to be confused by a different language.

Quote:

This leads right back to the extra code I posted before. Making arbitrary structs just to reference data, making arbitrary un-reuseable functions, and making the user set callbacks if he wants to do anything useful with your GUI's widgets.

You can always use callbacks to post events to the main event queue.

And you can also make your data global variables instead of declaring them as local variables in a function, so as that you don't need to make arbitrary structs.

There is no practical difference between declaring variables in function 'main' and outside of 'main' anyway.

Quote:

It is rather rare that you need to have reaction code immediately follow a widget event, and thus you rarely need to have callbacks.

It's not rare at all. Take a text box, for example: immediate callbacks are needed so as that the text box controls which text goes in the widget. The alternative is to code a parser which will be used by the text box, something that is extremely more difficult to do.

Quote:

I think that you are just making things more complicated than they should be, but it's your choice.

Events are more complicated than callbacks.

You need to manage all those event types.

You need to write giant switch or if-then-else statements.

When debugging, event code will be executed at a different time from the actual event, making debugging difficult.

Quote:

This is not true, and it is also not a problem.

If you have composite widgets, then set the contained widgets parent pointer to the container address. When QueueUserMessage is called from the contained widgets, their parent(the container) gets QueueUserMessage called and it can check the messages before passing them on to its parent. This way, a scrollbar can check a scroll button's messages and set the slider widget's value accordingly.

The only requirement is to set a parent pointer, and to monitor the child's messages with QueueUserMessage. It works very well in my GUI.

Your system does not work in the following cases:

1) if a parent widget wants to handle events from grandchildren.
2) if there is no parent widget.
3) if the children want to handle events of their parent.
4) if you want a subclass of a widget to manage the event raised by a base class.
5) if you want to invoke other code after the event.

Many people here say the event queue is the simplest option, but I say that it is not. Let's write some C example code to support our arguments.

Please show me an example where events in the event queue is simpler code than callbacks.

Here is an example of mine with a textbox that uses a callback to filter inserted text:

#SelectExpand
1//insert text event 2static void text_box_insert(ALGUI_TEXTBOX *textbox, ALGUI_TEXTBOX_INSERT *insert_data, void *user_data) 3{ 4 int pos = 0; 5 int ch; 6 for(;;) { 7 ch = al_ustr_get(insert_data->text, pos); 8 if (ch == '\0') break; 9 if (!isdigit(ch)) { 10 insert_data->ok = false; 11 break; 12 } 13 pos += al_utf8_width(ch); 14 } 15} 16 17//textbox 18ALGUI_TEXT_BOX *text_box; 19 20//main 21int main() { 22 //bla bla bla init, create widgets etc 23 24 //create the text box 25 text_box = algui_create_text_box(); 26 algui_set_text_box_insert(text_box, text_box_insert, NULL); 27 28 //event loop 29 for(;;) { 30 al_wait_for_event(&event); 31 switch (event.type) { 32 ... 33 } 34 } 35}

Please show me how to do the above with events.

And also show me one thing that cannot be done with callbacks just as easily as with events.

Trezker
Member #1,739
December 2001
avatar

That looks like your making custom behavior for handling input to the text box widget. But I don't see how you're getting the input for it...

My widgets have a single input function that takes all allegro events.

Each widget can have its own event queue. So you can have a controller with an event queue that only gets events from one specific widget. But the widgets also send their events to the parent, which sends to its parent and so on. So you can plug in an event queue at any level and get events from the whole interface or just a part of it.

axilmar
Member #1,204
April 2001

Trezker said:

That looks like your making custom behavior for handling input to the text box widget. But I don't see how you're getting the input for it...

A function takes an allegro event and a widget and sends messages to the widget or its children, depending on the event type.

Edgar Reynaldo
Member #8,592
May 2007
avatar

axilmar said:

You can always use callbacks to post events to the main event queue.

So the user should recreate event id's for all of your widgets just so they can use a style that works better for them? No thanks.

axilmar said:

And you can also make your data global variables instead of declaring them as local variables in a function, so as that you don't need to make arbitrary structs.

There is no practical difference between declaring variables in function 'main' and outside of 'main' anyway.

Why should I litter my code with global variables just so I can use your GUI library cleanly? There is a practical difference between local and global variables, global variables are harder to track because they are declared away from where they are used. I prefer to work locally if I can, and only use global variables if they are necessary to share information between functions.

So here are the choices that you are giving your users :
A) Write arbitrary structs and functions to use in a callback
B) Use lots of global variables
C) Recreate events on their own

Those don't look like choices I would want to make if I were using your library.

axilmar said:

It's not rare at all. Take a text box, for example: immediate callbacks are needed so as that the text box controls which text goes in the widget. The alternative is to code a parser which will be used by the text box, something that is extremely more difficult to do.

So far, a text box is the only example of a widget that may need a callback that you've given. If that's the only widget that needs it, I'd say it is still pretty rare. In my text_input class, I have three parsers, one for integer input, one for floating point input, and one for regular string input. While they took more than a few minutes to write, they cover all the basic input needs. There's no callback involved, but I could extend it to do so if necessary for custom data. Like I said earlier, if you really need a callback that's fine, but if you don't then why trouble the user with one.

axilmar said:

Events are more complicated than callbacks.

You need to manage all those event types.

You need to write giant switch or if-then-else statements.

I think our definitions of simple disagree.

When I write my GUI editor, the widgets will have functions to save their constructors and accompanying code to a source file via an ostringstream. Each widget event that can be produced by a widget will be written to the stream for them, making managing event types very, very easy. Also, the events are named well, and there will be no confusion as to what has happened with each event.

In my current GUI test program, I have 77 widgets and 40 different messages that I check for. It has not been a problem to keep them separate, nor is it a problem to modify the code that takes place when the events are detected. So there's a list of 40 if statements in my code, so what? It's better than having 40 different functions and 40 different structs and setting 40 callbacks. Don't tell me that the processor can't handle 40*60 if statements per second, because it can handle millions.

axilmar said:

When debugging, event code will be executed at a different time from the actual event, making debugging difficult.

Well, I can't speak for you, but I haven't had any problems debugging my program because of events.

axilmar said:

Your system does not work in the following cases:

1) if a parent widget wants to handle events from grandchildren.
2) if there is no parent widget.
3) if the children want to handle events of their parent.
4) if you want a subclass of a widget to manage the event raised by a base class.
5) if you want to invoke other code after the event.

I will respond to each case :

1) A widget gets the events of all of it's children, and all of it's children's children, and so on... So technically, it could handle it's grandchilren's events. It would need to know their address and type, but it could do so. To this date, I have never needed to handle events more distant than parent to child, and it is probably a design flaw if you need to do something like that. Also, a parent can intercept and retransmit an altered message, thereby passing the message from grandchild to grandparent anyway.

2) If there is no parent widget, then it is a top level widget. In my GUI, all top level widgets should be WidgetHandlers, which queue the messages for the user. You can use a standalone widget if you really want to, but then you will not receive it's messages and you have to handle everything that the WidgetHandler would have handled for you. There's really no reason not to use a top level WidgetHandler though, so it doesn't bother me. The user will know that if they use standalone widgets, they will have to manage many things on their own.

3) No child should ever handle a parent's event, and I don't see any reason why they should want to either.

4) Base classes shouldn't really be sending events, and even if they are, the derived class will probably override the base class method that sends that event anyway.

5) I don't understand what you are saying with this point. The widget/user can invoke whatever code they want to after the event occurs.

axilmar said:

Many people here say the event queue is the simplest option, but I say that it is not. Let's write some C example code to support our arguments.

Please show me an example where events in the event queue is simpler code than callbacks.

Okay, I'll take an example from my GUI. It's in C++ because even if I was using your C library, I would still write my code in C++. You can imagine it in C just as well, so I'll leave that for an exercise to the reader.

This is what happens when the user changes some HSV sliders.

#SelectExpand
1 if ((wmsg == hs_delta) || (wmsg == ss_delta) || (wmsg == vs0_delta) || 2 (wmsg == vs1_delta) || (wmsg == vs2_delta) || (wmsg == vs3_delta) || (wmsg == vs4_delta) || (wmsg == vs5_delta)) { 3 set_default_hsv_vcolor(SDCOL , vslider0.GetValue()); 4 set_default_hsv_vcolor(BGCOL , vslider1.GetValue()); 5 set_default_hsv_vcolor(MGCOL , vslider2.GetValue()); 6 set_default_hsv_vcolor(FGCOL , vslider3.GetValue()); 7 set_default_hsv_vcolor(HLCOL , vslider4.GetValue()); 8 set_default_hsv_vcolor(TXTCOL , vslider5.GetValue()); 9 huevalue.SetText(hue_slider.GetValue() , min_width , num_decimals); 10 satvalue.SetText(sat_slider.GetValue() , min_width , num_decimals); 11 vs0value.SetText(vslider0.GetValue() , min_width , num_decimals); 12 vs1value.SetText(vslider1.GetValue() , min_width , num_decimals); 13 vs2value.SetText(vslider2.GetValue() , min_width , num_decimals); 14 vs3value.SetText(vslider3.GetValue() , min_width , num_decimals); 15 vs4value.SetText(vslider4.GetValue() , min_width , num_decimals); 16 vs5value.SetText(vslider5.GetValue() , min_width , num_decimals); 17 hsv_colors = DefaultWidgetHSVColors(hue_slider.GetValue() , sat_slider.GetValue()); 18 gui.UseWidgetColorset(&hsv_colors); 19 gui.QueueUserMessage(colselect); 20 }

And now, contrast my simple message based code with what you would have to use with a callback mechanism :

#SelectExpand
1// at top of main.cpp 2class UserData { 3public : 4 Slider<float>* vsl[6]; 5 Slider<float>* hsl; 6 Slider<float>* ssl; 7 TextWidget* vsvalue[6]; 8 TextWidget* hvalue; 9 TextWidget* svalue; 10 WidgetColorset* hsv_cols; 11 WidgetHandler* _gui; 12} 13 14void CallBack(void* caller , void* data); 15 16void InitUserData(UserData* ud , Slider<float>* vsl0 , Slider<float>* vsl1 , Slider<float>* vsl2 , 17 Slider<float>* vsl3 , Slider<float>* vsl4 , Slider<float>* vsl5 , 18 Slider<float>* hueslider , Slider<float>* satslider, 19 TextWidget* vsvalue0 , TextWidget* vsvalue1 , TextWidget* vsvalue2, 20 TextWidget* vsvalue3 , TextWidget* vsvalue4 , TextWidget* vsvalue5, 21 TextWidget* huevalue , TextWidget* satvalue, 22 WidgetColorset* hsv_colors , WidgetHandler* gui); 23 24// in main 25// gui setup... 26UserData ud; 27InitUserData(&ud , &vslider0 , &vslider1 , &vslider2, 28 &vslider3 , &vslider4 , &vslider5, 29 &hue_slider , &sat_slider, 30 &vs0value , &vs1value , &vs2value, 31 &vs3value , &vs4value , &vs5value, 32 &huevalue , &satvalue, 33 &hsv_colors , &gui); 34hue_slider.SetSliderValueChangeCallbackAndData(CallBack , (void*)(&ud)); 35sat_slider.SetSliderValueChangeCallbackAndData(CallBack , (void*)(&ud)); 36vslider0.SetSliderValueChangeCallbackAndData(CallBack , (void*)(&ud)); 37vslider1.SetSliderValueChangeCallbackAndData(CallBack , (void*)(&ud)); 38vslider2.SetSliderValueChangeCallbackAndData(CallBack , (void*)(&ud)); 39vslider3.SetSliderValueChangeCallbackAndData(CallBack , (void*)(&ud)); 40vslider4.SetSliderValueChangeCallbackAndData(CallBack , (void*)(&ud)); 41vslider5.SetSliderValueChangeCallbackAndData(CallBack , (void*)(&ud)); 42 43// after main 44 45 46void InitUserData(UserData* ud , Slider<float>* vsl0 , Slider<float>* vsl1 , Slider<float>* vsl2 , 47 Slider<float>* vsl3 , Slider<float>* vsl4 , Slider<float>* vsl5 , 48 Slider<float>* hueslider , Slider<float>* satslider, 49 TextWidget* vsvalue0 , TextWidget* vsvalue1 , TextWidget* vsvalue2, 50 TextWidget* vsvalue3 , TextWidget* vsvalue4 , TextWidget* vsvalue5, 51 TextWidget* huevalue , TextWidget* satvalue, 52 WidgetColorset* hsv_colors , WidgetHandler* gui) { 53 ud->vsl[0] = vsl0; 54 ud->vsl[1] = vsl1; 55 ud->vsl[2] = vsl2; 56 ud->vsl[3] = vsl3; 57 ud->vsl[4] = vsl4; 58 ud->vsl[5] = vsl5; 59 ud->hsl = hueslider; 60 ud->ssl = satslider; 61 ud->vsvalue[0] = vsvalue0; 62 ud->vsvalue[1] = vsvalue1; 63 ud->vsvalue[2] = vsvalue2; 64 ud->vsvalue[3] = vsvalue3; 65 ud->vsvalue[4] = vsvalue4; 66 ud->vsvalue[5] = vsvalue05; 67 ud->hvalue = huevalue; 68 ud->svalue = satvalue; 69 ud->hsv_colr = hsv_colors; 70 ud->_gui = gui; 71} 72 73 74 75void CallBack(void* caller , void* data) { 76 UserData* ud = (UserData*)data; 77 if (ud) { 78 set_default_hsv_vcolor(SDCOL , ud->vsl[0]->GetValue()); 79 set_default_hsv_vcolor(BGCOL , ud->vsl[1]->GetValue()); 80 set_default_hsv_vcolor(MGCOL , ud->vsl[2]->GetValue()); 81 set_default_hsv_vcolor(FGCOL , ud->vsl[3]->GetValue()); 82 set_default_hsv_vcolor(HLCOL , ud->vsl[4]->GetValue()); 83 set_default_hsv_vcolor(TXTCOL , ud->vsl[5]->GetValue()); 84 ud->hvalue->SetText(ud->hsl->GetValue() , min_width , num_decimals); 85 ud->svalue->SetText(ud->ssl->GetValue() , min_width , num_decimals); 86 ud->vsvalue[0]->SetText(ud->vsl[0]->GetValue() , min_width , num_decimals); 87 ud->vsvalue[1]->SetText(ud->vsl[1]->GetValue() , min_width , num_decimals); 88 ud->vsvalue[2]->SetText(ud->vsl[2]->GetValue() , min_width , num_decimals); 89 ud->vsvalue[3]->SetText(ud->vsl[3]->GetValue() , min_width , num_decimals); 90 ud->vsvalue[4]->SetText(ud->vsl[4]->GetValue() , min_width , num_decimals); 91 ud->vsvalue[5]->SetText(ud->vsl[5]->GetValue() , min_width , num_decimals); 92 *(ud->hsv_cols) = DefaultWidgetHSVColors(ud->hsl->GetValue() , ud->ssl->GetValue()); 93 ud->_gui->UseWidgetColorset(ud->hsv_cols); 94 ud->_gui->QueueUserMessage(colselect); 95 } 96}

axilmar said:

Here is an example of mine with a textbox that uses a callback to filter inserted text:

Like I said before, if you really need a callback, use one. However, if it doesn't make things easier and simpler, then don't use a callback.

axilmar said:

Please show me how to do the above with events.

You picked one example where it's more appropriate to use a callback, but that doesn't prove that using callbacks is always appropriate however.

axilmar said:

And also show me one thing that cannot be done with callbacks just as easily as with events.

Just compare the two sets of code above. Which would you prefer?

Trezker
Member #1,739
December 2001
avatar

No widgets should ever handle events from any other widgets, they're just dumb views. All GUI event handling should be outside your views in controller code.
Widgets handle pure input and timer events.

There are some things I don't count in this. I don't know how to name it, the view internal behavior. Like, a text box does change itself on user input without a controller, a button gets pressed, a slider moves etc... It would be a real pain if the GUI user would have to make his own code for all this, that would defeat the purpose of even making a GUI library.

There's also the layout system, where containers and their children interact to handle automatic resizing and positioning. This is not handled through events or callbacks at all. Its a regular system of function calls up and down the widget hierarchy.

The only time widgets handle events from other widgets is if it's a composite widget. But then those events aren't even supposed to reach outside the GUI system, they're internal to a single composite widget.

EDIT:
If you put your widgets in a std container.

if("moved" == event.type && std::find(sliders.begin(), sliders.end(), event.source) != sliders.end())
{
   //Controll code
}

axilmar
Member #1,204
April 2001

So the user should recreate event id's for all of your widgets just so they can use a style that works better for them? No thanks.

There is no 'recreation' involved. It's 'creation'. My gui library will not contain any event ids.

Quote:

Why should I litter my code with global variables just so I can use your GUI library cleanly?

Why should I litter my code with local variables just so I can use your gui library cleanly?

Quote:

There is a practical difference between local and global variables, global variables are harder to track because they are declared away from where they are used.

With your system, main() will be so long that the effect will practically be the same.

I don't know about you, but I consider long functions badly written code. I always break up long functions into smaller functions, so, for me, having global variables makes more sense.

Variables in main are there for the duration of the program anyway, so there is no practical difference with globals.

I also do the following: I always have comment headers that define program sections, and I always put my global variables into one section, so actually it's a lot clearer than having local variables in the main function.

Quote:

So far, a text box is the only example of a widget that may need a callback that you've given.

There may be others. I can't tell how many they are if I don't do a complete design of the gui library.

Just now I can think of these other cases where callbacks are needed:

  • popup context menus embedded into widgets. For example, in a text box, the right click usually leads to a menu with cut/copy/paste/select all commands. With callbacks, it is much easier to create such a menu which goes hand-in-hand with the text box widget. With events, one would need to handle the cut/copy/paste/select all commands and then invoke a function of the widget to perform the action.


  • dynamic population of lists with items.


  • composite widgets like a scroll view with buttons.

etc

Quote:

When I write my GUI editor, the widgets will have functions to save their constructors and accompanying code to a source file via an ostringstream. Each widget event that can be produced by a widget will be written to the stream for them, making managing event types very, very easy. Also, the events are named well, and there will be no confusion as to what has happened with each event.

But when you want to delete widgets, their event types will require management.

Look, I have had quite a few problems with MFC the last 12 years that I am forced to use it. MFC uses event types/notification ids all over the place. I can't tell you how fast they can become problematic.

Quote:

In my current GUI test program, I have 77 widgets and 40 different messages that I check for.

Well, a game or application may have ten times more widgets.

Quote:

So there's a list of 40 if statements in my code, so what?

How long is your main()? if it's anything like more 40 lines, you need to break it up. Otherwise, it can quickly become unmaintainable.

Quote:

To this date, I have never needed to handle events more distant than parent to child, and it is probably a design flaw if you need to do something like that.

So I guess you never had layout management in your code, where there is a layout widget between the parent and child.

Quote:

If there is no parent widget, then it is a top level widget.

What if the events should be handled by a non-widget?

Quote:

No child should ever handle a parent's event, and I don't see any reason why they should want to either.

Why not? a child widget in a top-level window may cancel its operation when the top-level window is closed (for example). It's good code to be able to make the child respond to a parent's widget, because this code can be easily reused. It's a more object-oriented design.

Quote:

Base classes shouldn't really be sending events, and even if they are, the derived class will probably override the base class method that sends that event anyway.

Are you telling me that a Button base class should not send a click event, even if I subclassed it to change the way it is drawn?

I think you should re-examine your design ideas.

Quote:

I don't understand what you are saying with this point. The widget/user can invoke whatever code they want to after the event occurs.

But the problem is when you break up a well defined procedure. For example, a widget does this:

  • receive an event

  • get input from the user

  • process input with the help of a callback

  • raise another event

With callbacks, the procedure can be embedded into the widget's logic. With your design, the steps 1-2 happen inside the widget, the step 3 happens from the event queue, and step 4 does not happen unless the user handles the event.

Quote:

And now, contrast my simple message based code with what you would have to use with a callback mechanism :

You are doing extra unnecessary work. Here is how I would code it with callbacks:

#SelectExpand
1void callback() { 2 if ((wmsg == hs_delta) || (wmsg == ss_delta) || (wmsg == vs0_delta) || 3 (wmsg == vs1_delta) || (wmsg == vs2_delta) || (wmsg == vs3_delta) || (wmsg == vs4_delta) || (wmsg == vs5_delta)) { 4 set_default_hsv_vcolor(SDCOL , vslider0.GetValue()); 5 set_default_hsv_vcolor(BGCOL , vslider1.GetValue()); 6 set_default_hsv_vcolor(MGCOL , vslider2.GetValue()); 7 set_default_hsv_vcolor(FGCOL , vslider3.GetValue()); 8 set_default_hsv_vcolor(HLCOL , vslider4.GetValue()); 9 set_default_hsv_vcolor(TXTCOL , vslider5.GetValue()); 10 huevalue.SetText(hue_slider.GetValue() , min_width , num_decimals); 11 satvalue.SetText(sat_slider.GetValue() , min_width , num_decimals); 12 vs0value.SetText(vslider0.GetValue() , min_width , num_decimals); 13 vs1value.SetText(vslider1.GetValue() , min_width , num_decimals); 14 vs2value.SetText(vslider2.GetValue() , min_width , num_decimals); 15 vs3value.SetText(vslider3.GetValue() , min_width , num_decimals); 16 vs4value.SetText(vslider4.GetValue() , min_width , num_decimals); 17 vs5value.SetText(vslider5.GetValue() , min_width , num_decimals); 18 hsv_colors = DefaultWidgetHSVColors(hue_slider.GetValue() , sat_slider.GetValue()); 19 gui.UseWidgetColorset(&hsv_colors); 20 } 21}

The variables touched inside the callback would be globals.

Quote:

Like I said before, if you really need a callback, use one. However, if it doesn't make things easier and simpler, then don't use a callback.

I'd like my library to be consistent. If there was something that the event queue offered me that I cannot do with callbacks, then I'd consider that solution.

Quote:

You picked one example where it's more appropriate to use a callback, but that doesn't prove that using callbacks is always appropriate however.

As I've shown you, callbacks cover more use cases than events. Callbacks are a more generic solution.

Quote:

Just compare the two sets of code above. Which would you prefer?

Signals and slots coupled with lambda functions. Since I can't have that in C, I prefer callbacks.

I had quite a lot of issues with MFC. The event-id model just doesn't scale that well.

And then, there is the issue of turning a design of events into object-oriented code. This is where the event system completely breaks down. MFC message maps anyone?

Martin Kalbfuß
Member #9,131
October 2007
avatar

which is hardly helpful as an argument because it's circular.

You could use two queues. This would brake the cicle.

hardware/system interface -> GUI-queue -> GUI -> game-queue -> game

The gui chatches the user input and either consumes it, or push it into the game queue. The only event source of the game queue is the GUI. I think the system does the same. If you resize a window it's done with the mouse. But you get an resize-event.

This way you could even run the gui in it's own thread.

http://remote-lisp.spdns.de -- my server side lisp interpreter
http://www.nongnu.org/gm2/ -- Modula-2 alias Pascal++

Evert
Member #794
November 2000
avatar

axilmar said:

I don't know about you, but I consider long functions badly written code. I always break up long functions into smaller functions, so, for me, having global variables makes more sense.

To me, long, convoluted functions and global variables are both examples of bad code, with global variables by far the greater evil of the two.

Quote:

How long is your main()? if it's anything like more 40 lines, you need to break it up.

This is fine as a guideline, but sometimes it's unavoidable that a function becomes longer than that. That's ok, as long as the overall structure is clear.

Trezker
Member #1,739
December 2001
avatar

Of course you don't put all the event handling in main. You make controllers that handle specific tasks.

while()
{
   ALLEGRO_EVENT event;
   while (al_get_next_event(event_queue, &event))
   {
      root->Handle_event(event);
   }
   for(Controllers::iterator i = controllers.begin(); i != controllers.end())
   {
      (*i)->Update();
   }
   root->Render();
}

axilmar
Member #1,204
April 2001

@Evert: in the case of an event queue, main() variables are equal to global variables. There is no practical difference between the two.

@Trezker: I like your idea, but I guess Edgar Raynaldo will disagree with you, I am sure :).

Evert
Member #794
November 2000
avatar

axilmar said:

in the case of an event queue, main() variables are equal to global variables. There is no practical difference between the two.

???

Matthew Leverton
Supreme Loser
January 1999
avatar

I declare a stalemate. 8-)

axilmar
Member #1,204
April 2001

Main() variables and global variables are the same, when it comes to using an event queue: all the code will be in the main loop, which has access to all the main() variables or the global variables. So, one might put the main() variables as globals.

Evert
Member #794
November 2000
avatar

axilmar said:

all the code will be in the main loop,

No...

Quote:

which has access to all the main() variables or the global variables. So, one might put the main() variables as globals.

Oh, you mean: if I look at one function, from the point of view of that function, it doesn't matter whether the variables used in it are global or local?
That is true, of course. It is also an empty statement. By the same logic, you could get rid of function arguments and local variables alltogether and use globals exclusively everywhere.
Whatever handles the message from the GUI, be it an event handler or a callback, obviously needs to have access to whatever object it needs to manipulate as a result of the message. That is trivially the case if the object is in the same scope, or, alternatively, if it is passed in as a variable.

I strongly prefer the latter, since it's far easier to write reusable and maintainable code that way.

I declare a stalemate.

More of a draw by repetition. :P

Edgar Reynaldo
Member #8,592
May 2007
avatar

axilmar said:

There is no 'recreation' involved. It's 'creation'. My gui library will not contain any event ids.

So all of your users that prefer clean local code have to create events on their own. Thanks a lot.

Once again, here are the choices that you are giving your users :
A) Write arbitrary structs and functions to use in a callback
B) Use lots of global variables
C) Recreate events on their own

These are not nice choices to have to make for a user. Here are the choices you give your users when you use events :
A) Write clean local code
B) Use a callback function if they wish to, not because they have to
C) Use global or local variables as they wish, and not be forced to use globals just to work cleanly with your library.

These are much better choices to provide to your users. They can respond in whatever way works best for them, and they are not limited by callbacks.

axilmar said:

Why should I litter my code with local variables just so I can use your gui library cleanly?

You're kidding right? You use local variables because they are only supposed to last as long as the function that uses them. You use global variables because they outlive function calls. Don't use global variables just because they have global access. Say my gui handling wasn't done in main, but in a separate callable function, like for opening a dialog. Why should all the widgets in that dialog outlive the function they were designed for? It's a waste of memory, and bad practice. Besides all that, you can use variables of any scope when working with my library, but your library forces the use of global variables.

axilmar said:

I don't know about you, but I consider long functions badly written code. I always break up long functions into smaller functions, so, for me, having global variables makes more sense.

In some cases it makes sense to break up functions, but with widget messages your code is separated by if clauses. They're just as readable and just as modular as separate function calls, but you don't need to look somewhere else to find out what they are doing.

axilmar said:

Variables in main are there for the duration of the program anyway, so there is no practical difference with globals.

Go back to the example I just gave about using a function to bring up a dialog. The variables used by that function should not outlive it.

axilmar said:

Just now I can think of these other cases where callbacks are needed:

  • popup context menus embedded into widgets. For example, in a text box, the right click usually leads to a menu with cut/copy/paste/select all commands. With callbacks, it is much easier to create such a menu which goes hand-in-hand with the text box widget. With events, one would need to handle the cut/copy/paste/select all commands and then invoke a function of the widget to perform the action.

  • dynamic population of lists with items.

  • composite widgets like a scroll view with buttons.

  • You don't need a callback to have a context menu. Just make the widget own the context menu and respond to messages from the context menu. The user doesn't need to be involved in handling messages from the context menu at all, because the widget deals with them.

  • A list widget doesn't need a callback to get list values. The user sets the values on the list whenever they feel it is appropriate.

  • A widget that owns a scrollbar doesn't need a callback. It simply handles the messages that the scrollbar sends to it using QueueUserMessage. In a way, QueueUserMessage is the callback.

So the only example you can give me where a callback is necessary is with a textbox where the user wants to have a custom parser. That's fine, but it still doesn't prove that only callbacks should be used to handle messages/events from widgets.

axilmar said:

But when you want to delete widgets, their event types will require management.

You don't need to manage an event type at all. If a widget no longer exists, it will no longer send messages and the message check will simply return false and not be executed.

axilmar said:

Look, I have had quite a few problems with MFC the last 12 years that I am forced to use it. MFC uses event types/notification ids all over the place. I can't tell you how fast they can become problematic.

So what are the typical problems with using widget messages/events then? Nothing you've said about how it causes problems for you has ever caused any problems for me.

axilmar said:

Well, a game or application may have ten times more widgets.

If you use widget messages then I would have 400 if statements, you on the other hand would have 400 functions and either 400 structs or all global variables. I would know exactly where to look when important actions occur, you on the other hand have to check which callback you set for which widget and then find that function across one or many different source files.

axilmar said:

How long is your main()? if it's anything like more 40 lines, you need to break it up. Otherwise, it can quickly become unmaintainable.

Currently, for the GUI test program, the source file is 700 lines long, of which there are 310 lines to declare widgets and 118 lines to deal with widget messages. I still know exactly where everything is, and modifying it is still not a problem. This is real life, functions take more than 40 lines.

axilmar said:

So I guess you never had layout management in your code, where there is a layout widget between the parent and child.

My gui doesn't currently have any layout management, for now that is up to the user. It's very difficult for a function to know how you want to arrange your widgets unless you use some kind of system like HTML. I will have separator widgets that manage two widgets sharing the same space that divides the space between them though.

axilmar said:

What if the events should be handled by a non-widget?

I don't really know what you're talking about here. Top level widgets are WidgetHandlers and they store the messages the widgets queue. After that, do whatever you want to with them.

Axilmar said:

Edgar said:

No child should ever handle a parent's event, and I don't see any reason why they should want to either.

Why not? a child widget in a top-level window may cancel its operation when the top-level window is closed (for example). It's good code to be able to make the child respond to a parent's widget, because this code can be easily reused. It's a more object-oriented design.

In my GUI this is handled by a Shutdown function. If a child widget needs to cancel some operation during Shutdown then it will. It still doesn't require a callback. Generally, it's the responsibility of the parent to tell the child what to do, not the other way around.



Go to: