|
5 | 5 | [](https://github.com/leftger/embedded-gui/actions/workflows/ci.yml) |
6 | 6 | [](LICENSE-MIT) |
7 | 7 |
|
8 | | -`embedded-gui` is a `no_std` GUI/HUD crate for `embedded-graphics` displays. |
9 | | -It is built around fixed-capacity data structures, deterministic rendering, and |
10 | | -embedded-friendly interaction patterns (pointer, encoder, and keyboard-style input). |
11 | | - |
12 | | -Heavily inspired by the [Pebble](https://developer.rebble.io/developer.pebble.com/docs/index.html) |
13 | | -smartwatch UI framework — its animation model, interaction contracts, and cinematic motion |
14 | | -primitives draw directly from Pebble's design language. [LVGL](https://lvgl.io) is a |
15 | | -secondary influence, particularly for widget composition and style-state conventions. |
16 | | - |
17 | | -## Current State |
18 | | - |
19 | | -`embedded-gui` currently provides a broad baseline of widgets, interaction semantics, |
20 | | -fixed-capacity animation primitives, and screen transition tooling for embedded UIs. |
21 | | -Recent work has focused on tightening interaction fidelity, expanding state transition |
22 | | -coverage, and improving regression-focused examples/docs. |
23 | | - |
24 | | -- Pointer release semantics now consistently target the originally pressed widget. |
25 | | -- Focused open dropdowns now close on `Back` before global back handling. |
26 | | -- State transitions now cover keyboard and pointer press feedback paths. |
27 | | -- Enabled/disabled flag changes now participate in state transitions with regression coverage. |
28 | | -- Style-class state overrides now participate correctly in transition endpoint blending. |
29 | | -- Select activation now supports configurable double-select recognition (`DoubleClicked` events). |
30 | | -- Per-widget raw key input policy now supports `SelectPressed/Released` and `BackPressed/Released`. |
31 | | -- Pointer interaction now supports configurable double-click recognition (`DoubleClicked` events). |
32 | | -- Per-widget key bindings can override `Select`/`Back` behavior (`Default`, `Ignore`, `Activate`, `Back`). |
33 | | -- Textarea no-op edits avoid emitting mutation events, with regression coverage. |
34 | | -- Wrapped-line selection navigation (`SelectHome`/`SelectEnd`) now has dedicated edge-case coverage. |
35 | | -- Cross-widget interaction matrix coverage now includes dropdown/list/tabs/roller keyboard navigation paths. |
36 | | -- Nested layout + clip behavior now has dedicated regression coverage for parent-relative composition. |
37 | | -- A dedicated interaction semantics simulator example is available in `examples/interaction_semantics_showcase.rs`. |
| 8 | +`embedded-gui` is a lightweight, deterministic, zero-allocation (`no_std`) GUI & HUD framework for microcontrollers and [`embedded-graphics`](https://crates.io/crates/embedded-graphics) displays. |
38 | 9 |
|
39 | | -## Quick Start |
40 | | - |
41 | | -```rust |
42 | | -use embedded_gui::prelude::*; |
43 | | - |
44 | | -let mut gui = GuiContext::<8, 4, 8>::new(Rect::new(0, 0, 128, 64)); |
45 | | -gui.add_label(Rect::new(4, 4, 80, 8), "READY", Style::label())?; |
46 | | -gui.add_progress_bar(Rect::new(4, 18, 80, 8), 0.6, Style::progress())?; |
47 | | -gui.render(&mut display)?; |
48 | | -``` |
49 | | - |
50 | | -## Implemented Feature Surface |
51 | | - |
52 | | -### Widgets and primitives |
53 | | - |
54 | | -The current widget set includes: |
55 | | - |
56 | | -- labels, panels, buttons, icon buttons, borders/spacers |
57 | | -- progress bars, sliders, toggles, checkboxes, value labels |
58 | | -- lists, menus, tabs, dialogs, toasts, scroll views |
59 | | -- dropdowns, rollers, tables |
60 | | -- chart (line and bars), spinner, meter/gauge/arc gauge/needle |
61 | | -- textareas and on-screen keyboards |
62 | | -- image widgets (`ImageRef`, `ImageFit`, atlas/sprite helpers) |
63 | | - |
64 | | -### Input and events |
65 | | - |
66 | | -- `InputEvent` supports D-pad/encoder navigation, pointer events, and text-editing-oriented key events |
67 | | -- UI-level events (`UiEvent`) and widget-level events (`WidgetEvent`) are both exposed |
68 | | -- Event flow supports capture, target, and bubble phases with filterable dispatch policy hooks |
69 | | -- Pointer semantics include long-press, gesture dispatch, drag scrolling, inertia, and press-repeat timing hooks |
| 10 | +Heavily inspired by the **Pebble** smartwatch UI framework—its animation model, interaction contracts, and cinematic motion primitives draw directly from Pebble's design language. **LVGL** serves as a secondary influence for widget composition, layout rules, and state-variant styling. |
70 | 11 |
|
71 | | -### Layout, styling, and text |
| 12 | +--- |
72 | 13 |
|
73 | | -- `LinearLayout` and constraint-based item layout (`Length`, `Min/Max`, `Percent`, `Ratio`, `Fill`) |
74 | | -- Style model with stateful variants (`normal`, `focused`, `pressed`, `disabled`) |
75 | | -- Style interpolation and transition primitives (`StyleTransition`, `lerp_style`) |
76 | | -- Text layout primitives (`Span`, `Line`, `Text`) with alignment, wrapping, spacing, overflow controls |
77 | | -- Text shaping interface (`TextShaper`) with a basic shaping implementation for embedded-safe defaults |
| 14 | +## Key Capabilities |
78 | 15 |
|
79 | | -### Animation and transitions |
| 16 | +- 🧱 **Zero-Allocation (`no_std`)**: Built entirely on fixed-capacity data structures (`heapless`) with strict memory bounds and deterministic execution times. |
| 17 | +- 🧩 **Rich Built-in Widgets**: Buttons, Sliders, Dropdowns, Toggles, Checkboxes, Gauges, Meters, Sweeping Arcs, Plotters/Charts, TextAreas, On-Screen Keyboards, and Circular Lists. |
| 18 | +- 🛠️ **Native Custom Widgets**: Extensible third-party widget support via type-erased `WidgetStorage<'a>` and object-safe `Widget` trait contracts. |
| 19 | +- 🎬 **Unified Motion Engine**: Pebble-inspired spatial easing curves (`moook`), spring dynamics, timeline keyframing, property mutator bindings, and screen stack transitions (flip-card, peek/glance, shutter, portal). |
| 20 | +- 🎨 **Decoupled Rendering Engine**: Bounding-box dirty tracking, opacity layering, software IIR blur, subpixel anti-aliasing, and custom display backends. |
| 21 | +- ⚡ **Async DMA & Double/Triple Buffering**: Zero-copy presentation via `CompletionSlot` and `StandardSwapChain`, fully compatible with Embassy `async/await` or bare-metal superloop polling. |
| 22 | +- 🎯 **Multi-Target Tested**: Continuously verified across ARM Cortex-M0/M0+ (`thumbv6m`), Cortex-M4F/M7F (`thumbv7em`), Cortex-M33/M55 (`thumbv8m.main`), and RISC-V (`riscv32imac`). |
80 | 23 |
|
81 | | -- Core animation manager and easing/tween/path/spring/inertia primitives |
82 | | -- Spatial timing (`moook_curve`, `TransitionPreset`) — see `docs/transition-presets.md` |
83 | | -- Widget property animation (`WidgetAnimator`) and preset helpers (`presets`) |
84 | | -- Timeline/keyframe sequencing support (`AnimationSequence`, `SequencePlayer`) |
85 | | -- Screen stack + transition primitives for app-level flows (fade, slide, wipe, shutter, port-hole, round-flip, modal) |
| 24 | +--- |
86 | 25 |
|
87 | | -## Animation Quickstart |
88 | | - |
89 | | -`embedded-gui` includes fixed-capacity animation primitives that stay `no_std` friendly. |
| 26 | +## Quick Start |
90 | 27 |
|
91 | 28 | ```rust |
| 29 | +use embedded_graphics::pixelcolor::Rgb565; |
92 | 30 | use embedded_gui::prelude::*; |
93 | 31 |
|
94 | | -let mut animator = WidgetAnimator::<8, 8>::new(); |
95 | | -let progress = gui.add_progress_bar(Rect::new(4, 18, 80, 8), 0.0, Style::progress())?; |
96 | | -animator.animate_progress(progress, 0.0, 1.0, 600, Easing::InOutSine)?; |
97 | | - |
98 | | -// In your frame loop: |
99 | | -animator.tick(16, &mut gui)?; |
100 | | -gui.render_dirty(&mut display)?; |
101 | | -gui.clear_dirty(); |
102 | | -``` |
103 | | - |
104 | | -## Textarea Editing Input |
105 | | - |
106 | | -Textarea widgets support editor-style navigation and mutation events, including: |
| 32 | +// 1. Create a fixed-capacity GUI context (Max Widgets, Focus Group Capacity, Dirty Rects) |
| 33 | +let mut gui = GuiContext::<16, 4, 8>::new(Rect::new(0, 0, 320, 240)); |
107 | 34 |
|
108 | | -- `InputEvent::WordLeft` / `InputEvent::WordRight` |
109 | | -- `InputEvent::Home` / `InputEvent::End` |
110 | | -- `InputEvent::Select*` expansion variants |
111 | | -- `InputEvent::Undo` / `InputEvent::Redo` |
112 | | -- selection-replace behavior on typing/backspace/delete |
| 35 | +// 2. Spawn widgets using the fluent builder pattern |
| 36 | +let status_label = gui.spawn( |
| 37 | + WidgetBuilder::new(Rect::new(10, 10, 150, 20)) |
| 38 | + .with_style_class("header") |
| 39 | + .build() |
| 40 | +)?; |
113 | 41 |
|
114 | | -See `docs/textarea-input-keybindings.md` for mappings and loop snippets. |
115 | | - |
116 | | -## Font Glyph Overrides |
117 | | - |
118 | | -The build pipeline supports external glyph overrides from: |
119 | | - |
120 | | -- `assets/fonts/ascii_3x5.txt` |
121 | | -- `assets/fonts/ascii_4x7.txt` |
122 | | - |
123 | | -Format per line: |
124 | | - |
125 | | -```text |
126 | | -key:row0,row1,row2,row3,row4 |
127 | | -``` |
| 42 | +// 3. Mutate properties dynamically using the generic property engine |
| 43 | +gui.set_widget_property(status_label, PropertyKey::Text, PropertyValue::Text("SYSTEM OK"))?; |
128 | 44 |
|
129 | | -- `key`: a single character or `space` |
130 | | -- each row: 3 bits using `0`/`1` (left to right) |
131 | | -- blank lines and `#` comments are ignored |
132 | | - |
133 | | -Example: |
134 | | - |
135 | | -```text |
136 | | -?:111,001,010,000,010 |
137 | | -!:010,010,010,000,010 |
138 | | -space:000,000,000,000,000 |
139 | | -``` |
140 | | - |
141 | | -Unspecified glyphs fall back to built-in defaults in `build.rs`. |
142 | | - |
143 | | -Preview helper: |
144 | | - |
145 | | -```bash |
146 | | -python3 scripts/preview_glyphs.py assets/fonts/ascii_3x5.txt "?!@" |
| 45 | +// 4. Render only dirty regions to your embedded-graphics DrawTarget |
| 46 | +gui.render(&mut display)?; |
147 | 47 | ``` |
148 | 48 |
|
149 | | -## Examples |
150 | | - |
151 | | -The `examples/` folder currently includes: |
152 | | - |
153 | | -- `widgets_showcase.rs` |
154 | | -- `dashboard_app.rs` |
155 | | -- `simulator_menu.rs` |
156 | | -- `event_dispatch_showcase.rs` |
157 | | -- `interaction_semantics_showcase.rs` |
158 | | -- `raw_key_input_showcase.rs` |
159 | | -- `form_flow_showcase.rs` |
160 | | -- `complex_layout_showcase.rs` |
161 | | -- `input_gesture_drag_repeat_showcase.rs` |
162 | | -- `long_press_input_showcase.rs` |
163 | | -- `text_layout_showcase.rs` |
164 | | -- `font_text_model_showcase.rs` |
165 | | -- `animation_motion_showcase.rs` |
166 | | -- `animation_kitchen_sink_showcase.rs` |
167 | | -- `animation_dirty_showcase.rs` |
168 | | -- `timeline_transition_showcase.rs` |
169 | | -- `visual_quality_showcase.rs` |
170 | | -- `embedded_3dgfx_overlay.rs` |
171 | | - |
172 | | -## Behavior Notes |
173 | | - |
174 | | -- Input and widget interaction guarantees are documented in `docs/interaction-behavior-contract.md`. |
| 49 | +--- |
175 | 50 |
|
176 | 51 | ## Visual Showcase |
177 | 52 |
|
178 | | -Animation and transition capabilities (generated from simulator output): |
179 | | - |
| 53 | +### Pebble-style Cinematic Motion & Transitions |
180 | 54 |  |
181 | 55 |
|
182 | | -Flip-card screen transition (`RoundFlipLeft`) — card selection between views: |
183 | | - |
| 56 | +### Flip-card Screen Stack Transitions |
184 | 57 |  |
185 | 58 |
|
186 | | -Cinematic peek/glance/carddeck — launcher-style glance tiles cycling with slide-prominence animation, peek reveal, and card story transitions: |
187 | | - |
| 59 | +### Launcher Glance Tiles & Card Story Deck |
188 | 60 |  |
189 | 61 |
|
190 | | -Dashboard-style UI composition: |
191 | | - |
| 62 | +### Dashboard UI Composition |
192 | 63 |  |
193 | 64 |
|
194 | | -Typography and mixed-font text model: |
195 | | - |
| 65 | +### Mixed Typography & Font Models |
196 | 66 |  |
197 | 67 |
|
198 | | -Static motion frame preview: |
199 | | - |
200 | | - |
| 68 | +--- |
201 | 69 |
|
202 | | -## Async present and DMA double-buffering |
| 70 | +## Feature Architecture |
203 | 71 |
|
204 | | -Both `embedded-gui` and `embedded-3dgfx` share a runtime-agnostic present stack: |
| 72 | +### 1. Widgets & Layouts |
| 73 | +- **Controls**: Buttons, Icon Buttons, Sliders, Toggles, Checkboxes, Dropdowns, Rollers. |
| 74 | +- **Data & Display**: Progress Bars, Gauges, Meters, Sweeping Arcs, Plotters/Line Charts, Bar Charts, Busy Wheels. |
| 75 | +- **Structure & Layout**: Linear Layouts (Row/Column with spacing & constraints), Panels, Tabs, Cards, Dialogs, Circular Lists. |
| 76 | +- **Input & Text**: TextAreas (word wrap, selection, undo/redo), On-Screen Keyboards. |
205 | 77 |
|
206 | | -- **Sync (fastest poll path):** `swap.try_present()` / `gui.try_present_dirty()` — ideal for RTIC tasks and bare-metal superloops. |
207 | | -- **Blocking:** `swap.present()` — simple bring-up. |
208 | | -- **Async:** `swap.present_async().await` — yields while DMA runs; works with Embassy or any `core::future` executor. |
| 78 | +### 2. Motion Framework (`src/motion/`) |
| 79 | +- **Easing & Physics**: Standard Easings (Linear, Quad, Cubic, Sine, Exponential) + Pebble Spatial Easing (`moook_curve`), Spring Physics, Inertia. |
| 80 | +- **Timelines & Keyframes**: Multi-track property keyframing and sequence controllers. |
| 81 | +- **Screen Stack Transitions**: Slide, Fade, Portal, Shutter, Modal Overlay, Round-Flip Card. |
209 | 82 |
|
210 | | -Core types (always available, no Embassy dependency): |
| 83 | +### 3. Render Engine (`src/render/`) |
| 84 | +- **Dirty Region Tracking**: Merges overlapping invalidate rectangles to minimize SPI/I2C/Parallel bus transfers. |
| 85 | +- **Compositing**: Software alpha blending, opacity stacks, subpixel anti-aliasing, and IIR blur filters (RGB565, RGBA8888, GRAY8). |
211 | 86 |
|
212 | | -- [`CompletionSlot`](https://docs.rs/embedded-gui/latest/embedded_gui/struct.CompletionSlot.html) — ISR-safe DMA done flag + waker. |
213 | | -- [`WaitTransfer`](https://docs.rs/embedded-gui/latest/embedded_gui/struct.WaitTransfer.html) — reference `AsyncDmaTransfer` token wired to a `CompletionSlot`. |
| 87 | +### 4. Input & Semantics (`src/input/`) |
| 88 | +- Multi-device event mapping: Rotary Encoders (CW/CCW/Press), D-Pad/Keyboards (Arrow keys, Select, Back), Touch/Pointer (Tap, Long Press, Drag, Flick). |
| 89 | +- Configurable per-widget focus navigation, raw key policies, and event routing phases (Capture, Target, Bubble). |
214 | 90 |
|
215 | | -Optional Cargo features: |
| 91 | +--- |
216 | 92 |
|
217 | | -| Feature | Purpose | |
218 | | -|---------|---------| |
219 | | -| `embedded-graphics` | Enables transparent `embedded-graphics` `MonoFont` support (`&FONT_6X10`, `&FONT_9X15`, etc.) across `FontId`, `TextStyle`, `Style`, and `RenderCtx`. | |
220 | | -| `embedded-text` | Enables `embedded-text` `TextBox` interop adapter | |
221 | | -| `embedded-layout` | Enables `embedded-layout` `View` alignment adapter | |
222 | | -| `embassy-time` | Monotonic `dt_ms` via `embassy_time::Instant` | |
223 | | -| `embassy` | [`EmbassyWaitTransfer`](https://docs.rs/embedded-gui/latest/embedded_gui/struct.EmbassyWaitTransfer.html) + [`FrameClock`](https://docs.rs/embedded-gui/latest/embedded_gui/struct.FrameClock.html) | |
224 | | -| `triple-buffering` | Triple-buffer swap chain for bursty frame times | |
| 93 | +## Examples Directory |
225 | 94 |
|
226 | | -### `embedded-graphics` MonoFont Quickstart |
| 95 | +The repository includes showcase examples categorized under `examples/`: |
227 | 96 |
|
228 | | -To use any `embedded-graphics` `MonoFont` (such as `ascii::FONT_6X10`, `FONT_7X13`, `FONT_9X15`, `FONT_10X20`, `profont`, etc.) transparently in `embedded-gui`, enable the `embedded-graphics` feature in your `Cargo.toml`: |
| 97 | +| Directory | Purpose & Highlights | |
| 98 | +|-----------|----------------------| |
| 99 | +| **`examples/basics/`** | Core layout rules, dashboard layout, form flows, interaction semantics, raw key input, and keyboard navigation (`dashboard_app.rs`, `complex_layout_showcase.rs`, `form_flow_showcase.rs`). | |
| 100 | +| **`examples/widgets/`** | Comprehensive widget showcases, gauges, sweeping arcs, alpha blending, and visual quality benchmarks (`widgets_showcase.rs`, `visual_quality_showcase.rs`, `sweeping_arc_widget_showcase.rs`). | |
| 101 | +| **`examples/motion/`** | Motion framework, Pebble-style spring physics, dirty-region animation, timeline keyframing, and cinematic peek/glance cards (`animation_motion_showcase.rs`, `cinematic_peek_glance_carddeck_showcase.rs`). | |
| 102 | +| **`examples/integrations/`** | Third-party interop, Embassy async frames, DMA swapchain simulation, and 3D graphics overlays (`embassy_gui_frame.rs`, `completion_swapchain_sim.rs`, `embedded_3dgfx_overlay.rs`). | |
229 | 103 |
|
230 | | -```toml |
231 | | -[dependencies] |
232 | | -embedded-gui = { version = "0.1.5", features = ["embedded-graphics"] } |
233 | | -embedded-graphics = "0.8" |
| 104 | +Run any example using Cargo: |
| 105 | +```bash |
| 106 | +cargo run --example dashboard_app --features std |
| 107 | +cargo run --example animation_motion_showcase --features std |
234 | 108 | ``` |
235 | 109 |
|
236 | | -Then pass `&MonoFont` references directly into `TextStyle`, `Style`, or `RenderCtx`: |
237 | | - |
238 | | -```rust |
239 | | -use embedded_graphics::mono_font::ascii::{FONT_6X10, FONT_9X15}; |
240 | | -use embedded_gui::prelude::*; |
241 | | - |
242 | | -// 1. Direct style construction: |
243 | | -let title_style = TextStyle::new(Rgb565::CYAN).with_font(&FONT_9X15); |
244 | | -let body_style = Style::new().with_font(&FONT_6X10); |
245 | | - |
246 | | -// 2. RenderCtx calls: |
247 | | -ctx.draw_text_in_with_font(rect, "DOOM HUD", title_style, &FONT_6X10)?; |
248 | | -``` |
| 110 | +--- |
249 | 111 |
|
250 | | -Examples: |
| 112 | +## Cargo Features |
251 | 113 |
|
252 | | -```bash |
253 | | -cargo run --example completion_swapchain_sim --features std |
254 | | -cargo run --example embassy_gui_frame --features embassy,std |
255 | | -``` |
| 114 | +| Feature | Description | |
| 115 | +|---------|-------------| |
| 116 | +| `embedded-graphics` | *(Default)* Transparent support for `embedded-graphics` `MonoFont` references (`&FONT_6X10`, `&FONT_9X15`) in styles and text rendering. | |
| 117 | +| `libm` | Provides floating-point math support (`f32::sin`, `cos`, `round`, `sqrt`) when building for `no_std` targets without standard library floats. | |
| 118 | +| `rich-widgets` | Enables advanced visual widgets including Gauges, Plotters, TextAreas, and On-Screen Keyboards. | |
| 119 | +| `embedded-text` | Enables interoperability adapters for `embedded-text` `TextBox`. | |
| 120 | +| `embedded-layout` | Enables interoperability adapters for `embedded-layout` `View` alignment. | |
| 121 | +| `embassy` | Adds `EmbassyWaitTransfer` and `FrameClock` for Embassy async executor integration. | |
| 122 | +| `triple-buffering` | Enables triple-buffer swapchain for bursty display frame rates. | |
256 | 123 |
|
257 | | -**Performance note:** triangle and widget rasterization stay synchronous. Async is for **present/wait** only — overlapping CPU render with DMA scan-out. Making inner pixel loops async would add executor overhead without speeding up single-core MCUs. |
| 124 | +--- |
258 | 125 |
|
259 | 126 | ## License |
260 | 127 |
|
261 | | -The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ |
262 | | -License. That means you can choose either the MIT license or the Apache 2.0 |
263 | | -license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or |
264 | | -[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific |
265 | | -license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). |
| 128 | +Dual-licensed under either of: |
| 129 | + |
| 130 | +- **MIT License** ([`LICENSE-MIT`](./LICENSE-MIT)) |
| 131 | +- **Apache License, Version 2.0** ([`LICENSE-APACHE`](./LICENSE-APACHE)) |
266 | 132 |
|
| 133 | +at your option. |
0 commit comments