In Runt's UI I had a quite specific requirements on how I needed the information about the focused view to be stored. The UI combines retained and immediate UI. That and some specific features of Runt make for quite specific requirements that I didn't anticipate at the beginning.
The UI tree is a retained structure kept in memory with Views having stable pointers at all times. Immediate mode is used to do update
and draw
that happen every frame for every view (in tree order). This results in a requirement of having immediate access to as many properties as possible, focus being one of the top ones.
Problem
Originally I just stored the pointer of the focused view to the window struct, and made a contract that anytime you want to change the focus, you need to call view_focus(View* view)
. First part didn't last long, though.
Once the Shell entered the game, I needed the shell to know which view was being targeted with commands. So if the target view is a code editor, I can ask it which commands it implements, and am able to call the command's function with the correct context information when it's executed.
Not only that, if you pay close attention to well made desktop applications, you can see the focus doesn't reset when you're activating tabs or windows. That would happen if you only kept one pointer to the focused view.
Solution
To illustrate the solution say you have following view structure:
Window
Root
- Panels
- Panel 0
- Code Editor 0
- Code Editor 1
- ...
- Panel 1
- Shell
Say you focus Code Editor 1
.
One solution would be to store a focus chain: Window.focused = Root
, Root.focused = Panels
, Panels.focused = Panel 0
, and finally Panel0.focused = Code Editor 1
. However this way, most of the information stored is useless, and to get to the focused view in a certain UI branch you need to recurse every time.
My solution is to store the pointer to Code Editor 1
on every of it's parents Panel 0
, Panels
, Root
and Window
. This way, I know that when Window is activated, I need to set the focus to Code Editor 1
, and be done with it. It of course needs some careful update when the focus changes, or the view is removed, but these actions have very low frequency (compared with accessing the focused
information).
Now imagine I focus Shell
, I'll write the value of focused
in parenthesis after each UI node:
Window (Shell)
Root (Shell)
- Panels (Code Editor 1)
- Panel 0 (Code Editor 1)
- Code Editor 0
- Code Editor 1
- ...
- Panel 1
- Shell
Now it is very easy to tell what is the context for commands executed in Shell
. It just needs to look at focused
property of Panels
sibling. Additionally I can restore focus of any branch of the UI very easily.
When I recall the code acrobatics I had to do with third party UI libraries in prototypes I did in the past, it almost makes all the ready-made libraries not worth the time. Runt's UI core is quite tiny (cca 1k lines including layouts) and does exactly what the application needs.