Handmade Network»Forums
5 posts
Here's the thing about UI frameworks...
Edited by eternalStudent on

So I've been writing my own UI framework, as one does in this community, and I wanted to talk about a realization that I came across. But first, I want to address an elephant that unfortunately snuck into the room: RmGui vs ImGui. While the thing I want to talk about might seem like it can be framed as a RmGui vs ImGui, I found the discussion around RmGui vs ImGui to be less beneficial to the point I want to make. Instead I want to frame the issue at hand as a question about whether a "parameter" is either shared, private or static. With this caveat out of the way, let us begin with a simple example.

Arguably UI frameworks are all about composing quads, one on top of another, some of which has text in them. So let us pretend for simplicity sake that our entire app are about different ways to draw and manipulate quads. Imagine an app the has several quads drawn on the screen, each with different sizes and colors. The user can then move individual quads around, resize them, and maybe even change each quad's color. This design almost begs that each quad would be represented by a different instance of an "object", each with position, dimensions, and color.

But what if with instead of being able to change each quad's color, there is only one color, and the user can "press a button" and all quads change their color to red, blue or green? What if they are all the same size and a different button makes them stack horizontally, vertically, or in a grid? This would call for a different design, right? One that has a global layout and color parameters, instead of one per quad.

A third scenario would call for a mixture of the two. If, for example, we would still want the user to be able to drag and resize each quad separately, but also want to change all quads color in a single "press of a button", then each quad would be naturally represented by position and dimensions, but the color would be set globally.

For completion sake I would also note, that some parameters are static, say some quads have rounded corners, but their radius is fixed at compile time. In this case it matters very little, to this discussion, whether we store the radius for each quad, have a global shared parameter, or just keep it as part of the code with no dedicated variable in memory.

When writing a generic UI framework I accidently locked myself into a specific tradeoff, one that is useful in certain cases, but detrimental in others. I find it difficult to find a compromise that would satisfy my needs.

Simon Anciaux
1356 posts
Here's the thing about UI frameworks...
Edited by Simon Anciaux on

In the UI framework I'm using, each rectangle has a "style id" property.

If it's set to a non zero value, the colors (rectangle and text, depending on the state normal, hot or active) will come from a global style array.

If the style id is zero, it means that the rectangle has filled its own style and will use that instead of a global one.

So if several rectangles need to share the same style, they use the style id to use the global style array. Any modification in that array will be visible on everyone of those rectangles.

Size and layout is treated a bit differently.

In my experience sharing the size of rectangles isn't a common thing. In my framework (heavily inspired on Ryan Fleury's articles) a rectangle can specify that one (or both) of its dimensions should be copied or based on another rectangle. In practice that just means that a rectangle can store a pointer to another rectangle and the sizing part of the UI knows to use the pointed rectangle dimensions.

Layout changes are in my opinion more involve than a rectangle property. I have a horizontal/vertical layout flag that I can set on rectangles, but the purpose of that is not to be able to change the layout dynamically, it's just to let the sizing and positioning part of the UI know what to do with the rectangles.

If I had to change the layout at runtime, I would create different codepaths that produce different layout. Letting most of the responsibility to the user of the API.

That said I have a thing I'm trying (not sure yet if it's good, but it seems to work OK although the performance aren't great at the moment). I'm trying to display HTML pages. It can contain a paragraph and in the paragraph a link, then the rest of the paragraph. As a user of the API, I want to just say:

rect( text... );
if( rect( link...) ) { ... }
rect( text... );

And have the system figure out the layout, text wrapping etc. I did that by having those rects be "instructions" and have the size part figure out the layout. Figuring out the layout means that the sizing part will add rectangles into the tree, and that the instructions size will not be computed and will be ignored in the positioning and rendering.

A final though: I would suggest not basing your UI system on examples, but do it for a real application. This was my third time making a UI system, and I knew to use real things. So I made a mock-up application that reproduce the layout of the application I wanted to port to the new UI system. It helped (a lot), but when I did the actual port, so many things I hadn't though about appeared.

Mārtiņš Možeiko
2575 posts / 2 projects
Here's the thing about UI frameworks...
Edited by Mārtiņš Možeiko on

But what if with instead of being able to change each quad's color, there is only one color, and the user can "press a button" and all quads change their color to red, blue or green? What if they are all the same size and a different button makes them stack horizontally, vertically, or in a grid?

All of these things are easily handled both in rmgui and imgui approaches. Don't forget that rm & im is only decision how API looks, not the implementation details or feature set. Those are completely orthogonal concepts.

You can literally make immediate API over Qt if you want. Here's a paper for 1993 showing how to do that:

Differential Evaluation: a Cache-based Technique for Incremental Update of Graphical Displays of Structures, author has demos in https://sourceforge.net/projects/dyndlgdemo/files/ and https://github.com/MikeDunlavey/AucUI using win32/MFC controls.

For changing colors for the same set of components in rmgui applications you typically use some kind of "stylesheets" (like css). You set what style the specific components are. And then just change the color of specific style. And rmgui takes care of using this color. For imgui, you just reference the color you want to use in each api call, and/or simplify that with push/pop approach like dear imgui does.

As to layout - that is handled trivially with rmgui by setting property of "layout" component/container. It can be enum Horizontal, Vertical, etc.. and you just change it. For imgui that can be simple api function argument and you pass whatever you want every time you do the layout.