Designing a GUI system, hints appreciated.

Started by
100 comments, last by Shinkage 13 years, 10 months ago
Quote:Original post by Shinkage
Quote:Original post by apatriarca
if (doWindow(ui_state, INVENTORY_ID, "Inventory", ..., event)) {    handle_inventory_update(ui_state, event);}


Okay, and if we continue the chain of program logic down we'll end up having a big switch statement based on what event it is, that forwards the details further along to an even more fine-grained bit of update logic. An event-based system avoids all that by just letting you bind your logic directly to the event that triggers it, and then the library handles everything for you; no calls to handle_inventory_update, no need to figure out which event it was and keep passing it along, it's all direct and transparent.

Sorry, I don't follow you on this. I think event-based systems add a lot of noise and scatter GUI logic in a lot of different functions. I find them confusing when the GUI is sufficiently complex. Maybe I'm simply not able to design good event-based systems. Anyway, calling the right function when the corrent event is triggered isn't more difficult than binding that function to the event in the first place. Moreover, big switches aren't really required since you only have a very limited number of events for each widget. I think it's possible to make rougly equivalent event handling codes in both type of APIs with similar effort from the user.
Advertisement
Quote:Original post by Shinkage
This is why I'm claiming that the debate is actually event-based vs. polling-based, not IM vs. RM.


Ok, yes I agree with that 100%.

Quote:
This is precisely my point. The kind of sophisticated IMGUI libraries being proposed as sufficiently functional to provide a complex user interface are conceptually no different from RMGUI libraries.


That's what I was thinking. My initial reaction when I first read about this the other day was to think to myself, "ok, so what do I gain by polling over using events?". With events I check state and fire the event when it changes. I may be polling to check state of course, but that should be hidden away from the GUI. Is an event system difficult to implement in any case? No, not really. So why do I need to directly poll?

In terms of advantages/disadvantages, the argument seems to boil down to, "I don't have to learn how to use or create an RMGUI!". Well, that's nice in a way I suppose :p.
Quote:Original post by apatriarca
Sorry, I don't follow you on this. I think event-based systems add a lot of noise and scatter GUI logic in a lot of different functions. I find them confusing when the GUI is sufficiently complex. Maybe I'm simply not able to design good event-based systems. Anyway, calling the right function when the corrent event is triggered isn't more difficult than binding that function to the event in the first place. Moreover, big switches aren't really required since you only have a very limited number of events for each widget. I think it's possible to make rougly equivalent event handling codes in both type of APIs with similar effort from the user.


