fission_render/lib.rs
1//! Display list and rendering abstraction for the Fission UI framework.
2//!
3//! After the widget tree has been compiled to an IR and the layout engine has
4//! positioned every node, the framework flattens the result into a [`DisplayList`]
5//! -- an ordered sequence of [`DisplayOp`] commands that describe exactly what to
6//! draw and in what order.
7//!
8//! Platform backends implement the [`Renderer`] trait to consume a display list and
9//! produce pixels using whatever GPU or software rasterizer is available.
10//!
11//! # Pipeline
12//!
13//! ```text
14//! CoreIR --> LayoutSnapshot --> DisplayList --> Renderer (Metal, Vulkan, ...)
15//! ```
16//!
17//! # Graphics state model
18//!
19//! The display list uses a save/restore stack model (like HTML Canvas or
20//! CoreGraphics). [`DisplayOp::Save`] pushes the current clip and transform onto a
21//! stack, and [`DisplayOp::Restore`] pops it. Drawing operations between a
22//! save/restore pair are affected by the clips and transforms set within that scope.
23
24use fission_ir::NodeId;
25pub use fission_layout::{LayoutPoint, LayoutRect, LayoutSize, LayoutUnit};
26use serde::{Deserialize, Serialize};
27
28/// An RGBA color with 8-bit channels.
29///
30/// This is the render-side mirror of [`fission_ir::op::Color`]. A separate type is
31/// defined here so that `fission-render` can be used without pulling in the full IR
32/// crate's type system.
33#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
34pub struct Color {
35 /// Red channel (0-255).
36 pub r: u8,
37 /// Green channel (0-255).
38 pub g: u8,
39 /// Blue channel (0-255).
40 pub b: u8,
41 /// Alpha channel (0 = fully transparent, 255 = fully opaque).
42 pub a: u8,
43}
44
45/// A solid color fill for shapes.
46#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
47pub struct Fill {
48 /// The fill color.
49 pub color: Color,
50}
51
52/// A colored stroke (outline) with a line width.
53#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
54pub struct Stroke {
55 /// The stroke color.
56 pub color: Color,
57 /// The stroke width in logical pixels.
58 pub width: LayoutUnit,
59}
60
61/// A drop shadow rendered behind a rectangle.
62#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
63pub struct BoxShadow {
64 /// The shadow color (typically semi-transparent black).
65 pub color: Color,
66 /// The Gaussian blur radius in logical pixels.
67 pub blur_radius: LayoutUnit,
68 /// The horizontal and vertical offset: `(dx, dy)`.
69 pub offset: (LayoutUnit, LayoutUnit),
70}
71
72/// How an image scales to fit its layout box.
73///
74/// Equivalent to the CSS `object-fit` property.
75#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
76pub enum ImageFit {
77 /// Scale uniformly to fit inside the box (may letter-box).
78 Contain,
79 /// Scale uniformly to cover the entire box (may clip).
80 Cover,
81 /// Stretch to fill exactly (ignores aspect ratio).
82 Fill,
83 /// Display at natural size, no scaling.
84 None,
85}
86
87/// Styling properties for a run of text.
88///
89/// Controls font size, color, underline, and optional background highlight
90/// (used for search matches, selections, etc.).
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub struct TextStyle {
93 /// Font size in logical pixels.
94 pub font_size: LayoutUnit,
95 /// Text foreground color.
96 pub color: Color,
97 /// Whether to draw an underline beneath the text.
98 pub underline: bool,
99 /// Optional background highlight color for this run.
100 pub background_color: Option<Color>,
101}
102
103/// A contiguous run of text with a uniform style.
104///
105/// Rich text is represented as a `Vec<TextRun>`. Each run carries its own
106/// [`TextStyle`], allowing mixed colors, sizes, and underlines within a single
107/// text block.
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct TextRun {
110 /// The text content of this run.
111 pub text: String,
112 /// The style applied to this run.
113 pub style: TextStyle,
114}
115
116/// A single rendering command in a [`DisplayList`].
117///
118/// Display ops are executed in order by a [`Renderer`] backend. The save/restore
119/// stack model means that clip and transform ops affect all subsequent draw ops
120/// until the matching `Restore`.
121///
122/// Most draw ops carry a `bounds` field (the layout rectangle of the owning node)
123/// and an optional `node_id` for hit-testing and debugging.
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub enum DisplayOp {
126 /// Pushes the current graphics state (clip region, transform) onto the stack.
127 Save,
128 /// Pops the graphics state, reverting clip and transform to the last `Save`.
129 Restore,
130 /// Sets a rectangular clip region. Drawing outside this rectangle is discarded.
131 ClipRect(LayoutRect),
132 /// Sets a rounded-rectangle clip region.
133 ClipRoundedRect {
134 /// The clip rectangle.
135 rect: LayoutRect,
136 /// The corner radius for the rounded clip.
137 radius: LayoutUnit,
138 },
139 /// Translates the coordinate origin by the given offset.
140 Translate(LayoutPoint),
141 /// Applies a 4x4 column-major transform matrix to the coordinate space.
142 Transform([LayoutUnit; 16]),
143 /// Draws a filled and/or stroked rectangle with optional rounded corners and shadow.
144 DrawRect {
145 /// The rectangle to draw.
146 rect: LayoutRect,
147 /// Optional interior fill.
148 fill: Option<Fill>,
149 /// Optional border stroke.
150 stroke: Option<Stroke>,
151 /// Corner radius (0.0 for sharp corners).
152 corner_radius: LayoutUnit,
153 /// Optional drop shadow.
154 shadow: Option<BoxShadow>,
155 /// The layout bounds of the owning node (used for hit-testing).
156 bounds: LayoutRect,
157 /// The IR node that produced this draw call (for debugging).
158 node_id: Option<NodeId>,
159 },
160 /// Draws a single-style text string.
161 DrawText {
162 /// The text to render.
163 text: String,
164 /// The top-left position of the text.
165 position: LayoutPoint,
166 /// Font size in logical pixels.
167 size: LayoutUnit,
168 /// Text foreground color.
169 color: Color,
170 /// The layout bounds of the owning node.
171 bounds: LayoutRect,
172 /// The IR node that produced this draw call.
173 node_id: Option<NodeId>,
174 /// Whether to underline the text.
175 underline: bool,
176 /// If set, a text cursor is drawn at this byte index.
177 caret_index: Option<usize>,
178 },
179 /// Draws multi-style (rich) text.
180 DrawRichText {
181 /// The styled text runs, in order.
182 runs: Vec<TextRun>,
183 /// The top-left position of the text block.
184 position: LayoutPoint,
185 /// The layout bounds of the owning node.
186 bounds: LayoutRect,
187 /// The IR node that produced this draw call.
188 node_id: Option<NodeId>,
189 /// If set, a text cursor is drawn at this byte index.
190 caret_index: Option<usize>,
191 },
192 /// Draws a raster image.
193 DrawImage {
194 /// The rectangle to draw the image into.
195 rect: LayoutRect,
196 /// Image source: file path, URL, or asset identifier.
197 source: String,
198 /// How the image scales to fit `rect`.
199 fit: ImageFit,
200 /// The layout bounds of the owning node.
201 bounds: LayoutRect,
202 /// The IR node that produced this draw call.
203 node_id: Option<NodeId>,
204 },
205 /// Draws an SVG-style path string.
206 DrawPath {
207 /// SVG path data (e.g., `"M 0 0 L 10 10 Z"`).
208 path: String,
209 /// Optional fill for the path interior.
210 fill: Option<Fill>,
211 /// Optional stroke for the path outline.
212 stroke: Option<Stroke>,
213 /// The layout bounds of the owning node.
214 bounds: LayoutRect,
215 /// The IR node that produced this draw call.
216 node_id: Option<NodeId>,
217 },
218 /// Draws inline SVG content.
219 DrawSvg {
220 /// The raw SVG markup.
221 content: String,
222 /// Optional fill color override.
223 fill: Option<Fill>,
224 /// Optional stroke color override.
225 stroke: Option<Stroke>,
226 /// The layout bounds of the owning node.
227 bounds: LayoutRect,
228 /// The IR node that produced this draw call.
229 node_id: Option<NodeId>,
230 },
231 /// Blits an external surface (video frame, embedded web view, etc.).
232 DrawSurface {
233 /// The rectangle to draw the surface into.
234 rect: LayoutRect,
235 /// Platform-specific surface identifier.
236 surface_id: u64,
237 /// Position/frame index within the surface (e.g., video frame number).
238 position: u64,
239 /// The layout bounds of the owning node.
240 bounds: LayoutRect,
241 /// The IR node that produced this draw call.
242 node_id: Option<NodeId>,
243 },
244}
245
246/// An ordered list of [`DisplayOp`]s ready to be consumed by a [`Renderer`].
247///
248/// The display list is the final output of the Fission pipeline before pixels.
249/// It is serializable, so it can be sent to a separate render thread or process.
250///
251/// # Example
252///
253/// ```rust
254/// use fission_render::*;
255///
256/// let bounds = LayoutRect::new(0.0, 0.0, 800.0, 600.0);
257/// let mut list = DisplayList::new(bounds);
258///
259/// list.push(DisplayOp::Save);
260/// list.push(DisplayOp::DrawRect {
261/// rect: LayoutRect::new(10.0, 10.0, 100.0, 50.0),
262/// fill: Some(Fill { color: Color { r: 0, g: 0, b: 0, a: 255 } }),
263/// stroke: None,
264/// corner_radius: 0.0,
265/// shadow: None,
266/// bounds: LayoutRect::new(10.0, 10.0, 100.0, 50.0),
267/// node_id: None,
268/// });
269/// list.push(DisplayOp::Restore);
270///
271/// assert_eq!(list.ops.len(), 3);
272/// ```
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274pub struct DisplayList {
275 /// The ordered sequence of rendering commands.
276 pub ops: Vec<DisplayOp>,
277 /// The bounding rectangle of the entire scene (typically the viewport).
278 pub bounds: LayoutRect,
279}
280
281impl DisplayList {
282 /// Creates an empty display list with the given scene bounds.
283 pub fn new(bounds: LayoutRect) -> Self {
284 Self {
285 ops: Vec::new(),
286 bounds,
287 }
288 }
289
290 /// Appends a display operation to the end of the list.
291 pub fn push(&mut self, op: DisplayOp) {
292 self.ops.push(op);
293 }
294}
295
296/// A backend that consumes a [`DisplayList`] and produces pixels.
297///
298/// Implement this trait for each platform rendering backend (Metal, Vulkan, wgpu,
299/// Skia, software rasterizer, etc.). The framework calls
300/// [`render`](Renderer::render) once per frame with the display list for the
301/// current scene.
302///
303/// # Example
304///
305/// ```rust,ignore
306/// use fission_render::{Renderer, DisplayList, DisplayOp};
307///
308/// struct SoftwareRenderer { buffer: Vec<u8> }
309///
310/// impl Renderer for SoftwareRenderer {
311/// fn render(&mut self, display_list: &DisplayList) -> anyhow::Result<()> {
312/// for op in &display_list.ops {
313/// match op {
314/// DisplayOp::DrawRect { rect, fill, .. } => { /* rasterize */ }
315/// _ => {}
316/// }
317/// }
318/// Ok(())
319/// }
320/// }
321/// ```
322pub trait Renderer {
323 /// Renders the given display list. Called once per frame.
324 fn render(&mut self, display_list: &DisplayList) -> anyhow::Result<()>;
325}