Xilem is a framework that aims to provide a performant and productive option for Rust GUI.
This crates evolved from various experiments in the Linebender community (Druid, masonry, idiopath, lasagna, crochet, etc)
This document describes the high-level architecture of xilem. If you want to familirize yourself with the code base, you are just in the right place!
An evolution of the ideas implemented in xilem can be found in the following resources:
- Data Oriented GUI (2018)
- A Journey through incremental computation (2020)
- Raph Levien on UI Frameworks (2021)
- High Performance Rust UI (2022)
- Ergonomic APIs for hard problems (2022)
- Xilem Vector Graphics (2023)
- Announcing Masonry 0.1, and my vision for Rust UI (2023)
- So you want to write a GUI framework (2021)
- Rust GUI Infrastructure (2021)
- Towards a unified theory of reactive UI (2019)
- Towards principled reactive UI (2020)
- Xilem: an architecture for UI in Rust (2022)
- Advice for the next dozen Rust GUIs (2022)
- High Performance. Lightweight state management, retained widget layer, massively parallel rendering backend (Vello), fast startup time, small binary size
- Productive. Offer an ergonomic API, code is concise (low Rust tax), idiomatic and UI components compose well
- Rich 2D graphics model.
- Wide platform support. Windows, MacOS, Linux (Wayland/X11), Android (Google funded), iOS
- Batteries-included. Advanced text layout, IME, Accessibility, Animation, Styling
The code can be roughly divided into 3 levels:
- Reactive Data Layer
- UI Layer
- Framework Layer
Your main interaction with the framework is through the app_logic()
. Like Elm, the app_logic()
contains centralized state. On each cycle (meaning, roughly, on each high-level UI interaction such as a button click), the framework calls a closure, giving it mutable access to the AppData
, and the return value is a View
tree. This View
tree is fairly short-lived; it is used to render the UI, possibly dispatch some events, and be used as a reference for diffing by the next cycle, at which point it is dropped. You pass the app_logic()
to the framework (represented by Xilem
) and then use it to create the window and run the UI.
struct AppData {
count: u32,
}
fn app_logic(data: &mut AppData) -> impl View<AppData, (), Element = impl Widget> {
let count = data.count
let button_label = if count == 1 {
"clicked 1 time".to_string()
} else {
format!("clicked {count} times")
};
button(button_label, |data: &mut AppData| data.count +=1)
}
fn main() {
let data = AppData {
count: 0
}
let app = Xilem::new(data, app_logic);
app.run_windowed("Application Example".into()).unwrap();
}
The most important construct at the reactive layer is the View
trait. View
implementations need to assign the associated State
and Element
and implement build()
, rebuild()
and message()
. The build()
is run only once (first run) to initialize the associated State
create the associated Element
of the view. The rebuild()
is used to update the associated State
and Element
and it is what enables Xilem's reactivity. It is worth noting three special types of views the Adapt
, Memoize
and AnyView
views.
The Adapt
node is used to adapt/convert from the parent data (often the AppData
) to the child data (some subset of the AppData
). The Adapt
node can also adapt messages from the child scope to the parent scope and vice versa.
The View
trees are always eagerly evaluated when the app_logic()
is called. Even though the View
's are very lightweight when the tree becomes large it can create performance issues; the Memoize
node prunes the View
tree to improve performance. The generation of the subtree is postponed until the build()
/rebuild()
are executed. If none of the AppData
dependencies change during a UI cycle neither the View
subtree will be constructed nor the rebuild()
will be called. The View
subtree will only be constructed if any of the AppData
dependencies change. The Memoize
node should be used when the subtree is a pure function of the AppData
.
Generally the Xilem View
tree is statically typed. AnyView
allows for the tree to be type erased to enable easy dynamic reconfiguration of the UI.
The associated Elements of the View
trait are either DOM nodes for xilem_web
or implementations of the Widget
trait.
Contains the View
trait, and other general implementations. Is also contains the Message
, MessageResult
, Id
types and the tree-structrure tracking.
An implementation of Xilem running on the DOM.
See ARCHITECTURE.md
file located under crates/masonry/doc