Designing a GUI system, hints appreciated.

Started by
100 comments, last by Shinkage 13 years, 10 months ago
Quote:Original post by MoundS
No problem. I don't blame you for not wanting to write the RMGUI code for that example. :-p


My eyes just skipped over that sentence. The problem is, there are a number of different ways to write an RMGUI, do you want me to pick the one that best matches that exact situation you gave an example of above?
Advertisement
struct contact{    std::string name;    std::string phone;};class contact_window : public ui_window<ui_model<contact> >{     contact_window()     {          this->mode("view")               .insert("name", new ui_text(ui_bind_data(&contact::name)))               .insert("phone", new ui_text(ui_bind_data(&contact::phone)))               .insert("edit", new ui_button("Edit Mode")->connect(bind(&ui_window::set_mode, this, "edit")));          this->mode("edit")               .insert("name", new ui_text_edit(ui_bind(&contact::name)))               .insert("phone", new ui_text_edit(ui_bind(&contact::phone)))               .insert("view", new ui_button("View Mode")->connect(bind(&ui_window::set_mode, this, "view")));     }}...contact_window *window = new contact_window;window->bind_data(new contact);


Obviously, a UI design like this would be doing a whole lot "behind the scenes," like handling the binding of class data members to the UI and such. As an interesting note, I would argue that a system like I present here is MUCH more strongly reminiscent of functional programming than your IMGUI system. I mean, look at all those parentheses and tell me you don't think lisp! :)
Well, we should've done this much earlier. So yeah, I agree that an RMGUI with that level of data binding is much closer conceptually to IMGUI, though I think you need a more powerful mechanism that just "mode" to capture everything you can do with if statements in an IMGUI. For example, what if you have some text that's only displayed when (a && b || c) is true? Or what if you want to display the sum of two numbers in your model? Or what if you want to only bind a subset of the items in a list to a part of the UI?

I realize you could do all that with creative use of callbacks and whatnot, but at least in C++, callbacks aren't quite as convenient, so I'd rather use what I'm describing. But as far as I can see, you're right that at that level it's just a syntactic difference rather than a conceptual one. I would actually hesitate to even call such a system "RMGUI", because the GUI library isn't really "retaining" much other than a functional definition of the GUI and how to bind it to the model data, which is basically what the IMGUI UI function represents.

What I was trying to get at all thread were the advantages of this method over GUIs where you have to manually create/destroy/update widgets as your app state changes (because the library doesn't understand the relationship). That's what I've been calling RMGUI, and most of the GUI examples I find online seem to fit that mold. I wonder what percentage of GUI programmers actually work with something like what you're describing.
I've developed a few in-house GUI systems like that, so I know at least a few people work with such a thing :)

Quote:Original post by MoundS
I realize you could do all that with creative use of callbacks and whatnot, but at least in C++, callbacks aren't quite as convenient, so I'd rather use what I'm describing.


If you really wanted to get fancy about it, you'd use this. Further cementing the similarity to functional programming :)

EDIT: Or wait until C++0x is finalized and widely supported, or use a different language that natively supports lambda expressions.

Quote:Original post by MoundS
I would actually hesitate to even call such a system "RMGUI", because the GUI library isn't really "retaining" much other than a functional definition of the GUI and how to bind it to the model data, which is basically what the IMGUI UI function represents..


We have very VERY different definitions of what retained mode implies in this context then, which is why I was thinking to chalk it up to fundamental disagreement. This kind of declarative GUI system is almost the exact opposite of what I would call an immediate mode system, at least using any traditional definition of "immediate mode" that I've ever been familiar with as a programmer.
Quote:Original post by Shinkage
If you really wanted to get fancy about it, you'd use this. Further cementing the similarity to functional programming :)

EDIT: Or wait until C++0x is finalized and widely supported, or use a different language that natively supports lambda expressions.
Or if you really want to blow your mind (or your sanity), you'd use this.
Quote:This kind of declarative GUI system is almost the exact opposite of what I would call an immediate mode system, at least using any traditional definition of "immediate mode" that I've ever been familiar with as a programmer.
A lot of this confusion seems to be based in an erroneous (albeit widespread) misconception about how the OpenGL 'immediate mode' works. Many programmers believe that immediate mode commands are submitted directly to the GPU, and take immediate effect.

This has not been the case for a *very* long time (if indeed it ever was). Immediate mode commands are queued up in a buffer in main memory, rearranged, optimised, before finally (usually when the programmer swaps the buffers or calls glFlush) being sent en masse to the video card in a single transaction.

By this definition, immediate mode has nothing to do with when (or how) data is submitted, but purely with where the state is stored: 'retained' within the library, or held 'immediately' by the application.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by swiftcoder
By this definition, immediate mode has nothing to do with when (or how) data is submitted, but purely with where the state is stored: 'retained' within the library, or held 'immediately' by the application.


Agreed, and in the setup I presented there, ALL state (i.e. UI state) is retained by the library.
Well, you're not really retaining what a traditional RM system would retain. An RM system retains the list of scene elements (widgets, shapes, etc) from frame to frame. In your system, that list is dynamically computed each frame using the model data and the bindings, just as an IM renderer would look at the model data and produce a fresh list of shapes each frame. In fact, if you designed a graphics library using your way, it wouldn't really retain anything for a dynamic scene.

Maybe IM and RM aren't such great terms for a GUI API anyway, since everything gets kinda blurry when you introduce things like bindings/callbacks or my get_state. What's important is really whether dynamic parts of the UI are specified as functions of the underlying app state, and we clearly agree on this part. The reason people use the IM term for that is because it's in the spirit of IM graphics APIs, but like you said, declarative is probably a better term for it.
Quote:Maybe IM and RM aren't such great terms for a GUI API anyway, since everything gets kinda blurry when you introduce things like bindings/callbacks or my get_state.


Exactly what I've been saying for the last few pages! :P Your "IM library" is very similar to my "polling-based RM library" (not what I gave an example of, it would be a different design which I do not generally favor). I think the real points of contention are:

Unimportant -- Mostly Syntax
1) How widgets are specified
- YOURS, every frame, in a procedural manner (i.e. this frame, I'm drawing this, then this, then, if X, that), allowing the library to construct the actual display list based on the sequence of UI procedures called that frame (probably caching data between frames for optimization)
- MINE, at initialization, in a declarative manner (i.e. this is a list of the things that you will be drawing, and when you will be drawing them), with the library maintaining that list and internally deciding what to render and when

2) How events are handled
- YOURS, polling-based, every frame you're essentially asking, "Was this button pressed? How about this button? And this button, what about it?"
- MINE, event-based, at initialization I connect widget events to their handlers, and the UI library fires them off as needed based user input and its internal model of the current structure of the scene (I could see an IM-style system doing it this way, which I would personally prefer)