Okay, I'll expand it into some naive code examples. Polling-based:
void update_ui(){   while(doWindow(ui_state, INVENTORY_ID, "Inventory", ..., event)) {      handle_inventory_update(ui_state, event);   }}void handle_inventory_update(state, event){   switch(event) {      case ITEM_DELETED:         handle_item_deleted();         break;      // etc...   }}

Event-based:
void setup_ui(){   Widget *window = create_window(ui_state, ...);   window->connectEvent(ITEM_DELETED, &handle_item_deleted);}

At least, that's how I'm imagining the nature of the short code example you gave, and an event-based analogy. To me, event-based systems make it a lot clearer to determine by inspection what the relationship is between UI logic and UI presentation.
I still not get the difference. What's the real difference between
void update(state, event){   switch(event) {      case EVENT1:         handle1(state, event);         break;      case EVENT2:         handle2(state, event);         break;      // ...   }}

and
window->connectEvent(EVENT1, &handle1);window->connectEvent(EVENT2, &handle2);window->connectEvent(EVENT3, &handle3);// ...

Ok, the switch is a little more verbose but you can refactor the code to make it looks more like the other code. I don't see any advantage of the event based system in this case.
Quote:Original post by apatriarca
Sorry, I don't follow you on this. I think event-based systems add a lot of noise and scatter GUI logic in a lot of different functions.


I don't understand your point here with respect to scatter:

/* Virtual */voidButton::OnMouseOver(){}/* Virtual */voidButton::OnMouseDown(){}/* Virtual */voidButton::OnMouseUp(){} <--- relation between state change and code is explicit.


Another bonus here, I can derive a new button type that behaves slightly differently when OnMouseOver is called. Of course you can do this with IMGUI too, because you can do anything you like if you write enough code. We're using Turing Machines after all :p.

I would prefer the above to:

voidIMGUI::DoComplexGuiComponent(a, b, c, d, e, f, g, h, i, j, k, l){   if(a)      if(c)      else   else if(b)<---- cyclometric complexity is bad.}


Ok, so to the benefits: I have a system and I want to display a screen of GUI components. I don't have any particularly difficult or tricky data transformations to do and I want it done this afternoon before I go to the pub. Solution: use IMGUI!
to apatriarca:
Let's say you have 5 windows w1 to w5 and user clicks on the close_window_button of w5.
- In polling methods, all 5 windows would have to poll the GUI system to know if they have to close.
- In eventing methods, no polling is done by any window and only w5 receives an event from the GUI input system telling it to close.

That's the main advantage of eventing over polling (Function calls overhead reduction)
That's an interesting point Burnhard about cyclometric complexity. The UI code the user deals with directly has fewer control flow paths in an event-based system, since it's the library that handles control flow. That can be a really important thing when you're trying to debug something or read old code by tracing control flow paths.
First of all, in the last few posts I have written I was discussing about the differences and advantages of event-based systems against poll-based systems. This has NOTHING to do with object oriented versus procedural programming. It's quite easy to make either a procedural event-based system or an object oriented poll-based system (and there are a lot of examples of both). This has also NOTHING to do with the doWidget paradigm most IMGUI tutorial teach.

Quote:Let's say you have 5 windows w1 to w5 and user clicks on the close_window_button of w5.
- In polling methods, all 5 windows would have to poll the GUI system to know if they have to close.
- In eventing methods, no polling is done by any window and only w5 receives an event from the GUI input system telling it to close.

That's the main advantage of eventing over polling (Function calls overhead reduction)

Since most events have effect on very few widget each time they are triggered, it is quite important to have some mechanism to query all the widgets who must handle the current event. If this mechanism is implemented (in the library or in the application), then you can write something like that:
Window activeWindow = windows.getActiveWindow(event);activeWindow.update(event);

This is actually what your event system probably do. The code looks quite simple and there is no overhead compared to your event system (quite the opposite actually).

Quote:
I don't understand your point here with respect to scatter:

/* SOURCE CODE ELIMINATED... */

Another bonus here, I can derive a new button type that behaves slightly differently when OnMouseOver is called. Of course you can do this with IMGUI too, because you can do anything you like if you write enough code. We're using Turing Machines after all :p.

Well, I was actually thinking about a specific API. Your example is fine, but you can do exactly the same in a poll-based system. The only difference is that the code which calls that functions is explicit instead of being hidden in the library.

Quote:
I would prefer the above to:

voidIMGUI::DoComplexGuiComponent(a, b, c, d, e, f, g, h, i, j, k, l){   if(a)      if(c)      else   else if(b)<---- cyclometric complexity is bad.}


Why do you think a polling system update function should be written like that? People learned to write clean and readable code long ago. You can clearly use structures or classes to reduce the number of parameters and you can break your logic in several functions if it make sense. Bad code is just bad code, whatever paradigm you are following to write it.

Quote:That's an interesting point Burnhard about cyclometric complexity. The UI code the user deals with directly has fewer control flow paths in an event-based system, since it's the library that handles control flow. That can be a really important thing when you're trying to debug something or read old code by tracing control flow paths.

I have already discussed about the quality of code. I think you are really overstimating the additional complexity. You surely need some additional logic, but you also have a little more flexibility and control in how your code handle the events.

Anyway, I have used several event systems in the past. Some were really good and were a pleasure to use, others were really bad. I think you can write a good or bad API using both methods, but I prefer to have control over the application and GUI main loop.
Quote:Original post by Shinkage
Let's me restate it another way. Tell me what you think of this claim: all sophisticated IM libraries are essentially RM libraries accessed through an IM facade.

Well, I wouldn't exactly word it like that, but I agree with the spirit of what you're saying. But the most important part is the API, not what's underneath.

Quote:This is why I'm claiming that the debate is actually event-based vs. polling-based, not IM vs. RM.

OK, as long as you understand that those are independent issues. For example, my IMGUI library is event-based.

Quote:This is precisely my point. The kind of sophisticated IMGUI libraries being proposed as sufficiently functional to provide a complex user interface are conceptually no different from RMGUI libraries.

They're very different conceptually. With RMGUI, the library user has to manage the creation, destruction and updating of the dynamic elements of the UI. With IMGUI, the library takes care of it automatically. IMGUI also lends itself to thinking of the GUI as a function of the app state, which I find much cleaner conceptually.

Quote:Original post by Burnhard
There's a structural difference (you don't like OO and you can't be bothered to build a robust observer model). If you store it elsewhere, you're already breaking one of the supposed advantages of using IMGUI in the first place (no state). Except the difference is that the GUI state is no longer stored close to the component that uses it. We're peddling backwards and passing by a couple of decades of good OO practice in the process.

In my library, a widget function can just call get_state<bool>(ctx), and the library context returns a reference to a boolean that is associated with that widget. No one outside the function needs to know that the widget has state. All the associations are handled by the library. Is that "close" enough for you?

On another note, you seem to think that anything that's not OO is a mess of unstructured code where functions all have 15 parameters. There's a lot more to programming than just OO. Ideas like abstraction and encapsulation are not unique to OO. OO is a tool for dealing with state management, but state management is something you should avoid doing in the first place when it's not necessary. In general, if you have an interface that can be specified in pure functional terms, or a component that can be implemented naturally in a purely functional way, you should do it that way, as it's simpler and cleaner than the stateful alternatives. IM interfaces are conceptually very close to purely functional ideas.

If you're only familiar with OO, I would encourage you to read (or watch) SICP, and "Concepts, Techniques, and Models of Computer Programming." The latter especially will teach you a lot about other paradigms and how they all fit together. Or just go and learn a language like Haskell. It will make you a better programmer, regardless of what language you normally use.
Quote:Original post by MoundS
They're very different conceptually. With RMGUI, the library user has to manage the creation, destruction and updating of the dynamic elements of the UI. With IMGUI, the library takes care of it automatically. IMGUI also lends itself to thinking of the GUI as a function of the app state, which I find much cleaner conceptually.


But IS a GUI a function of app state? The two are certainly related in part, but a VERY large portion of the actual functionality of a GUI has nothing whatsoever to do with app state. Where widgets are drawn, how they're drawn, whether they're even drawn at all, all has generally no relation at all to basic app state. In fact, I would argue that an RMGUI does a much better job of only requiring interaction with those elements of the UI system that are relevant to app state.

Quote:Original post by MoundS
In general, if you have an interface that can be specified in pure functional terms, or a component that can be implemented naturally in a purely functional way, you should do it that way, as it's simpler and cleaner than the stateful alternatives.


This is where you lose me completely, because the kind of IMGUI you seem to be proposing--in fact the kind that everybody's examples demonstrate--require that the user be responsible for managing MORE state than a traditional RMGUI, not less. On top of the fact that I it's somewhat absurd to cleave to any kind of hard and fast rule like "state is bad." Like any task in programming, it just depends on what you're doing with it.

This topic is closed to new replies.

Advertisement