# azul — full documentation This file concatenates every azul guide page in teaching order. It is meant to be pasted wholesale into an LLM context window. For the structured index see llms.txt. # Application Architecture # Application Architecture ## Introduction Building graphical user interfaces has, despite its perceived simplicity, been a difficult problem in computer science. Despite a constant progress in languages, libraries, design patterns and compilers, developers face the same fundamental problems in user interfaces that they did 40 years ago: managing state, synchronizing application data with what users see, and enabling communication between distant components without creating a spaghettified mess. The root of this struggle lies in a core conflict that nearly every toolkit fails to properly address: the conflict between the "Visual Tree" (the hierarchy of object on the screen) and the "State Graph" (the logical relations between components interacting with each other). * The "Visual Tree" is the hierarchy of elements as they appear on the screen. It is always a tree: a window contains a panel, which contains a button. Its structure is defined by layout and presentation. * The "State Graph" is the map of how application data and logic are connected. A filter control in a toolbar (`Visual Tree` -> `Toolbar` -> `Filter Data`) needs to alter the data displayed in a completely separate table (`Visual Tree` -> `MainPanel` -> `Table`) _without_ the two pieces being merged in a "FilterDataTableWithToolbar", as that will create a complete mess once the interactions become more complex. Second, the biggest problem (especially confronted by React), is the inherent object-oriented mindset of web browsers, with operations such as `dom.addChild` or `removeChild` or `nodeABC.delete`: when something gets visually deleted, the nodes dependent on `nodeABC` need to be updated too, otherwise they now point to stale visual objects. ## Prior Art The key insight of Azul is that this network of dependencies is a complex _graph_, not a simple _tree_ and fusing them together (via a "minimum common ancestor approach") creates the unmaintainable mess of most modern UIs. ```mermaid --- title: Visual Tree --- graph LR v_App[App] --> v_Toolbar[Toolbar] & v_MainPanel[MainPanel] v_Toolbar --> v_SaveButton[SaveButton] v_MainPanel --> v_Sidebar[Sidebar] & v_Table[Table] v_Sidebar --> v_FilterControl[FilterControl] classDef app fill:#f9f,stroke:#333,stroke-width:2px classDef button fill:#ccf,stroke:#333,stroke-width:2px classDef control fill:#cfc,stroke:#333,stroke-width:2px class v_App app class v_SaveButton button class v_FilterControl control ``` ```mermaid --- title: State Graph --- graph TD s_SaveButton[SaveButton] -- "needs validity from" --> s_FormState[FormState] s_FilterControl[FilterControl] -- "updates data for" --> s_TableData[TableData] s_FormState -- "is part of" --> s_AppLogic[AppLogic] s_TableData -- "is part of" --> s_AppLogic classDef button fill:#ccf,stroke:#333,stroke-width:2px classDef control fill:#cfc,stroke:#333,stroke-width:2px classDef state fill:#ccf,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5 classDef data fill:#cfc,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5 class s_SaveButton button class s_FilterControl control class s_FormState state class s_TableData data ``` The "pain" of UI programming stems from frameworks that either fuse them together or awkwardly force the graph to conform to the shape of the tree. ### Fused Hierarchy [OOP] The first generation of toolkits (Qt, GTK, MFC, Swing) were built on an object-oriented model - not because it was necessary, but because it was considered "best practice". The paradigm was simple: the UI is a tree of stateful objects. A `Button` object holds its own text and state, a `MyCustomPanel` object inherits from `Panel` and adds its own data and logic, objects are then composed in a hierarchy until you get to the parent "window" object. ```python # OOP Paradigm class MyApp(othertoolkit.App): # ... def on_click(): # text_input implicitly comes from othertoolkit.App input = self.text_input.getText() calculated = do_somthing_with_input(input) self.output.setText(calculated) self.text_input.setText(„“) ``` ```mermaid graph TD A[MyApp extends App] -->|inherits| B[Panel extends Widget] B -->|inherits| C[Button extends Widget] A -.->|holds reference| C style A fill:#f9f,stroke:#333,stroke-width:2px style B fill:#ccf,stroke:#333,stroke-width:2px style C fill:#cfc,stroke:#333,stroke-width:2px ``` In this model, the Visual Tree and the State Graph are fused. The object inheritance hierarchy _is equal to_ the visual hierarchy. This immediately creates real problems: * Communication between logically related but visually distant components requires complex pointer management (in JS, reference management - no crash but not much better), global mediator objects, or a web of signal-and-slot connections that are difficult to trace and maintain (Qts meta-object-compiler). * Changing the visual layout in this paradigm forces a refactoring of the class hierarchy, which makes developing applications in such toolkits painful and creates hard dependencies on the toolkit itself (leading to „toolkit wars“, like the battle over GTK vs Qt). The application logic is not testable in isolation without the framework because it is fundamentally inseparable from the UI objects themselves. * It creates a hard dependency on the toolkit itself. Your application logic is not portable or reusable because it is fundamentally intertwined with the toolkit's base classes, rendering system, and event model. ### Constrained Hierarchy [Elm, React] The next major step, led by frameworks like React, Angular and the Elm Architecture, introduced a new functional paradigm: `UI = f(data)`. The UI is a declarative, pure function of the application's state. This was revolutionary at the time, as it solved the problem of state synchronization. When the data is changed, the framework efficiently updates the view to match instead of manually needing a `setText()` call ("two-way data binding"). ```python # React Paradigm Model def MyApp(): input_value, set_input_value = useState("") output_value, set_output_value = useState("") def handle_click(): calculated = do_something_with_input(input_value) set_output_value(calculated) set_input_value(„“) return Page(children=[ TextInput(value=input_value, on_change=set_input_value), Button(on_click=handle_click), Label(text=output_value) ]) ``` ```mermaid graph TD A[MyApp State] -->|props down| B[Toolbar] A -->|props down| C[MainPanel] B -->|callback up| A C -->|callback up| A style A fill:#f9f,stroke:#333,stroke-width:2px style B fill:#ccf,stroke:#333,stroke-width:2px style C fill:#cfc,stroke:#333,stroke-width:2px ``` However, while these frameworks finally decouple the view from imperative manipulation, they still constrain the flow of data to the shape of the Visual Tree. The example above works because `TextInput`, `Button`, and `Label` are all siblings, children of `MyApp`. But what if the `Button` were in a `Toolbar` and the `TextInput` and `Label` were in a `MainContent` panel? React's solution is to „lift state up“ to their lowest common ancestor, `MyApp`. The `MyApp` component must now hold the state and pass both the data and the callback functions down through the intermediate components. ```python def MyApp(): # State is lifted to the common ancestor input_value, set_input_value = useState("") # ... logic also lives in the ancestor ... return Page(children=[ # Toolbar is now forced to accept and pass down a prop it doesn't use Toolbar(on_button_click=handle_click), # MainContent is also forced to pass props MainContent( input_value=input_value, on_input_change=set_input_value, output_value=output_value ) ]) ``` Here, the State Graph is still being forced into the tree structure of the view, leading to "prop drilling" and components with indirect APIs. The existence of complex "escape hatches" like Redux or the Context API is evidence of this core constraint—they are patterns invented to work around this default tree-based data flow. Elms solution goes even further to „lift all state up“ to the root ancestor and route everything in a single, top-level "update" function. Elm therefore represents the philosophical extreme of the constrained hierarchy: 1. **Model:** The entire state of the application is held in a single, immutable data structure. 2. **View:** A pure function that takes the `Model` and returns a description of the UI. 3. **Update:** A single, central function that is the only entity allowed to modify the state. It does so by taking an incoming `Msg` (a message from the UI) and the current state, and producing a *new* state. ### Ignoring Hierarchy (IMGUI) Immediate Mode toolkits (IMGUI) take a different approach. The paradigm is to have no persistent UI objects at all; the UI is redrawn from scratch from application data every single frame. This solves synchronization issue by brute force but shoves the problem of application architecture onto the developer instead of the framework - leading to a "minimal, opinionated framework", but serious problems with layouting and accessibility. ```python # IMGUI Paradigm Model class AppState: input_buffer = "" output_text = "" # Inside the main application loop, every frame def render_ui(app_state): ui.text_input("Input:", &app_state.input_buffer) if ui.button("Calculate"): calculated = do_something_with_input(&app_state.input_buffer) app_state.output_text = calculated app_state.input_buffer.clear() ui.label(&app_state.output_text) ``` ```mermaid graph TD A[AppState] -->|reads| B[render_ui] B -->|writes| A B -->|draws| C[UI Every Frame] style A fill:#f9f,stroke:#333,stroke-width:2px style B fill:#ccf,stroke:#333,stroke-width:2px style C fill:#cfc,stroke:#333,stroke-width:2px ``` IMGUI doesn't solve the Visual Tree vs. State Graph problem — it just largely ignores the problem and instead creates a _hidden data binding_ in a "closure with captured arguments" instead of a "class with state and functions": While the form is different from OOP, the operation (and the problem) is the same. A closure is just a function on a struct containing all captured variables. The effect is the same as a class-with-methods, but on top of that, it provides even less layout flexibility than object-oriented code. Immediate Mode GUI does solve the synchronization problem, but it fails at the other two core problems of GUIs: data access and inter-widget communication. ## Intermediate Considerations ### Why Electron Won The success of Electron is (besides practical reasons) likely a consequence of the _architectural_ superiority of Reactive over OOP frameworks. In the 2010s, developers were moving to the declarative web paradigm - not because it provided more features, but primarily because it was more maintainable than the 1990s-era OOP model. When tasked with building a desktop application, they had a choice: revert to the painful, fused hierarchy paradigm of Qt or GTK, lock themselves to a certain vendor toolkit, or use the more modern (yet still constrained) hierarchy of React. Electron provides the bridge - while many developers were probably unconscious about it, they chose it not for its stellar performance (or lack of it), but for its better paradigm, as they were now free to use much more functional-ish frameworks than whatever XAML hell Microsoft was presenting at the time. The native desktop world had no answer to this at the time, so developers accepted the performance cost and tons of build-tool workarounds as a necessary evil. Azul is not an answer to the Reactive or OOP mindset. It doesn't try to reinvent "Electron, but in Rust" or "React, but in Rust". Instead, it tries to build a different paradigm: acknowledging the theoretical idea of `UI = f(data)` but fusing it with the practical reality that the final application will always be a "messy graph", and it's better to "contain" the mess rather than trying to out-theorize it. ### What is the essence of a UI toolkit? A question that sometimes comes up in discussions is how a "GUI toolkit" differs from a "rendering library". This is the second distinction between the major paradigms or classes of "GUI toolkits". One could mainly categorize the toolkit by its handling of the following three "hard problems": 1. **Data Access / Model-View separation:** Somehow a callback needs access to both the data model (i.e. the class) and the stateful UI object (to scrape the text out), but at the same time the „data model“ should be as far removed from the UI as possible, so that logic functions do not depend on view data (`my_ui_object.getText()`). 2. **Synchronization:** It is very easy for the visual UI state and the data model to go out of sync. Solutions so far include "Observer patterns" (callbacks that run when something changes), React-like reconciliation or "just redraw everything" (IMGUI). 3. **Inter-widget communication:** This is the hardest problem to solve, as it's not directly obvious in TodoMVC-esque applications. Existing toolkits assume that the widget hierarchy (visual tree) and the inheritance (or function call) hierarchy are the same (using the least common ancestor as a channel, either via OOP inheritance or via React-style prop drilling). Other solutions involve observable cells of functionality (`useMemo` / `useEffect` in SolidJS), which the framework then coordinates (downside: moves the "mess" implicitly into the framework instead of the user code). Overall, immediate-mode libraries do not solve these problems at all, instead shoving the responsibility for managing state onto the application programmer in the name of "freedom". Well, "freedom" in GUI state management is simply a euphemism for "we don't actually have a clue on how to manage state" - at least saying that would be simply more honest. ## Starting again from scratch So, if we could free our mind conceptually from both OOP and Reactive programming, what would a "proper toolkit look like? By "proper" it means that it solves the problems above and scales to larger (500K - 1 million lines of code) applications without becoming an unmaintainable mess. ### Encoding Visual Hierarchy The first thing we'd need to decide is whether we'd like to serialize the UI or render it directly (IMGUI), without first storing it. The choice here is relatively obvious, because the former creates instant opportunity for introspection of the visual state (such as in a HTML debugger). The counter-argument against this has been traditionally "performance", but when testing Azuls memory profile, this effectively came up as a non-issue: the entire DOM with styling in even a large application is only ~500KB - 1MB of actual data. In terms of efficiency there is a massive upside to this, as we don't need to redraw the entire screen just to blink a cursor, which enables power savings on low-powered devices. Second, it also nicely maps to how computers execute - compare the XML hierarchy to function call stacks: ```html
│ NodeData { ... } │
4 │ parent: 3 │ "text" │ NodeData { ... } │
└───────────────┘ └──────────────────────┘
```
Indices into both arrays match. The layout engine traverses by index,
not by pointer, and reads from compact arrays whose memory layout it
controls.
## Cache hierarchy
Layout itself isn't algorithmically hard. It's a tree walk plus a lot
of if/else. The expensive part isn't the math; it's pulling each
node's properties out of memory.
A modern CPU has a tiered memory hierarchy. Cycle counts are
approximate but the order of magnitude is right:
- **L1 data cache** — 32 to 128 KB per core. ~4 cycles to read.
- **L2** — 256 KB to several MB per core. ~12 cycles.
- **L3** — 4 to 32 MB shared. ~30 to 60 cycles. Doesn't exist on
embedded targets.
- **Main RAM** — gigabytes. ~100 to 300 cycles. A full miss costs
more than running 100 instructions.
Layout reads the same per-node fields once per relayout pass. If the
working set fits in L2, the second pass is essentially free. If it
spills to RAM, every node fetch stalls the pipeline.
The relevant per-node sizes in the layout hot path:
```text
NodeHierarchyItem 32 B parent + 3 sibling/child indices
StyledNodeState 10 B :hover / :focus / :active per node
NodeFlags 4 B contenteditable, tab index, anonymous
compact-cache tier 1 8 B display/position/float/etc bit-packed
compact-cache tier 2 (hot) 68 B width, height, margin, padding, ...
compact-cache tier 2 (cold) 28 B paint-only properties (color, opacity)
compact-cache tier 2b (text) 24 B text-related layout
per-node total (hot) ~150 B
per-node total (warm) ~170 B add cold + text tiers
NodeData (cold during layout) 152 B read once for inline CSS, classes
```
For 1,000 nodes the layout-hot working set is ~150 KB. That fits in L2
on every desktop chip and most embedded ones. For 10,000 nodes it's
~1.5 MB, still L2 on a modern Apple/Intel core. For 100,000 nodes it's
~15 MB, which is L3 on desktop and main memory on embedded.
The numbers indicate when virtual views, lazy panels, and other ways
to keep the rendered subtree small start to matter. See
[Virtual Views](dom/virtual-views.md).
## What's in a node
Each node is split across the two parallel arrays.
`NodeHierarchyItem` carries four indices into the same array — the
node's parent, its previous and next siblings, and its last
descendant:
```rust,ignore
pub struct NodeHierarchyItem {
pub parent: usize, // 0 means "no parent"
pub previous_sibling: usize,
pub next_sibling: usize,
pub last_child: usize, // index of last descendant
}
```
Because children sit contiguously after their parent in tree order,
`data[self_idx ..= last_child]` is the whole subtree rooted at
`self_idx`. No pointer-chasing, no recursion needed to copy a subtree.
`NodeData` carries everything that defines a single node:
```rust,ignore
pub struct NodeData {
pub node_type: NodeType, // HTML tag or leaf (Text/Image/Icon/VirtualView)
pub callbacks: CoreCallbackDataVec, // event handlers; empty for ~80% of nodes
pub style: Css, // inline CSS with implicit :scope, INLINE priority
pub flags: NodeFlags, // tab index, contenteditable, anonymous
pub accessibility: Option