Hello everyone!
I decided to learn ncurses a few days ago so I can do a project I’ve been thinking about lately, and in the process I figured since I’ll go to the trouble of solving a bunch of the problems with writing relatively complicated TUIs with ncurses, I might as well generalise the solutions into a library or two for future me and anyone else who will find them useful.
Anyway, enough introduction, heres what I want to do with the library:
I want it to dynamically manage size and position of ncurses windows. This means windows should be able to be added, and removed (just those for now, I don’t know if I’ll implement resizing windows from the perspective of someone using the library to have that functionality), and have the size and position of the windows update automatically.
Initially I thought a binary space partitioning tree situation might be a good way to tackle this, but I want it to be a bit more flexible. For example, the dev using the library should be able to set certain windows to behave differently than leaves in a bsp (think persistent status bar at the bottom of the application, taking up the entire width but only a couple lines in height for example, or toggle-able file explorer on the left/right of the application like neovim’s NERDTree).
How would you guys go about designing something like this, while still keeping the code relatively simple and efficient?
Here is a badly drawn diagram of what I mean:
Steps here refer to how someone using the library might go about creating each element (window) of their UI in their own project.
Until step 4 a bsp tree makes sense, but after that when I want to add a window which doesnt conform to the rules of a bsp tree, it gets a bit difficult.
Also, Im thinking there should be some way for windows to have some idea of their neighbours so that when a change occurs, they can adapt to it.
Anyways, essentially I’m looking for your thoughts on how you would go about implementing something like this.
Much appreciated!
BSP Tree (with custom nodes).
With a vanilla BSP-tree you can accomplish your diagram. Simply reordering your splits can do it by making the footer and main content areas first. Better approach is to support splits on non-leaf nodes. In the example below just split the root where all its children go to the new top node and a new bottom bar leaf node is created.
Root (split: vertical, ratio: 0.6) ├── Left child (Window A) └── Right child (split: horizontal, ratio: 0.5) ├── Top child (Window B) └── Bottom child (Window C)
To access neighbors you’ll need your nodes to track their parents (double linked). Then you can traverse until all edges are found. Worst case its O(height+num neighbors that exist) if I am remembering.
Depending on how efficient you want it to be, there are speed ups. It has been awhile, but I do remember keeping the tree as balanced as possible makes the search around log(n). Each split node keeping an index of all children in its sub-tree also reduces how much traversing is needed when you need all children after a split.
Can get a little complicated but it is doable. That said, how many splits will a TUI have? This may be preemptive to do.
Custom nodes is where you support some patterns that could use further optimizations. Tables that will always be a grid. Tab bars that are a 1xn grid could be a further specialized node.
This is all about layout. Fixed/Dynamic width/height windows, padding and margins, borders, are all render processing and don’t effect the layout (unless you want reactivity). By that I mean you have windows that will split differently when the viewport is portrait or landscape and it dynamically adjusts to the window size. Sometimes with different “steps” like a square viewport may be different from both portrait or landscape or 4:3 could be treated different from 16:9.
TUIs are not my day job but I’ve made a few in my day. Above are just my opinions from experiences. There is no “right” answer but hopefully some of this helps your journey.
TypeScript is my day job and using a custom JSX Factory makes it pretty easy to define HTML-like interfaces for devs that can support mixing layout, render attributes, content, and app logic.
Explicit BSP splits:
<Split type="vertical" ratio={0.6}> <WidgetA /> <Split type="horizontal" ratio={0.5}> <WidgetB /> <WidgetC /> </Split> </Split>
Custom nodes:
<Container> <TabBar> <Tab>Tab 1</Tab> <Tab>Tab 2</Tab> </TabBar> <StatusBar /> </Container>
Not sure your stack but throwing it out there as something I’ve used successfully.
Thanks! Ill take some time to look into what you said and try to really understand it, but if I need some help I’ll ask you. As for how many splits a TUI might have, I dont know because I need it to be quite general so that others can use it for their own purposes. Me personally and for this specific project, Ill mostly need a bunch of columns (like blue and green in step 2) as well as the “special” windows, for my application. I’m trying to make a trello/kanban style app with ncurses and C.