What's the right UI framework pattern?
I'm making a UI system (mostly for fun and to learn), and I'm trying to figure out the right pattern for it.
I want to use it for games and editors. So I was thinking some type of immediate mode GUI since in game UIs, it doesn't make sense to have events when states change, and it is pretty high performance, especially when moving position of elements a lot. And simple.
Immediate mode pattern has a few issues though, there isn't really a good way to have an editor for building the UI visually. And if there was some type of editor, doing bindings would require boxing for each value (at least I can't think of another way without boxing). It is difficult for extensions to modify the UI.
For retained mode patterns, if you do something like MVVM it is heavily event driven, which isn't great for game UIs and has lots of boilerplate, also rather complex to make. But is quite easy to do a visual UI editor/builder for.
Retained mode GUI also generally are less performant when moving elements and such.
I looked at like a MVU pattern from Uno, but that seems to have a lot of the same issues as MVVM had, with performance and events.
I feel like there are other patterns, one that would fit well with what I want. But I'm having trouble even finding different patterns to look in to.
To summarize what I am looking for:
- High performance updates.
- Mostly not event driven.
- Easily modifiable from third-party.
- Buildable from an editor.
Any suggestions?
18 Replies
Make a data oriented layer above a regular immediate mode GUI using which you'd generate the UI from an editor.
Modifiable by third party — what does this mean?
Not sure I follow what you mean exactly.
Modifiable by third partyLike with say, HTML you can get the elements of a page and then insert new child elements or modify them.
It need to be data oriented then, there's no other way
It means that you put the widget data to draw in an array and then send them to the system
Not like
if (Button(x, y, ...)) ...
but like
I don't see it being possible unless you have something like this
Or an array of Buttons
So people could modify what to day by adding to the array
This can be added on top of your main systemAhh yeah yeah. That makes sense. I was thinking about if each widget was a struct, and re-create them each 'frame'/'render'. But that feels like it would be potentially pretty memory heavy, and can't think of a good way of doing it without using interfaces, which would degrade the performance more.
At least that's how I see it
Still if you're going to have an editor, you need it to be able to serialize and deserialized the widgets
Unless you want it parsing actual draw code, you need a way to store instructions as data
So with an editor it's kind of a necessity to have a layer like this
If they are just structs that shouldn't be an issue.
What do you need interfaces for?
If I did the structs, they way I see it I would at least need something like an
IDraw { void Draw(DrawContext ctx); }
, along with a IWidget { IEnumerable<IWidget> CreateChildren(); }
,
With IDraw
handling drawing of 'primitive' widgets, like text, rectangle, circle, etc.Well that's just a regular object oriented system then
For performance and flexibility you could use an ECS
This feels like a great use case for one
Yeah... I know... I still struggle fully grasping how to design data oriented systems at times
I mean you probably could just pool the objects
And have an object oriented design here
But it will be slower
I was thinking about doing it like an ECS, but considering A, things need to be computed in a specific order, and B, I can't see how to do the layout.
Makes sense to do a component-per-layout type, but when you go to evaluate a entity, how do you know which layout it has? Can't really do all of the VerticalStack layout components and then all the Grid layout components since the child entities sizes rely on the parents*
I was thinking applying the layout to the base data before drawing, and drawing out of order in a loop (because you've saved the context)
I've never done something like this
Yeah I guess you'd still need to go by layers somehow
Perhaps you could order things in the arrays by layers
So you'd have to jump around less
And store the layer
By layer I mean like how nested it is
Right, but to do that would need to have like a
ILayout { void Layout(Size size);
} interface that components/widgets implement. Which kind of is not great for performance on structs.You don't. You'd just store each type in a separate array
Store the data of all VerticalStacks in a single array
Then when you're going to process the vertical stacks, you know all the items in the array are vertical stacks
So you will be able to call the concrete function for the vertical stack
ILayout { void Layout<T, U>(ReadOnlySpan<T> dataArray, Span<U> layoutArray) }
T = vertical stack
U = the layout struct that T produces
The size is going to be precomputed and stored in the T
Precomputed from the other layers
The upper layers i guess
So yeah I guess there's going to be another step
First make a layout from all things in a layer
Then apply it to the next layer
Then move on to the next layer
Idk, that's just how I see itOh that is different than what I thought you were saying. But I either way I see what you are saying.
But then the question is I think is if the extra complexity is worth the performance. My thinking originally with the structs was to re-create them each frame. But I guess that would really be not the best performance wise probably.
Idk one would have to try and measure it
Also I just thought of a thing
If you could make layouts be like a one fixed thing
Only determined by the data
They could stay in a single array
Yeah guess so. The thing I do like about normal IMGUI is not having to manage the state of the elements, like if you select a 'tab' you don't have to 'unselect' another 'tab'.
I don't think so, as I want to be able to support like radial layout or stacks etc.
I was trying to look at what thing like Comet , ReactMAUI and ReactJS do, but truth be told I didn't fully follow and didn't seem to fit.