Since I've finished school and my previous game project, I've had a bit of down time before I start work (not game-dev related, unfortunately). I've decided to try my hand at writing something I've always been a little bit interested in: UI frameworks. Ideally I'd want something that offers a similar user experience to an immediate-mode framework, but with better support for things like styling and animation (and isn't horribly ugly). If completed, I'd want to be able to use it for tools, as well as in-game UI. Anyway, I've got a few loose ideas floating around in my head, so I thought I'd throw them out here to see if anyone had any insight. I have a decent amount of experience using a variety of UI frameworks in the past, but I've never actually implemented one before so if any of this sounds incorrect it's probably because I'm thinking about it from the wrong angle.
My first idea for this was to just use the same sorts of controls (and a similar layout algorithm) to that offered by WPF, but I'm a bit concerned that regenerating the entire UI every frame with that sort of system would be too inefficient. It requires two passes over every element, first to compute required width/height (bottom-up) and second to arrange children (top-down). This is the same design in use by Unreal for its editor. I began working on a simple implementation, but one snag I ran into was in the case of something like a WrapPanel. Say you have the following scenario:
For each column (assuming you're creating a vertical wrap panel), the amount of horizontal space allocated to each element in each column is determined by the amount of horizontal space required by the widest element in that column.
If the WrapPanel runs out of vertical space, a new column is created (regardless of remaining horizontal space).
Images compute required size based on the width and height of the image, but may scale up assuming they remain proportional. (So allocating more horizontal space also requires more vertical space).
In a given wrap panel you have some narrow elements, an image, and a wide element.
Based on individually computed height requirements, it is determined that all elements can fit in one column, with the wide element being the widest.
By allocating more horizontal space to the image, it now requires more vertical space and the wide element can no longer fit in the column, creating a new one.
Now the element that set the width of the column no longer exists in the column, which is weird.
I can think of a few solutions to this problem:
Only allocate height in a vertical wrap panel based on what the element computed. This would effectively disallow elements that must stretch proportionally from stretching at all. This seems like the most sensible solution, but I don't believe this is what WPF is doing (playing around with it, I'm really not sure at all what WPF is doing).
Place the wide element in a new column, and recompute layout for elements in the first column with the width of the next widest element (inefficient).
Place the wide element in a new column, and keep the elements in the first column as they are (might cause some elements to be clipped).
Keep all elements in the same column (will definitely cause some elements to be clipped).
Just force WrapPanels to have uniformly sized elements. I can't think of any use cases off the top of my head where this would be a significant issue.
On the complete opposite end of the complexity spectrum, one UI layout solution would be to simply disallow elements from computing width/height based on contents. Elements may stretch to fill their container, but UI is laid out in a single top-to-bottom pass where allocated width and height for each element are known up front. This may be acceptable for in-game UIs (which are typically very restrained in their complexity), but this breaks completely when faced with lists of non-uniformly sized elements. This could be loosened to a degree by requiring only one axis to be fixed (ie, allocated width is known but height may be computed, or allocated height is known but width may be computed), but this would face problems if you have say a horizontal list (where height allocated for each element is known, but not width) which contains a vertical list (where width allocated for each element is supposed to be known, but not height).
Even if that issue were resolved somehow, I imagine this would create quite a burden on the UI programmer, as they would have to perform a lot of guessing and checking on the requirements for certain elements, which in some cases may not even be possible.
One loosely formed idea I had was to use the layout to generate a linear set of instructions for computing coordinates/dimensions, ordered by the dependencies elements had on one another. So auto-sized elements would generate instructions to have the dimensions of their contained elements computed first, then compute their own coordinates/dimensions, then compute the coordinates of their contained elements. Fixed-size elements would simply compute their contained elements coordinates and dimensions directly. This is very similar to the first approach (and I imagine it could suffer from similar or even worse complexity issues), with the exception that the generated instructions wouldn't distinguish between any sort of bottom-up or top-down pass, which could potentially reduce the amount of redundant work. I haven't thought this idea through very thoroughly, however, so I could be wrong.
Anyway, I'd love to get a second opinion on these ideas, especially when it comes to performance. I wouldn't be opposed to introducing some burden on the application to specify which elements have become invalid in between UI iterations if necessary, but ideally those would be very coarse-grain at the least.
↧