ailloli_ui_runtime/scene/
draw_cmd.rs

1//! Renderer-neutral drawing commands emitted by retained views.
2
3use std::sync::Arc;
4
5use ailloli_ui_core::{
6    Border, BoxShadow, Color, IconId, Point, Radius, Rect, StrokeStyle, TextDecoration,
7};
8use ailloli_ui_text::PreparedTextLayout;
9
10/// Filled axis-aligned rectangle draw primitive.
11///
12/// Coordinates are window-space logical pixels; color is linear RGBA.
13///
14/// # Examples
15///
16/// ```
17/// use ailloli_ui_core::{Color, Rect};
18/// use ailloli_ui_runtime::scene::DrawRect;
19/// let draw = DrawRect { rect: Rect::new(1.0, 2.0, 30.0, 10.0), color: Color::WHITE };
20/// assert_eq!(draw.rect.w, 30.0);
21/// ```
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct DrawRect {
24    /// Filled rectangle in logical pixels.
25    pub rect: Rect,
26    /// Linear-RGBA fill color.
27    pub color: Color,
28}
29
30/// Filled rounded-rectangle draw primitive.
31///
32/// # Examples
33///
34/// ```
35/// use ailloli_ui_core::{Color, Rect};
36/// use ailloli_ui_runtime::scene::DrawRRect;
37/// let draw = DrawRRect { rect: Rect::new(0.0, 0.0, 20.0, 10.0), radius: 4.0, color: Color::WHITE };
38/// assert_eq!(draw.radius, 4.0);
39/// ```
40#[derive(Debug, Clone, Copy, PartialEq)]
41pub struct DrawRRect {
42    /// Filled rectangle in window-space logical pixels.
43    pub rect: Rect,
44    /// Corner radius in logical pixels (clamped by the renderer).
45    pub radius: f32,
46    /// Linear-RGBA fill color.
47    pub color: Color,
48}
49
50/// Layout-aware border drawn around a rounded rectangle.
51///
52/// # Examples
53///
54/// ```
55/// use ailloli_ui_core::{Border, Color, Radius, Rect};
56/// use ailloli_ui_runtime::scene::DrawBorder;
57/// let draw = DrawBorder {
58///     rect: Rect::new(0.0, 0.0, 40.0, 20.0),
59///     radius: Radius::uniform(3.0),
60///     border: Border::new(1.0, Color::WHITE),
61/// };
62/// assert_eq!(draw.border.widths.left, 1.0);
63/// ```
64#[derive(Debug, Clone, Copy, PartialEq)]
65pub struct DrawBorder {
66    /// Border box in window-space logical pixels.
67    pub rect: Rect,
68    /// Per-corner radii in logical pixels.
69    pub radius: Radius,
70    /// Per-edge widths, colors, and stroke style.
71    pub border: Border,
72}
73
74/// Paint-only shadow associated with a rounded source rectangle.
75///
76/// # Examples
77///
78/// ```
79/// use ailloli_ui_core::{BoxShadow, Radius, Rect};
80/// use ailloli_ui_runtime::scene::DrawBoxShadow;
81/// let draw = DrawBoxShadow {
82///     rect: Rect::new(0.0, 0.0, 40.0, 20.0),
83///     radius: Radius::uniform(2.0),
84///     shadow: BoxShadow::sm(),
85/// };
86/// assert_eq!(draw.shadow.blur_radius, 2.0);
87/// ```
88#[derive(Debug, Clone, Copy, PartialEq)]
89pub struct DrawBoxShadow {
90    /// Source box in window-space logical pixels.
91    pub rect: Rect,
92    /// Source-box corner radii in logical pixels.
93    pub radius: Radius,
94    /// Shadow offset, blur, spread, color, and inset flag.
95    pub shadow: BoxShadow,
96}
97
98/// Circular progress-ring primitive.
99///
100/// Numeric inputs are stored verbatim. The conventional fraction range is
101/// `0.0..=1.0`, but this type does not clamp it or reject negative/non-finite
102/// thicknesses and angles.
103///
104/// # Examples
105///
106/// ```
107/// use ailloli_ui_core::{Color, Rect};
108/// use ailloli_ui_runtime::scene::DrawRingProgress;
109/// let ring = DrawRingProgress {
110///     rect: Rect::new(0.0, 0.0, 24.0, 24.0), thickness: 2.0, fraction: 0.5,
111///     track_color: Color::BLACK, fill_color: Color::WHITE,
112///     start_angle: -std::f32::consts::FRAC_PI_2,
113/// };
114/// assert_eq!(ring.fraction, 0.5);
115/// ```
116#[derive(Debug, Clone, Copy, PartialEq)]
117pub struct DrawRingProgress {
118    /// Outer ring rectangle in window-space logical pixels.
119    pub rect: Rect,
120    /// Requested stroke thickness in logical pixels.
121    pub thickness: f32,
122    /// Requested filled revolution fraction; conventionally `0.0..=1.0`.
123    pub fraction: f32,
124    /// Linear-RGBA color of the complete background track.
125    pub track_color: Color,
126    /// Linear-RGBA color of the fractional foreground arc.
127    pub fill_color: Color,
128    /// Radians, with `-FRAC_PI_2` meaning top-center start.
129    pub start_angle: f32,
130}
131
132/// Connected line segments rendered with one stroke style.
133///
134/// Zero or one point cannot form a segment but is accepted; allocation size is
135/// proportional to the caller-provided point count.
136///
137/// # Examples
138///
139/// ```
140/// use ailloli_ui_core::{Color, Point, StrokeStyle};
141/// use ailloli_ui_runtime::scene::DrawPolyline;
142/// let line = DrawPolyline {
143///     points: vec![Point::new(0.0, 0.0), Point::new(10.0, 5.0)],
144///     stroke: StrokeStyle::new(2.0, Color::WHITE),
145/// };
146/// assert_eq!(line.points.len(), 2);
147/// ```
148#[derive(Debug, Clone, PartialEq)]
149pub struct DrawPolyline {
150    /// Vertices in window-space logical pixels, in traversal order.
151    pub points: Vec<Point>,
152    /// Width, color, cap, join, and miter settings for every segment.
153    pub stroke: StrokeStyle,
154}
155
156/// Prepared shaped text positioned by its first baseline.
157///
158/// The shared layout keeps source text, glyphs, and line metrics alive. Color
159/// is the uniform paint color; styled layouts may additionally carry per-glyph
160/// colors. All positions and metrics are logical pixels.
161///
162/// # Examples
163///
164/// ```
165/// use std::sync::Arc;
166/// use ailloli_ui_core::{Color, FontId, TextDecoration, TextStyle};
167/// use ailloli_ui_runtime::scene::DrawText;
168/// use ailloli_ui_text::{TextLayoutParams, TextSystem};
169/// let mut system = TextSystem::new();
170/// let layout = system.layout_cached(TextLayoutParams::new(
171///     "hello", TextStyle::new(FontId::Ui, 14, Color::WHITE),
172/// ));
173/// let draw = DrawText { pos: [5.0, 20.0], color: Color::WHITE,
174///     decoration: TextDecoration::None, layout };
175/// assert_eq!(draw.layout.text(), "hello");
176/// ```
177#[derive(Debug, Clone)]
178pub struct DrawText {
179    /// `[x, first_baseline_y]` in window-space logical pixels.
180    pub pos: [f32; 2],
181    /// Uniform linear-RGBA paint color.
182    pub color: Color,
183    /// Requested paint-only text decoration.
184    pub decoration: TextDecoration,
185    /// Shared, renderer-ready shaped layout.
186    pub layout: Arc<PreparedTextLayout>,
187}
188
189/// Provides the operations defined for DrawText.
190impl DrawText {
191    /// Returns logical-pixel underline rectangles for non-empty visual lines.
192    ///
193    /// Decorations other than underline return an empty vector. `dpr` is
194    /// clamped to at least `0.01` using floating-point `max`; NaN therefore also
195    /// becomes `0.01`, while positive infinity can yield non-finite snapped
196    /// coordinates. Each underline is at least one physical pixel thick, uses
197    /// the line width, and is vertically snapped to the effective DPR.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use ailloli_ui_core::{Color, FontId, TextDecoration, TextStyle};
203    /// use ailloli_ui_runtime::scene::DrawText;
204    /// use ailloli_ui_text::{TextLayoutParams, TextSystem};
205    /// let mut system = TextSystem::new();
206    /// let layout = system.layout_cached(TextLayoutParams::new(
207    ///     "underlined", TextStyle::new(FontId::Ui, 14, Color::WHITE),
208    /// ));
209    /// let draw = DrawText { pos: [0.0, layout.lines[0].baseline_y], color: Color::WHITE,
210    ///     decoration: TextDecoration::Underline, layout };
211    /// assert_eq!(draw.decoration_rects(2.0).len(), 1);
212    /// assert!(draw.decoration_rects(2.0)[0].h >= 0.5);
213    /// ```
214    pub fn decoration_rects(&self, dpr: f32) -> Vec<Rect> {
215        if self.decoration != TextDecoration::Underline {
216            return Vec::new();
217        }
218        let dpr = dpr.max(0.01);
219        let first_baseline = self
220            .layout
221            .lines
222            .first()
223            .map(|line| line.baseline_y)
224            .unwrap_or(0.0);
225        let origin_y = self.pos[1] - first_baseline;
226        self.layout
227            .lines
228            .iter()
229            .filter(|line| line.width > 0.0)
230            .map(|line| {
231                let thickness = (line.descent / 3.0).max(1.0 / dpr);
232                let y = origin_y + line.baseline_y + (line.descent * 0.5).max(thickness * 0.5);
233                let snapped_y = (y * dpr).round() / dpr;
234                Rect::new(self.pos[0], snapped_y, line.width, thickness)
235            })
236            .collect()
237    }
238}
239
240/// Icon or image draw primitive.
241///
242/// Rotation is in radians around the renderer-defined image center. Values are
243/// stored verbatim and tint uses linear RGBA.
244///
245/// # Examples
246///
247/// ```
248/// use ailloli_ui_core::{Color, IconId, Rect};
249/// use ailloli_ui_runtime::scene::DrawImage;
250/// let image = DrawImage {
251///     rect: Rect::new(0.0, 0.0, 16.0, 16.0), icon: IconId::Check,
252///     tint: Color::WHITE, rotation_rad: 0.0,
253/// };
254/// assert_eq!(image.rotation_rad, 0.0);
255/// ```
256#[derive(Debug, Clone, PartialEq)]
257pub struct DrawImage {
258    /// Destination rectangle in window-space logical pixels.
259    pub rect: Rect,
260    /// Provider-neutral image/icon identifier.
261    pub icon: IconId,
262    /// Linear-RGBA multiplicative tint.
263    pub tint: Color,
264    /// Clockwise rotation in radians; zero means unrotated.
265    pub rotation_rad: f32,
266}
267
268/// Single paint primitive emitted during the paint phase.
269///
270/// Commands retain their data and are executed later by a renderer. The enum
271/// does not validate geometry, clamp colors, or guarantee backend support.
272///
273/// # Examples
274///
275/// ```
276/// use ailloli_ui_core::{Color, Rect};
277/// use ailloli_ui_runtime::scene::{DrawCmd, DrawRect};
278/// let cmd = DrawCmd::Rect(DrawRect { rect: Rect::new(0.0, 0.0, 8.0, 8.0), color: Color::WHITE });
279/// assert!(matches!(cmd, DrawCmd::Rect(_)));
280/// ```
281#[derive(Debug, Clone)]
282pub enum DrawCmd {
283    /// Solid rectangle.
284    Rect(DrawRect),
285    /// Rounded rectangle.
286    RRect(DrawRRect),
287    /// Layout-aware box border.
288    Border(DrawBorder),
289    /// Paint-only box shadow.
290    BoxShadow(DrawBoxShadow),
291    /// Circular progress ring with a complete track and fractional fill.
292    RingProgress(DrawRingProgress),
293    /// Stroked polyline.
294    Polyline(DrawPolyline),
295    /// Shaped text at a baseline position.
296    Text(DrawText),
297    /// Icon or image in a rect.
298    Image(DrawImage),
299}