Published on to joshleeb's blog
G’day!
Exploration towards building Ica (a GUI code editor) has continued in October. Having figured out a rendering strategy at the end of last month, the next step has been to design a framework/engine that sits atop the rendering layer and deals in terms of element trees, event listeners, and effect observers.
At this level we don’t have to think about how primitives are drawn to the screen, nor how to create a window and receive events from user interactions. Our high level responsibility is to define the lifecycle for abstract elements and figure out how state is managed. What this boils down to is an overwhelming amount of pieces that have to fit nicely together.
No doubt writing a GUI system is difficult and complex. Writing a GUI system in Rust also adds another degree of complexity as these systems tend to build upon mutable, bi-directional trees which are non-trivial to implement with the restrictions of Rust’s borrow checker.
Thankfully there are many GUI frameworks being developed in the Rust ecosystem. Raph Levien has catalogued many of them with articles that break down their architectures and tradeoffs, such as Advice For the Next Dozen Rust GUIs. As I am going down the path of a native (i.e: non-electron), retained/hybrid mode GUI that isn’t based on the Elm architecture, the GUIs I’ve been looking at this month have been Xilem, Flutter, and GPUI.
GPUI has been of particular interest as it’s the framework being developed by Zed Industries for their Zed GUI code editor that is already released and being used in the wild.
One of the structures employed by GPUI as a solution for mutable trees is
their EntityMap
where references between nodes are IDs rather than smart
pointers. This is similar to one of the more common approaches for trees in
Rust, where all nodes are stored in a Vec
and referenced through their
position. What’s new to me in the EntityMap
is this lease-pattern where
ownership of a value is acquired by removing it from the tree. The value is
then mutated and added back into their previous position. Of course this
assumes single-threaded execution but within the event loop of a GUI framework
that is perfectly fine.
The EntityMap
, and the effect system built on top of it, are topics I might
be writing about more (as my backlog of posts continues to grow). In the mean
time the Zed folks discuss this approach to data flow in Ownership and Data
Flow in GPUI.
Inspired by GPUI’s EntityMap
, I’ve added a conceptual implementation of my
own here that replicates both the EntityMap
and the effect
system. As with many of my concept pieces, the API and the implementation
needs a lot of cleaning up but the idea is there.
Coming back to the overwhelming nature of building a GUI, part of this overwhelm is that working out the details for most subsystems depends on the details of most other subsystems. For example knowing how you’re going to dispatch keyboard events requires (for one) being able to register listeners, which depends on the effect system, which depends on how entities are stored and referenced. This was less of an issue when I was exploring rendering which is fairly isolated in this dependency graph.
So, I’ve decided to shift to a new approach in the progression towards building Ica which is to build a GUI replica of antirez’s Kilo terminal code editor. Kilo is an editor with only a few core editing features and keeping this surface area small will allow me to focus on the GUI systems rather than the editing features.
I’m going to call this project Mega as it will take much more than 1k LoC, but should be less than 1M. It’s main purpose is as a precursor to Ica that will allow me to make more mistakes with building my own GUI system and build up an intuition for avoiding them with Ica.
As a secondary goal, and depending on the complexity at the end, I hope to write a tutorial series showing how to build your own hardware-accelerated GUI code editor from scratch without Electron, without Skia, and without an established framework. I’m thinking this will be a similar style of tutorial to Build Your Own Text Editor for Kilo and Build Your Own Text Editor in Rust for Hecto.
The progression of building Mega has, so far, been very productive. At the moment I have a conceptual GUI system that glues together
- hardware-accelerated rendering of primitives with a scene-style abstraction;
- retained entities and elements as similar concepts to GPUI;
- dispatching mouse events and per-element checks with hitboxes;
- handling window focus and the construction of a dispatch tree; and
- dispatching keyboard events to the focused element with event bubbling.
This concept has many of the main components required to build an editor though there are still many problems that need to be solved at the level of the GUI system such as
- integrating with Taffy to manage layout;
- combining scenes as layers with localized coordinates;
- designing a more dynamic glyph atlas; and
- supporting undo/redo with an undo stack or tree
I’ll be flying to Sydney for a couple of weeks so all of this can wait until I’m back. I’ll start by mapping out Kilo’s features and then make a start on building Mega proper. Until then I plan to catch up on reading a bunch of articles I have saved in my Pinto reading list.
That’s all for now, see you next month!