Expand description
§Guillotine
§Guillotine
A no-std graphical user interface framework for embedded devices prioritizing resource efficiency and ergonomics. The UI declaration API is heavily inspired by GPUI.
Works everywhere embedded-graphics works.
§Demo
A demo Guillotine UI on a Waveshare ESP32-C6 1.47“ LCD board:
use embedded_graphics::{
prelude::*,
mock_display::MockDisplay,
pixelcolor::Rgb565,
mono_font::ascii::FONT_9X18_BOLD,
};
use guillotine::*;
struct BasicView {
greeting: &'static str,
}
impl Render for BasicView {
// Render lets you declaratively build your UI tree.
fn render<'a>(&'a self, _cx: &mut Context) -> impl IntoElement<Element = Element<'a>> {
column()
.padding(10)
.margin(10)
.border(2)
.border_color(Rgb565::BLUE)
.child(text(self.greeting).background(Rgb565::RED).margin(5))
.child(text("GUILLOTINE").margin(5).font(Font::mono(&FONT_9X18_BOLD)))
}
}
fn main() {
// Display should implement embedded_graphics DrawTarget
let display = MockDisplay::new();
let view = BasicView {
greeting: "Hello world!"
};
let mut ui = Ui::new(display);
// Render the view
ui.render(&view);
}
§Core Concepts
§Declarative Definition
- Explain the idea of declaratively building your UI
§TODO: Hybrid Immediate & Retained Mode
§TODO: Similar tree-based layout to X
- GPUI
§State Management
§Layout Engine
- Conceptually similar to Flutter (i.e. constraints go down, sizes go up)
- constraints flow downward, sizes flow upward, positions flow downward
- Requirement: single pass.
§API
extern crate alloc;
use embedded_graphics::{prelude::*, mock_display::MockDisplay, pixelcolor::Rgb565};
use guillotine::*;
struct Home {
show_button: bool,
header: &'static str,
}
impl Render for Home {
fn render<'a>(&'a self, _cx: &mut Context) -> impl IntoElement<Element = Element<'a>> {
row()
.bg(Rgb565::RED)
.child(text(self.header))
.when(self.show_button, |row| row.child(text("Click me")))
.children([text("Copyright"), text("ACME Corp")])
}
}
struct Page {
power: f32,
current: f32,
voltage: f32,
}
impl Render for Page {
fn render<'a>(&'a self, _cx: &mut Context) -> impl IntoElement<Element = Element<'a>> {
column()
.child(text(alloc::format!("Power: {}", self.power)))
.child(text(alloc::format!("Current: {}", self.current)))
.child(text(alloc::format!("Voltage: {}", self.voltage)))
}
}
fn main() {
let display = MockDisplay::new();
let mut ui = Ui::new(display);
let mut home = Home {
show_button: false,
header: "Some Title",
};
ui.render(&home).unwrap();
home.show_button = true;
ui.render(&home).unwrap();
let mut page = Page {
power: 50.0,
voltage: 230.0,
current: 0.2173
};
ui.render(&page);
}Element trees use the display’s PixelColor type throughout. Rgb565 views keep the API shown
above; other targets select their color once at the Render<Color> boundary, after which element
constructors and style methods infer it. See the binary-color example.
§Insets and the box model
Margin, padding, and border widths accept CSS-like physical-edge shorthands:
column()
.margin(10) // all edges
.padding((4, 8)) // vertical, horizontal
.border((1, 2, 3)) // top, horizontal, bottom
.margin((4, 8, 12, 16)); // top, right, bottom, leftUse Insets::new(top, right, bottom, left) when a named value is clearer. Insets are non-negative
pixel lengths. Guillotine doesn’t currently support percentages, auto, logical edges, negative
margins, margin collapsing, per-edge border colors, or border styles. Adjacent margins in rows and
columns add together.
Style::size is the border-box size: padding and border are placed inside it, and margin is added
outside it. The box grows to contain its padding and border when parent constraints allow.
§Examples
To run the examples, you need to enable the simulator feature. This pulls in a bundled sdl2 for opening windows. You will need cmake to compile it.
cargo run --example power_monitor --features simulator§Roadmap
§v0.0.1
- Try to mimic GPUI declaration style: https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/hello_world.rs
-
Low-level
Element/ParentElementtrait for custom elements and widgets -
Support generic
PixelColor - Full immediate mode redrawing
-
Make repo ready for publishing:
- README documentation (a la Dioxus)
- Rustdoc documentation
-
Examples
- Sizing (insets)
- Fonts
- Cool
- ESP32
- Fix exports
- Dual Apache / MIT license
- Fix sdl2 vendoring for embedded-graphics-simulator
- Benchmarks for Frame building
-
TextStylefonts -
Support for non-interactive elements:
- Row
- (formatted) Text
- Column
- Spinner
- Container gaps
§v0.1.0
-
Custom render modes:
Incremental(only repaint changed regions, requires more memory), orRedraw(full redraw on every render, lowest memory footprint). Either as a feature or runtime flag. - Define inremental redrawing triggers / states:
enum DrawState {
Clean,
Paint, // same geometry; repaint old/current bounds
Layout, // size or position changed
Structure, // child added, removed, moved, or keyed differently
Full, // theme, rotation, display reset, etc.
}-
New elements
- Dialogs / Modals (floating containers)
- Charts
-
Overflow behaviour:
- Visible
- Clip
-
profilefeature withdefmtlogs - No alloc
- Custom elements
- Alignment
- Support for interaction
-
Support interactive elements:
- Button
- Slider
§Why?
I was trying to build a clean-looking dashboard on a small LCD screen powered by an ESP32-C6, that’s supposed to monitor and display the power consumption of my home lab (project here). I wanted to do this in Rust, with the esp-rs ecosystem. The ecosystem is quite mature, but I couldn’t really find a UI framework that was:
- Performant
- Very low memory footprint (no Slint / LVGL)
- Beautiful
Additionally, I wanted to learn what it would take to build something like this.
§Prior Work & Inspiration
Re-exports§
pub use style::Insets;pub use style::Style;pub use style::StyledElement;
Modules§
- style
- Styling utilities.
Structs§
- Column
- A vertical container declaration.
- Column
Style - Style for this column.
- Context
- For now, unused. In the future, will be used for context management, such as:
- Row
- A horizontal container declaration.
- RowStyle
- Style for this row.
- Text
- An ephemeral text declaration.
- Text
Style - Text style.
- Theme
- Colors used by the UI when an element doesn’t specify a color explicitly.
- Ui
- The
Uistruct is the main entrypoint for the Guillotine UI framework. It manages the display and takes care of rendering the UI from a tree ofElements, withSelf::render.
Enums§
- Element
- An ephemeral element declaration produced while rendering a frame.
- Font
- Font for text rendering.
Traits§
- Fluent
Builder - A helper trait for building complex objects with imperative conditionals in a fluent style.
- Into
Element - A value that can be converted into Guillotine’s closed element enum.
- Parent
Element - This is a helper trait to provide a uniform interface for constructing elements that can accept any number of any kind of child elements
- Render
- The
Rendertrait is implemented by types that can be rendered into anElement. Use this trait to define UI elements. - Text
Styled Element - A trait for elements that can be styled with text properties.