Most Importantly
3) How much the UI library "knows"
- YOURS, it knows what's being drawn this frame, and could obviously retain a cache of information regarding what's previously been drawn
- MINE, in addition to what your UI system knows, mine also knows what might be drawn in the future

It's #3 that's the real advantage here. If, for example, a scene is declared in such a way that my UI system can determine it will not change in the future, it can optimize the drawing directly to a static display list that it simply passes to the renderer directly every frame in a single command, and never looks at again.

With an IM-style system, I would argue, this isn't possible*, because the very POINT of an IM-style system is that the scene description is built up procedurally by the library user at run time, which obviously is not something the library has a direct view of.

* It could be possible with a reflective language where the GUI system is capable of actually reflecting into all of the calls the application can make to it--man that would be quite the thing to design! EDIT: A lot easier to do it this way if your UI system is script-based.
Right, and that's basically what my macros are, a way for the library to reflect on parts of the UI function (control statements in this case). I could add other macros if I wanted, to deal with static sections of code, but the user would have to identify them manually, whereas you could just check for static sections automatically.

We both want the same thing - to specify a parameterized definition of the UI. I specify it as real code and have to use macros so that my library can analyze the code. You specify it as data, so it's really easy to analyze, but you're stuck with the fact that representing code as data in C++ isn't as easy as it should be. We're working in a language that's less than ideal for what we're trying to do, and we've arrived at different tradeoff points. In a language like LISP, we probably would've designed the exact same thing!
Quote:Original post by MoundS
We both want the same thing - to specify a parameterized definition of the UI. I specify it as real code and have to use macros so that my library can analyze the code. You specify it as data, so it's really easy to analyze, but you're stuck with the fact that representing code as data in C++ isn't as easy as it should be. We're working in a language that's less than ideal for what we're trying to do, and we've arrived at different tradeoff points. In a language like LISP, we probably would've designed the exact same thing!


Well, I guess we were making the same thing all along. I guess this just goes to show exactly why talking about RM and IM is a bad idea in the first place, because it seems that one it comes down to it, sophisticated implementations of either "style" really boil down to a difference only in minor bits of syntax it seems.

EDIT: Although this discussion has given me a really cool idea for a prototype of a UI system that mixes both techniques. As a hint, think Boost Spirit but for UI specification.

This topic is closed to new replies.

Advertisement