stipple_core/lib.rs
1//! Stipple's runtime core.
2//!
3//! Defines the declarative [`View`] trait, the [`Element`] IR that views build,
4//! and the layout + paint passes ([`render_view`]) that turn a view into a
5//! [`Scene`] ready for `stipple-render` to rasterize.
6//!
7//! # Pipeline
8//!
9//! `build → layout → paint`, with pointer events routed back through the laid-
10//! out tree:
11//! - a [`View`] (or an app build closure with a [`Cx`]) produces an [`Element`]
12//! tree, registering `on_tap` handlers in the context;
13//! - [`layout`] turns it into a retained [`LayoutNode`] tree;
14//! - [`paint`] draws that tree into a [`Scene`], and [`hit_test`] routes
15//! pointer taps to the registered [`Handlers`].
16//!
17//! Between frames, [`diff_trees`] reconciles the previously-presented
18//! [`LayoutNode`] tree against the freshly built one to compute the changed
19//! [`Damage`] region, so the platform re-presents only what moved rather than
20//! the whole window.
21//!
22//! Still ahead (ROADMAP.md Phase 1+): fine-grained per-node state so a rebuild
23//! can skip unchanged subtrees entirely, and richer gesture recognition.
24
25#![forbid(unsafe_code)]
26
27pub mod a11y;
28mod clipboard;
29mod diff;
30mod element;
31mod render;
32pub mod runtime;
33
34pub use a11y::{AccessNode, Role, accessibility_tree};
35pub use clipboard::{clipboard_text, set_clipboard_text};
36pub use diff::{Damage, diff_trees};
37pub use element::{Align, BoxStyle, Element, ElementKind, LayoutStyle, SizeOverride};
38pub use render::caret_index_at;
39pub use render::{apply_scroll, layout, measure, paint, paint_focus, paint_hover};
40pub use runtime::{
41 ActionId, Anchor, ContextId, Cx, DragId, FocusId, Handlers, KeyInput, LayoutNode, NodeContent,
42 OverlaySpec, ScrollId, TextPosId, ViewportEvent, ViewportId, collect_focusables,
43 collect_viewports, context_at, drag_at, find_action, find_focus, find_scroll, find_text_pos,
44 first_text, focus_at, hit_test, scroll_at, text_pos_at, viewport_at,
45};
46
47// The font type lives in stipple-render; re-export so callers of the layout/paint
48// passes have one import path for the active font.
49pub use stipple_render::Font;
50
51// Re-export the layout axis so widget crates speak one vocabulary.
52pub use stipple_layout::Axis;
53
54use stipple_geometry::{Rect, Size};
55use stipple_render::Scene;
56use stipple_style::Theme;
57
58/// A piece of UI, described declaratively as a function of theme.
59///
60/// Implementors return an [`Element`] tree. This is the static-composition
61/// entry point; interactive UIs use an app build closure threaded with a
62/// [`Cx`] (see the `stipple` umbrella crate's `App`) to register handlers.
63pub trait View {
64 /// Build this view's element tree under the given `theme`.
65 fn build(&self, theme: &Theme) -> Element;
66}
67
68/// An [`Element`] is itself a (trivial) view.
69impl View for Element {
70 fn build(&self, _theme: &Theme) -> Element {
71 self.clone()
72 }
73}
74
75/// Build `view`, lay it out to fill `size` logical pixels, and paint it into a
76/// fresh [`Scene`]. Text is rendered with `font` (pass `None` to skip text).
77/// Interaction handles on the elements are ignored (use [`layout`] +
78/// [`hit_test`] directly to route events).
79pub fn render_view(view: &impl View, size: Size, theme: &Theme, font: Option<&Font>) -> Scene {
80 let element = view.build(theme);
81 let tree = layout(
82 &element,
83 Rect::from_xywh(0.0, 0.0, size.width, size.height),
84 font,
85 );
86 let mut scene = Scene::new(size);
87 paint(&tree, &mut scene, font);
88 scene
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use stipple_render::Color;
95
96 #[test]
97 fn render_view_paints_a_root_panel() {
98 let root = Element::stack(Axis::Vertical, vec![])
99 .fill(Color::rgb(10, 20, 30))
100 .padding(stipple_geometry::Insets::uniform(8.0));
101 let scene = render_view(&root, Size::new(100.0, 100.0), &Theme::light(), None);
102 assert_eq!(scene.len(), 1);
103 assert_eq!(scene.logical_size(), Size::new(100.0, 100.0));
104 }
105}