Skip to main content

fenestra_core/
element.rs

1//! The element IR: a plain tree of boxes and text, with typed styles,
2//! interaction variant overlays, and `Msg`-carrying handlers. `view()`
3//! rebuilds this tree on every redraw; it must stay cheap to construct.
4
5use peniko::Color;
6
7use crate::events::KeyInput;
8use crate::style::{Length, Paint, Style, TextAlign, TextWrap, ThemedFn, Transition};
9use crate::theme::Theme;
10use crate::tokens::{ShadowToken, TextSize, Weight};
11
12/// Maps a key press on a focused element to an optional message.
13pub(crate) type TypeAheadFn<Msg> = Box<dyn Fn(&str) -> Option<Msg>>;
14pub(crate) type KeyFn<Msg> = Box<dyn Fn(&KeyInput) -> Option<Msg>>;
15/// Maps a pointer position (as fractions of the element rect) to a message.
16pub(crate) type DragFn<Msg> = Box<dyn Fn(f32, f32) -> Option<Msg>>;
17/// Maps a recognized [`SwipeDir`] to a message.
18pub(crate) type SwipeFn<Msg> = Box<dyn Fn(SwipeDir) -> Msg>;
19/// Maps the edited text to a message.
20pub(crate) type InputFn<Msg> = Box<dyn Fn(&str) -> Msg>;
21/// Maps a dropped OS file path to a message.
22pub(crate) type FileDropFn<Msg> = Box<dyn Fn(&std::path::Path) -> Msg>;
23/// Maps an internal drag payload to an optional message on drop.
24pub(crate) type DropFn<Msg> = Box<dyn Fn(&str) -> Option<Msg>>;
25/// Resolves a control's content color (the color drawn *on* it) from the
26/// theme, for the uniform state-layer engine.
27pub(crate) type ContentFn = Box<dyn Fn(&Theme) -> Color>;
28
29/// What an element fundamentally is.
30#[derive(Debug, Clone, PartialEq)]
31pub enum Kind {
32    /// A container box.
33    Box,
34    /// A text run (one style for the whole paragraph).
35    Text(String),
36    /// A paragraph of styled runs ([`rich_text`]): one wrapped layout,
37    /// per-span weight/color/size/family/italic.
38    Rich(Vec<Span>),
39    /// A themed hairline rule (resolved to `border_subtle`).
40    Divider,
41    /// A vector path (icons, check marks), scaled from its viewbox to the
42    /// element rect and painted in the resolved text color.
43    Path(PathData),
44    /// A single-line editable text field driven by parley's `PlainEditor`.
45    /// The app's value is the source of truth; edits emit `on_input`.
46    Input(InputData),
47    /// An RGBA8 image stretched to the element rect and clipped to its
48    /// corner radius.
49    Image(ImageData),
50}
51
52/// Payload for virtualized rows ([`Element::virtual_rows`]): only the
53/// scrolled-into-view window of rows is materialized each frame.
54pub struct VirtualData<Msg> {
55    pub(crate) count: usize,
56    pub(crate) row_height: f32,
57    /// Rows size themselves; `row_height` is the estimate for
58    /// unmaterialized ones (heights are measured and corrected).
59    pub(crate) variable: bool,
60    pub(crate) builder: std::rc::Rc<dyn Fn(usize) -> Element<Msg>>,
61}
62
63impl<Msg> Clone for VirtualData<Msg> {
64    fn clone(&self) -> Self {
65        Self {
66            count: self.count,
67            row_height: self.row_height,
68            variable: self.variable,
69            builder: std::rc::Rc::clone(&self.builder),
70        }
71    }
72}
73
74/// Payload for a container query ([`responsive`]): the closure rebuilds this
75/// container's subtree from its own available size, and `hint` is the size the
76/// first frame uses before any measurement exists.
77pub(crate) struct ResponsiveData<Msg> {
78    pub(crate) hint: (f32, f32),
79    pub(crate) f: std::rc::Rc<dyn Fn((f32, f32)) -> Element<Msg>>,
80}
81
82/// Payload for [`Kind::Image`].
83#[derive(Debug, Clone)]
84pub struct ImageData {
85    /// Decoded straight-alpha RGBA8 pixels, shared cheaply across frames.
86    pub image: peniko::ImageData,
87}
88
89impl PartialEq for ImageData {
90    /// Identity comparison (same pixel allocation and dimensions), not a
91    /// byte-by-byte one: rebuilt views share the `Arc`'d blob.
92    fn eq(&self, other: &Self) -> bool {
93        self.image.data.id() == other.image.data.id()
94            && self.image.width == other.image.width
95            && self.image.height == other.image.height
96    }
97}
98
99/// Payload for [`Kind::Input`].
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct InputData {
102    /// The current value (app state).
103    pub value: String,
104    /// Placeholder shown when the value is empty.
105    pub placeholder: String,
106    /// Multiline editing: text wraps to the element width, Enter inserts a
107    /// newline, and the measured height grows with the content.
108    pub multiline: bool,
109}
110
111/// Optical corrections for a [`Kind::Path`] (see [`crate::optical`]): geometric
112/// nudges that make a shape *look* right even though it measures "wrong". Both
113/// default to off, so a path renders byte-identically until opted in — set them
114/// per icon where the shape needs it, rather than auto-detecting (which would
115/// silently shift every existing icon).
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
117pub struct OpticalCorrection {
118    /// Scale the drawn path up by [`crate::optical::CIRCLE_OVERSHOOT`] about the
119    /// viewbox center, so a round or pointed icon reads the same visual size as
120    /// square-edged neighbors.
121    pub overshoot: bool,
122    /// Translate the path so its centroid (visual mass, [`crate::optical::centroid`])
123    /// sits at the viewbox center instead of its bounding-box center — the
124    /// classic "nudge the play triangle toward its point" correction.
125    pub center: bool,
126}
127
128/// Path payload for [`Kind::Path`].
129#[derive(Debug, Clone, PartialEq)]
130pub struct PathData {
131    /// The path, in viewbox coordinates.
132    pub path: std::sync::Arc<kurbo::BezPath>,
133    /// Design-space size the path was drawn in.
134    pub viewbox: (f64, f64),
135    /// Stroke width in viewbox units; `None` fills the path instead.
136    pub stroke: Option<f64>,
137    /// Optical corrections (overshoot / centroid centering); both off by default.
138    pub optical: OpticalCorrection,
139}
140
141/// How an overlay child opens and closes.
142#[derive(Debug, Clone, Copy, PartialEq)]
143pub enum OverlayMode {
144    /// Open while present in the tree; the app controls it (modals).
145    /// Outside clicks and Esc emit the overlay's `on_close` message.
146    Open,
147    /// Clicking the anchor (parent) toggles it; outside clicks and Esc
148    /// close it, and a click on any clickable inside closes it (menus).
149    Toggle,
150    /// Shows after hovering the anchor for the delay; never hit-tested.
151    Hover {
152        /// Hover delay in milliseconds.
153        delay_ms: f32,
154    },
155}
156
157/// Which screen edge a drawer/sheet overlay is anchored to.
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub enum DrawerSide {
160    /// The left edge, filling the full canvas height.
161    Left,
162    /// The right edge, filling the full canvas height.
163    Right,
164    /// The top edge, filling the full canvas width.
165    Top,
166    /// The bottom edge, filling the full canvas width — a bottom sheet.
167    Bottom,
168}
169
170/// Where an overlay is positioned.
171#[derive(Debug, Clone, Copy, PartialEq)]
172pub enum OverlayPlacement {
173    /// Below the anchor, left edges aligned, with a vertical gap. Flips
174    /// above when there is no room below.
175    Below {
176        /// Gap in logical px.
177        gap: f32,
178    },
179    /// Below the anchor, horizontally centered on it.
180    BelowCenter {
181        /// Gap in logical px.
182        gap: f32,
183    },
184    /// Centered in the canvas.
185    Center,
186    /// Pinned to the top-right of the canvas (toast stacks).
187    TopRight {
188        /// Margin from the canvas edges in logical px.
189        margin: f32,
190    },
191    /// At the pointer position when the overlay opened (context menus);
192    /// pinned there until it closes.
193    Pointer {
194        /// Offset from the pointer in logical px.
195        gap: f32,
196    },
197    /// Flush against a screen edge, filling that edge's full span (drawers and
198    /// sheets); slides in from off-canvas as it opens.
199    Edge {
200        /// Which edge to anchor to.
201        side: DrawerSide,
202    },
203    /// To the right of the anchor with top edges aligned (submenu flyouts);
204    /// flips to the anchor's left when there is no room on the right.
205    RightStart {
206        /// Gap from the anchor's right (or left, when flipped) edge in px.
207        gap: f32,
208    },
209}
210
211/// Marks an element as an overlay child of its parent (the anchor):
212/// excluded from normal layout, laid out against the canvas, painted after
213/// the root, and hit-tested first.
214#[derive(Debug, Clone, Copy, PartialEq)]
215pub struct Overlay {
216    /// Open/close behavior.
217    pub mode: OverlayMode,
218    /// Positioning relative to the anchor or canvas.
219    pub placement: OverlayPlacement,
220    /// Dim everything beneath with black at 0.4 alpha.
221    pub backdrop: bool,
222    /// Tab cycles only inside this overlay while it is open.
223    pub trap_focus: bool,
224}
225
226impl Overlay {
227    /// A click-toggled menu below its anchor (select listboxes).
228    pub fn menu() -> Self {
229        Self {
230            mode: OverlayMode::Toggle,
231            placement: OverlayPlacement::Below { gap: 4.0 },
232            backdrop: false,
233            trap_focus: false,
234        }
235    }
236
237    /// A hover tooltip: 400ms delay, 6px below, centered, untouchable.
238    pub fn tooltip() -> Self {
239        Self {
240            mode: OverlayMode::Hover { delay_ms: 400.0 },
241            placement: OverlayPlacement::BelowCenter { gap: 6.0 },
242            backdrop: false,
243            trap_focus: false,
244        }
245    }
246
247    /// An app-driven centered modal with backdrop and focus trap.
248    pub fn modal() -> Self {
249        Self {
250            mode: OverlayMode::Open,
251            placement: OverlayPlacement::Center,
252            backdrop: true,
253            trap_focus: true,
254        }
255    }
256
257    /// An app-driven context menu pinned at the right-click position:
258    /// pair with `.on_right_click(open_msg)` on the target and
259    /// `.on_close(close_msg)` on the menu; item clicks are the app's cue
260    /// to close.
261    pub fn context() -> Self {
262        Self {
263            mode: OverlayMode::Open,
264            placement: OverlayPlacement::Pointer { gap: 2.0 },
265            backdrop: false,
266            trap_focus: false,
267        }
268    }
269
270    /// An app-driven toast stack pinned to the top-right: no backdrop, no
271    /// focus trap, and nothing closes it from outside — dismissal is the
272    /// stack's own buttons (or the app removing items).
273    pub fn toasts() -> Self {
274        Self {
275            mode: OverlayMode::Open,
276            placement: OverlayPlacement::TopRight { margin: 16.0 },
277            backdrop: false,
278            trap_focus: false,
279        }
280    }
281
282    /// An app-driven drawer/sheet flush to a screen `side`, with a backdrop and
283    /// focus trap; it slides in from that edge. Render it only while open;
284    /// `on_close` fires on Esc and an outside (scrim) click.
285    pub fn drawer(side: DrawerSide) -> Self {
286        Self {
287            mode: OverlayMode::Open,
288            placement: OverlayPlacement::Edge { side },
289            backdrop: true,
290            trap_focus: true,
291        }
292    }
293
294    /// A click-toggled submenu flyout to the right of its anchoring menu item
295    /// (flipping left at the canvas edge). Clicking or pressing Enter on the
296    /// anchor opens/closes it; outside clicks and Escape close it.
297    pub fn submenu() -> Self {
298        Self {
299            mode: OverlayMode::Toggle,
300            placement: OverlayPlacement::RightStart { gap: 2.0 },
301            backdrop: false,
302            trap_focus: false,
303        }
304    }
305}
306
307/// Accessible role and state of an element, projected into the platform
308/// accessibility tree by the shell (AccessKit) and exposed headlessly via
309/// `Frame::access_tree`. Text, image, and input leaves project
310/// automatically; kit widgets set the rest.
311#[derive(Debug, Clone, Copy, PartialEq)]
312pub enum Semantics {
313    /// An activatable button.
314    Button,
315    /// A checkbox: two-state, or tri-state when `mixed`.
316    Checkbox {
317        /// Whether it is checked.
318        checked: bool,
319        /// Whether it is in the indeterminate (mixed) state — projects
320        /// `aria-checked="mixed"`.
321        mixed: bool,
322    },
323    /// An on/off switch.
324    Switch {
325        /// Whether it is on.
326        on: bool,
327    },
328    /// One option of a radio group.
329    Radio {
330        /// Whether it is the selected option.
331        selected: bool,
332    },
333    /// A numeric slider.
334    Slider {
335        /// Current value.
336        value: f32,
337        /// Minimum value.
338        min: f32,
339        /// Maximum value.
340        max: f32,
341    },
342    /// An editable text field (value comes from the input element).
343    TextInput {
344        /// Whether it accepts newlines.
345        multiline: bool,
346    },
347    /// A button that opens a listbox of options.
348    ComboBox,
349    /// A modal dialog.
350    Dialog,
351    /// One tab of a tab strip.
352    Tab {
353        /// Whether it is the active tab.
354        selected: bool,
355    },
356    /// A transient notification (toasts).
357    Alert,
358    /// Static text (automatic for text leaves).
359    Label,
360    /// An image (automatic for image leaves).
361    Image,
362    /// A numeric input stepped by buttons / arrow keys (CSS `<input type=number>`).
363    Spinbutton {
364        /// Current value.
365        value: f32,
366        /// Minimum value.
367        min: f32,
368        /// Maximum value.
369        max: f32,
370    },
371    /// A scalar measurement within a known range (the HTML `<meter>`).
372    Meter {
373        /// Current value.
374        value: f32,
375        /// Minimum value.
376        min: f32,
377        /// Maximum value.
378        max: f32,
379    },
380    /// Task completion. `value` is the fraction `0.0..=1.0`, or `None` when the
381    /// progress is indeterminate.
382    ProgressBar {
383        /// Completed fraction `0.0..=1.0`, or `None` for indeterminate.
384        value: Option<f32>,
385    },
386}
387
388impl Semantics {
389    /// The ARIA role word for this role (`"button"`, `"textbox"`, …) — the same
390    /// vocabulary [`Frame::access_yaml`](crate::Frame::access_yaml) emits. The
391    /// public name for a node's role, stable across versions.
392    #[must_use]
393    pub fn aria_role(&self) -> &'static str {
394        crate::query::role_name(self)
395    }
396}
397
398/// One styled run of a [`rich_text`] paragraph. Unset properties
399/// inherit the paragraph's text style.
400#[derive(Debug, Clone, PartialEq)]
401pub struct Span {
402    pub(crate) text: String,
403    pub(crate) weight: Option<crate::tokens::Weight>,
404    pub(crate) color: Option<Color>,
405    pub(crate) size_px: Option<f32>,
406    pub(crate) family: Option<crate::tokens::FamilyRole>,
407    pub(crate) italic: bool,
408}
409
410/// A styled run for [`rich_text`].
411pub fn span(text: impl Into<String>) -> Span {
412    Span {
413        text: text.into(),
414        weight: None,
415        color: None,
416        size_px: None,
417        family: None,
418        italic: false,
419    }
420}
421
422impl Span {
423    /// Font weight for this run.
424    pub fn weight(mut self, weight: crate::tokens::Weight) -> Self {
425        self.weight = Some(weight);
426        self
427    }
428
429    /// Color for this run (route through theme tokens in app code).
430    pub fn color(mut self, color: Color) -> Self {
431        self.color = Some(color);
432        self
433    }
434
435    /// Font size in logical px for this run.
436    pub fn size_px(mut self, px: f32) -> Self {
437        self.size_px = Some(px);
438        self
439    }
440
441    /// Family role for this run.
442    pub fn family(mut self, family: crate::tokens::FamilyRole) -> Self {
443        self.family = Some(family);
444        self
445    }
446
447    /// Italic (synthesized when the face has no italic variant).
448    pub fn italic(mut self) -> Self {
449        self.italic = true;
450        self
451    }
452}
453
454/// A recognized swipe (flick) direction, delivered by
455/// [`Element::on_swipe`]. Screen axes: `Down` is toward the bottom.
456#[derive(Debug, Clone, Copy, PartialEq, Eq)]
457pub enum SwipeDir {
458    /// A flick toward the top.
459    Up,
460    /// A flick toward the bottom.
461    Down,
462    /// A flick toward the start of the line (left in LTR).
463    Left,
464    /// A flick toward the end of the line (right in LTR).
465    Right,
466}
467
468/// Mouse cursor shown while hovering an element.
469#[derive(Debug, Clone, Copy, PartialEq, Eq)]
470pub enum Cursor {
471    /// The platform default arrow.
472    Default,
473    /// Pointing hand (buttons, links).
474    Pointer,
475    /// Text I-beam.
476    Text,
477    /// Action not available.
478    NotAllowed,
479}
480
481/// How an element animates *out* when it leaves the tree (the counterpart of
482/// [`Element::enter`]). When an element tagged with [`Element::exit`] is
483/// removed, a paint-only snapshot ("ghost") is left in its place and animates
484/// toward these targets over `transition`, then is dropped — there is no live
485/// widget behind it (inputs collapse to a plain box). Inert under reduced
486/// motion: the element is removed immediately, so headless renders are
487/// unchanged.
488#[derive(Debug, Clone, Copy, PartialEq)]
489pub struct ExitAnim {
490    /// Timing of the exit (spring, or duration + easing).
491    pub transition: Transition,
492    /// Opacity the ghost fades to (0.0 = fully gone, the default).
493    pub opacity_to: f32,
494    /// Scale the ghost reaches about its center (1.0 = no scale change).
495    pub scale_to: f32,
496    /// Translation the ghost drifts by as it leaves, logical px `(dx, dy)`.
497    pub translate_to: (f32, f32),
498}
499
500/// One node in the view tree. `Msg` is the app's message type; handlers
501/// carry `Msg` values, not closures over state.
502pub struct Element<Msg> {
503    pub(crate) kind: Kind,
504    pub(crate) style: Style,
505    pub(crate) children: Vec<Element<Msg>>,
506    /// User key for stable identity (`.id()`).
507    pub(crate) key: Option<String>,
508    /// Where this element was constructed (file:line of the builder
509    /// call) — surfaced by `Frame::debug_tree` so dumps map back to
510    /// source. Captured via `#[track_caller]`, zero proc macros.
511    pub(crate) source: &'static std::panic::Location<'static>,
512    /// Forces children of a z-stack into the same grid cell.
513    pub(crate) stack: bool,
514    pub(crate) focusable: bool,
515    pub(crate) autofocus: bool,
516    /// Scroll containers only: keep pinned to the bottom while content
517    /// grows (chat/log pattern).
518    pub(crate) stick_bottom: bool,
519    pub(crate) cursor: Option<Cursor>,
520    pub(crate) disabled: bool,
521    pub(crate) on_click: Option<Msg>,
522    pub(crate) on_double_click: Option<Msg>,
523    pub(crate) on_right_click: Option<Msg>,
524    pub(crate) on_hover: Option<Msg>,
525    pub(crate) on_key: Option<KeyFn<Msg>>,
526    pub(crate) on_drag: Option<DragFn<Msg>>,
527    /// Fired once on pointer release after a captured drag (the gesture
528    /// that drove [`Self::on_drag`] ended). Only meaningful alongside
529    /// `on_drag`; used for drag lifecycles like column-resize commit.
530    pub(crate) on_drag_end: Option<Msg>,
531    /// Fired when a press-drag-release on this element is recognized as a swipe
532    /// (a fast flick past a small distance), with the dominant [`SwipeDir`].
533    pub(crate) on_swipe: Option<SwipeFn<Msg>>,
534    pub(crate) on_input: Option<InputFn<Msg>>,
535    pub(crate) on_close: Option<Msg>,
536    pub(crate) on_file_drop: Option<FileDropFn<Msg>>,
537    /// Payload announced when a pointer drag starts on this element.
538    pub(crate) drag_source: Option<String>,
539    pub(crate) on_drop: Option<DropFn<Msg>>,
540    pub(crate) overlay: Option<Overlay>,
541    /// Continuous rotation period in ms (spinners); paint-time, clock-driven.
542    pub(crate) spin: Option<f32>,
543    /// Looping keyframe timeline sampled from the frame clock.
544    pub(crate) keyframes: Option<crate::style::Keyframes>,
545    /// Virtualized rows: materialized from scroll state at build time.
546    pub(crate) virtual_rows: Option<VirtualData<Msg>>,
547    /// Container query: a transparent wrapper that `build` replaces with the
548    /// element its closure builds from this container's own measured size.
549    pub(crate) responsive: Option<ResponsiveData<Msg>>,
550    /// Accessible role and state (kit widgets set it; leaves auto-project).
551    pub(crate) semantics: Option<Semantics>,
552    /// Explicit accessible value (ARIA `valuetext`), overriding the input-derived
553    /// one. Widgets like spinbutton/meter set it to a formatted value string.
554    pub(crate) access_value: Option<String>,
555    /// Announce content changes to assistive technology (polite).
556    pub(crate) live: bool,
557    /// Static text: users can drag-select and copy it.
558    pub(crate) selectable: bool,
559    /// Fade-in transition seeded when the id first appears.
560    pub(crate) enter: Option<crate::style::Transition>,
561    /// Exit animation: a paint-only ghost lingers and animates out when the
562    /// id is removed from the tree (the counterpart of [`Self::enter`]).
563    pub(crate) exit: Option<ExitAnim>,
564    /// FLIP/shared-element layout animation: when the element's measured rect
565    /// moves between frames it slides from the old position to the new.
566    pub(crate) animate_layout: bool,
567    /// Buffered type-ahead while focused (1s window per keystroke).
568    pub(crate) on_type_ahead: Option<TypeAheadFn<Msg>>,
569    /// Accessible name (screen-reader label).
570    pub(crate) label: Option<String>,
571    pub(crate) themed: Option<ThemedFn>,
572    pub(crate) hover_style: Option<ThemedFn>,
573    pub(crate) active_style: Option<ThemedFn>,
574    pub(crate) focus_style: Option<ThemedFn>,
575    /// Uniform Material state layer: resolves the content color veiled over the
576    /// container on hover/focus/press/drag (replaces per-state color swaps).
577    pub(crate) state_layer: Option<ContentFn>,
578    /// Dip to [`crate::tokens::PRESS_SCALE`] while pressed.
579    pub(crate) press_scale: bool,
580    /// Recolor the focus ring (and swapped border) to the danger hue.
581    pub(crate) invalid: bool,
582    pub(crate) transition: Option<Transition>,
583}
584
585impl<Msg> Element<Msg> {
586    #[track_caller]
587    fn new(kind: Kind) -> Self {
588        Self {
589            kind,
590            style: Style::default(),
591            children: Vec::new(),
592            key: None,
593            source: std::panic::Location::caller(),
594            stack: false,
595            focusable: false,
596            autofocus: false,
597            stick_bottom: false,
598            cursor: None,
599            disabled: false,
600            on_click: None,
601            on_double_click: None,
602            on_right_click: None,
603            on_hover: None,
604            on_key: None,
605            on_drag: None,
606            on_drag_end: None,
607            on_swipe: None,
608            on_input: None,
609            on_close: None,
610            on_file_drop: None,
611            drag_source: None,
612            on_drop: None,
613            overlay: None,
614            spin: None,
615            keyframes: None,
616            virtual_rows: None,
617            responsive: None,
618            semantics: None,
619            access_value: None,
620            live: false,
621            selectable: false,
622            enter: None,
623            exit: None,
624            animate_layout: false,
625            on_type_ahead: None,
626            label: None,
627            themed: None,
628            hover_style: None,
629            active_style: None,
630            focus_style: None,
631            state_layer: None,
632            press_scale: false,
633            invalid: false,
634            transition: None,
635        }
636    }
637
638    /// The element's style (read access for tests and tooling).
639    pub fn style(&self) -> &Style {
640        &self.style
641    }
642
643    /// Appends children. Anything convertible to an element works, so kit
644    /// widget builders drop in next to `text()`/`div()` trees.
645    pub fn children<M>(mut self, children: impl crate::IntoChildren<Msg, M>) -> Self {
646        self.children.extend(children.into_children());
647        self
648    }
649
650    /// Appends one child.
651    pub fn child(mut self, child: impl Into<Element<Msg>>) -> Self {
652        self.children.push(child.into());
653        self
654    }
655
656    /// Sets a user key for stable identity across reorders.
657    pub fn id(mut self, key: &str) -> Self {
658        self.key = Some(key.to_owned());
659        self
660    }
661
662    /// Emits this message when the element is clicked.
663    pub fn on_click(mut self, msg: Msg) -> Self {
664        self.on_click = Some(msg);
665        self.focusable = true;
666        if self.cursor.is_none() {
667            self.cursor = Some(Cursor::Pointer);
668        }
669        self
670    }
671
672    /// Style overlay applied while hovered.
673    pub fn hover(mut self, f: impl Fn(Style) -> Style + 'static) -> Self {
674        self.hover_style = Some(Box::new(move |_, s| f(s)));
675        self
676    }
677
678    /// Theme-aware hover overlay (used by kit widgets, which have no theme
679    /// in scope at build time).
680    pub fn hover_themed(mut self, f: impl Fn(&Theme, Style) -> Style + 'static) -> Self {
681        self.hover_style = Some(Box::new(f));
682        self
683    }
684
685    /// Style overlay applied while pressed.
686    pub fn active(mut self, f: impl Fn(Style) -> Style + 'static) -> Self {
687        self.active_style = Some(Box::new(move |_, s| f(s)));
688        self
689    }
690
691    /// Theme-aware active overlay.
692    pub fn active_themed(mut self, f: impl Fn(&Theme, Style) -> Style + 'static) -> Self {
693        self.active_style = Some(Box::new(f));
694        self
695    }
696
697    /// Style overlay applied while focused.
698    pub fn focus(mut self, f: impl Fn(Style) -> Style + 'static) -> Self {
699        self.focus_style = Some(Box::new(move |_, s| f(s)));
700        self
701    }
702
703    /// Theme-aware focus overlay.
704    pub fn focus_themed(mut self, f: impl Fn(&Theme, Style) -> Style + 'static) -> Self {
705        self.focus_style = Some(Box::new(f));
706        self
707    }
708
709    /// Drives the uniform Material state layer for this control. `content`
710    /// resolves the color drawn *on* the control (its label/icon color); a
711    /// translucent veil of it is laid over the container on hover, keyboard
712    /// focus, press, and drag at the [`crate::tokens::STATE_LAYER`] opacities —
713    /// one recipe instead of per-state color swaps. A disabled control fades
714    /// its container toward the resting surface. The veil animates through the
715    /// element's transition like any fill change.
716    pub fn state_layer(mut self, content: impl Fn(&Theme) -> Color + 'static) -> Self {
717        self.state_layer = Some(Box::new(content));
718        self
719    }
720
721    /// Dips the control to [`crate::tokens::PRESS_SCALE`] while pressed
722    /// (pointer down) — a tactile shrink that animates and never disturbs
723    /// layout or hit-testing.
724    pub fn press_scale(mut self) -> Self {
725        self.press_scale = true;
726        self
727    }
728
729    /// Marks the control invalid: its keyboard focus ring and swapped border
730    /// recolor to the danger hue (shadcn's `aria-invalid` ring).
731    pub fn invalid(mut self, invalid: bool) -> Self {
732        self.invalid = invalid;
733        self
734    }
735
736    /// Theme-deferred base styling, applied during style resolution. This is
737    /// how kit widgets route every color through tokens without a theme in
738    /// scope: `view()` has no theme parameter.
739    pub fn themed(mut self, f: impl Fn(&Theme, Style) -> Style + 'static) -> Self {
740        self.themed = Some(match self.themed.take() {
741            Some(prev) => Box::new(move |t, s| f(t, prev(t, s))),
742            None => Box::new(f),
743        });
744        self
745    }
746
747    /// Emits this message when the element is clicked twice within 400ms
748    /// (the single-click message fires for both clicks too).
749    pub fn on_double_click(mut self, msg: Msg) -> Self {
750        self.on_double_click = Some(msg);
751        self
752    }
753
754    /// Emits this message on a right-button press over the element (the
755    /// context-menu gesture; fires on press, like macOS).
756    pub fn on_right_click(mut self, msg: Msg) -> Self {
757        self.on_right_click = Some(msg);
758        self
759    }
760
761    /// Emits this message when the pointer enters the element.
762    pub fn on_hover(mut self, msg: Msg) -> Self {
763        self.on_hover = Some(msg);
764        self
765    }
766
767    /// Maps key presses (while focused) to messages.
768    pub fn on_key(mut self, f: impl Fn(&KeyInput) -> Option<Msg> + 'static) -> Self {
769        self.on_key = Some(Box::new(f));
770        self.focusable = true;
771        self
772    }
773
774    /// Maps pointer presses and captured drags to messages. The callback
775    /// receives the pointer position as fractions (0..=1) of the element
776    /// rect on both axes.
777    pub fn on_drag(mut self, f: impl Fn(f32, f32) -> Option<Msg> + 'static) -> Self {
778        self.on_drag = Some(Box::new(f));
779        self
780    }
781
782    /// Emits a message once when a captured drag (see [`Self::on_drag`])
783    /// ends on pointer release. It fires only when this element actually
784    /// captured the press as a drag, so a plain click on a different
785    /// (click-only) element never triggers it. Pairs with `on_drag` to
786    /// model gesture lifecycles — e.g. committing a column resize.
787    pub fn on_drag_end(mut self, msg: Msg) -> Self {
788        self.on_drag_end = Some(msg);
789        self
790    }
791
792    /// Recognizes a swipe (flick) on this element: a press, a quick drag past a
793    /// small threshold, and release fire the closure with the dominant
794    /// [`SwipeDir`]. Good for carousels, dismissible cards, and back gestures —
795    /// the element captures the press, so it works without `on_drag`.
796    pub fn on_swipe(mut self, f: impl Fn(SwipeDir) -> Msg + 'static) -> Self {
797        self.on_swipe = Some(Box::new(f));
798        self
799    }
800
801    /// Maps each text edit of an input element to a message.
802    pub fn on_input(mut self, f: impl Fn(&str) -> Msg + 'static) -> Self {
803        self.on_input = Some(Box::new(f));
804        self
805    }
806
807    /// Maps an OS file dropped onto this element to a message (delivered
808    /// at the last pointer position; with no hit, the first handler in the
809    /// tree receives it). The OS sends one event per dropped file.
810    pub fn on_file_drop(mut self, f: impl Fn(&std::path::Path) -> Msg + 'static) -> Self {
811        self.on_file_drop = Some(Box::new(f));
812        self
813    }
814
815    /// Marks this element as an internal drag source carrying a string
816    /// payload: pressing on it starts a drag, and releasing over an
817    /// element with [`Self::on_drop`] delivers the payload there.
818    pub fn drag_source(mut self, payload: impl Into<String>) -> Self {
819        self.drag_source = Some(payload.into());
820        self
821    }
822
823    /// Receives internal drag payloads released over this element; return
824    /// `None` to reject. Style the drag with `.active(..)` on the source.
825    pub fn on_drop(mut self, f: impl Fn(&str) -> Option<Msg> + 'static) -> Self {
826        self.on_drop = Some(Box::new(f));
827        self
828    }
829
830    /// Marks this element as an overlay child of its parent (the anchor).
831    pub fn overlay(mut self, overlay: Overlay) -> Self {
832        self.overlay = Some(overlay);
833        self
834    }
835
836    /// Emitted when an app-driven overlay is dismissed (outside click, Esc).
837    pub fn on_close(mut self, msg: Msg) -> Self {
838        self.on_close = Some(msg);
839        self
840    }
841
842    /// Rotates the element's painted content continuously (one turn per
843    /// `period_ms`). Only path elements rotate; used by spinners.
844    pub fn spin(mut self, period_ms: f32) -> Self {
845        self.spin = Some(period_ms);
846        self
847    }
848
849    /// Optically overshoots a path icon ([`crate::optical::CIRCLE_OVERSHOOT`]):
850    /// scales the drawn path up about the viewbox center so a round or pointed
851    /// glyph reads the same visual size as square-edged neighbors. No-op on
852    /// non-path elements; off by default (existing icons are unchanged).
853    pub fn optical_overshoot(mut self) -> Self {
854        if let Kind::Path(p) = &mut self.kind {
855            p.optical.overshoot = true;
856        }
857        self
858    }
859
860    /// Centers a path icon on its visual mass ([`crate::optical::centroid`])
861    /// rather than its bounding box — the play-triangle correction. No-op on
862    /// non-path elements; off by default.
863    pub fn optical_center(mut self) -> Self {
864        if let Kind::Path(p) = &mut self.kind {
865            p.optical.center = true;
866        }
867        self
868    }
869
870    /// Attaches a looping [`Keyframes`](crate::style::Keyframes) timeline,
871    /// sampled from the frame clock after `themed`, interaction variants,
872    /// and transitions resolve. Reduced motion pins the first stop.
873    pub fn keyframes(mut self, keyframes: crate::style::Keyframes) -> Self {
874        self.keyframes = Some(keyframes);
875        self
876    }
877
878    /// The most rows a virtualized container will address. The fixed-height
879    /// window (`virtual_rows`) is O(1) in `count` — window math only — but
880    /// the variable-height index (`virtual_rows_variable`) builds a
881    /// `heights`/`prefix` `Vec<f32>` per row (`HeightIndex::ensure`), so an
882    /// unclamped hostile `count` (`usize::MAX`) attempts a
883    /// capacity-overflow-panicking allocation before any window is computed.
884    /// Ten million rows is far beyond any in-memory dataset a real app would
885    /// virtualize in one list, so the ceiling never affects realistic usage
886    /// while keeping worst-case allocation in the tens-of-MB range.
887    const MAX_VIRTUAL_ROWS: usize = 10_000_000;
888
889    /// Virtualizes this container's rows: `builder(i)` is called only for
890    /// the rows inside the scrolled-into-view window (plus overscan), with
891    /// spacers standing in for the rest, so a 100k-row list builds a
892    /// screenful of nodes per frame. Rows are forced to `row_height` and
893    /// keyed by index. Pair with `.scroll_y()` and a stable `.id(..)` (the
894    /// kit's `virtual_list` does both). Overlays inside virtual rows are
895    /// not supported.
896    pub fn virtual_rows(
897        mut self,
898        count: usize,
899        row_height: f32,
900        builder: impl Fn(usize) -> Element<Msg> + 'static,
901    ) -> Self {
902        self.virtual_rows = Some(VirtualData {
903            count: count.min(Self::MAX_VIRTUAL_ROWS),
904            row_height,
905            variable: false,
906            builder: std::rc::Rc::new(builder),
907        });
908        self
909    }
910
911    /// Like [`Self::virtual_rows`], but rows size themselves:
912    /// `estimated_height` positions unmaterialized rows, and real
913    /// heights are measured as rows appear (offsets self-correct over
914    /// the next frame). Constraints: no overlays inside rows.
915    pub fn virtual_rows_variable(
916        mut self,
917        count: usize,
918        estimated_height: f32,
919        builder: impl Fn(usize) -> Element<Msg> + 'static,
920    ) -> Self {
921        self.virtual_rows = Some(VirtualData {
922            count: count.min(Self::MAX_VIRTUAL_ROWS),
923            row_height: estimated_height,
924            variable: true,
925            builder: std::rc::Rc::new(builder),
926        });
927        self
928    }
929
930    /// Maps the focused element's accumulated type-ahead buffer to a
931    /// message: printable keystrokes append (1s window between them,
932    /// Escape clears), and the handler sees the whole buffer — "che"
933    /// jumps a select to "Cherry" instead of cycling C-entries.
934    pub fn on_type_ahead(mut self, f: impl Fn(&str) -> Option<Msg> + 'static) -> Self {
935        self.on_type_ahead = Some(Box::new(f));
936        self
937    }
938
939    /// Animates the element in when it first appears (a fade from
940    /// opacity 0 through the given transition — give stateful entries a
941    /// stable `.id` so reorders don't retrigger it). Pair with
942    /// [`Self::exit`] to also animate removal.
943    pub fn enter(mut self, transition: crate::style::Transition) -> Self {
944        self.enter = Some(crate::style::Transition {
945            opacity: true,
946            ..transition
947        });
948        self
949    }
950
951    /// Animates the element *out* when it is removed from the tree: a
952    /// paint-only snapshot ("ghost") is left in place and fades to transparent
953    /// over `transition`, then dropped (the counterpart of [`Self::enter`]).
954    /// Give stateful entries a stable [`Self::id`] so removal is detected by
955    /// identity, not list position. Inert under reduced motion (removal is
956    /// immediate). Use [`Self::exit_to`] to also scale or slide the ghost.
957    pub fn exit(mut self, transition: crate::style::Transition) -> Self {
958        self.exit = Some(ExitAnim {
959            transition: crate::style::Transition {
960                opacity: true,
961                ..transition
962            },
963            opacity_to: 0.0,
964            scale_to: 1.0,
965            translate_to: (0.0, 0.0),
966        });
967        self
968    }
969
970    /// Like [`Self::exit`], but the leaving ghost animates toward an explicit
971    /// `opacity`, `scale` (about its center), and `(dx, dy)` translation over
972    /// the standard exit timing (a brisk accelerate-eased
973    /// [`MotionDuration::exit_ms`](crate::tokens::MotionDuration::exit_ms)).
974    /// For example `exit_to(0.0, 0.96, 0.0, 8.0)` fades a toast out while it
975    /// shrinks slightly and drops away.
976    pub fn exit_to(mut self, opacity: f32, scale: f32, dx: f32, dy: f32) -> Self {
977        self.exit = Some(ExitAnim {
978            transition: crate::style::Transition::all()
979                .duration_ms(crate::tokens::MotionDuration::Base.exit_ms())
980                .easing(crate::tokens::EASE_EXIT),
981            opacity_to: opacity,
982            scale_to: scale,
983            translate_to: (dx, dy),
984        });
985        self
986    }
987
988    /// Animates this element's *position* when layout moves it between frames
989    /// (FLIP / shared-element). Its measured rect is compared with the
990    /// previous frame's; when the center moves, the element is painted
991    /// starting at the old position and springs to the new one, so reordering
992    /// a list (or a resized sibling pushing it) glides instead of jumping.
993    /// Pair with a stable [`Self::id`] — without one, reordering changes the
994    /// `WidgetId`, the previous position is lost under the new identity, and
995    /// the slide never fires. Composes with any static or animated
996    /// `translate`. Inert under reduced motion (the element snaps).
997    ///
998    /// With no explicit [`Self::transition`]/[`Self::enter`] the slide rides an
999    /// implicit spatial spring; a declared transition wins and drives the slide
1000    /// at its own timing instead. Either way that transition is the element's
1001    /// general one, so an incidental style change animates through it too
1002    /// (colors clamp; only position springs here).
1003    pub fn animate_layout(mut self) -> Self {
1004        self.animate_layout = true;
1005        self
1006    }
1007
1008    /// Makes static text selectable: drag (or double/triple-click)
1009    /// selects, Cmd/Ctrl+C copies. For text and rich-text elements.
1010    pub fn selectable(mut self) -> Self {
1011        self.selectable = true;
1012        self
1013    }
1014
1015    /// Marks a live region: assistive technology announces content
1016    /// changes inside it without focus moving there (status lines,
1017    /// toasts — the kit's toast stack sets this itself).
1018    pub fn live(mut self) -> Self {
1019        self.live = true;
1020        self
1021    }
1022
1023    /// Sets the accessible role and state projected into the accessibility
1024    /// tree. Text, image, and input leaves project their role automatically;
1025    /// use this to set one on a custom element, or to override the default.
1026    pub fn semantics(mut self, semantics: Semantics) -> Self {
1027        self.semantics = Some(semantics);
1028        self
1029    }
1030
1031    /// Sets the accessible name announced by screen readers (text leaves
1032    /// use their content automatically).
1033    pub fn label(mut self, label: impl Into<String>) -> Self {
1034        self.label = Some(label.into());
1035        self
1036    }
1037
1038    /// Sets the accessible value (ARIA `valuetext`) for a non-input control —
1039    /// e.g. a spinbutton's formatted "$5.00" or a meter's "75%". Text inputs
1040    /// derive their value from the edited text; this overrides it.
1041    pub fn value(mut self, value: impl Into<String>) -> Self {
1042        self.access_value = Some(value.into());
1043        self
1044    }
1045
1046    /// Disables interaction: handlers and variants stop applying.
1047    pub fn disabled(mut self, disabled: bool) -> Self {
1048        self.disabled = disabled;
1049        self
1050    }
1051
1052    /// Declares which properties animate between style states.
1053    pub fn transition(mut self, t: Transition) -> Self {
1054        self.transition = Some(t);
1055        self
1056    }
1057
1058    /// Marks the element keyboard-focusable.
1059    pub fn focusable(mut self, focusable: bool) -> Self {
1060        self.focusable = focusable;
1061        self
1062    }
1063
1064    /// Focuses this element when it newly appears in the tree (opening a
1065    /// modal focuses its input). It does not steal focus while it stays
1066    /// mounted, and refocuses when it disappears and reappears. Give at
1067    /// most one element autofocus per view state.
1068    pub fn autofocus(mut self) -> Self {
1069        self.autofocus = true;
1070        self.focusable = true;
1071        self
1072    }
1073
1074    /// Sets the hover cursor.
1075    pub fn cursor(mut self, cursor: Cursor) -> Self {
1076        self.cursor = Some(cursor);
1077        self
1078    }
1079
1080    // ----- style delegation: every fluent `Style` method, on the element -----
1081
1082    /// Padding on all edges.
1083    pub fn p(mut self, v: f32) -> Self {
1084        self.style = self.style.p(v);
1085        self
1086    }
1087
1088    /// Horizontal padding.
1089    pub fn px(mut self, v: f32) -> Self {
1090        self.style = self.style.px(v);
1091        self
1092    }
1093
1094    /// Vertical padding.
1095    pub fn py(mut self, v: f32) -> Self {
1096        self.style = self.style.py(v);
1097        self
1098    }
1099
1100    /// Top padding.
1101    pub fn pt(mut self, v: f32) -> Self {
1102        self.style = self.style.pt(v);
1103        self
1104    }
1105
1106    /// Right padding.
1107    pub fn pr(mut self, v: f32) -> Self {
1108        self.style = self.style.pr(v);
1109        self
1110    }
1111
1112    /// Bottom padding.
1113    pub fn pb(mut self, v: f32) -> Self {
1114        self.style = self.style.pb(v);
1115        self
1116    }
1117
1118    /// Left padding.
1119    pub fn pl(mut self, v: f32) -> Self {
1120        self.style = self.style.pl(v);
1121        self
1122    }
1123
1124    /// Margin on all edges.
1125    pub fn m(mut self, v: f32) -> Self {
1126        self.style = self.style.m(v);
1127        self
1128    }
1129
1130    /// Horizontal margin.
1131    pub fn mx(mut self, v: f32) -> Self {
1132        self.style = self.style.mx(v);
1133        self
1134    }
1135
1136    /// Vertical margin.
1137    pub fn my(mut self, v: f32) -> Self {
1138        self.style = self.style.my(v);
1139        self
1140    }
1141
1142    /// Top margin.
1143    pub fn mt(mut self, v: f32) -> Self {
1144        self.style = self.style.mt(v);
1145        self
1146    }
1147
1148    /// Right margin.
1149    pub fn mr(mut self, v: f32) -> Self {
1150        self.style = self.style.mr(v);
1151        self
1152    }
1153
1154    /// Bottom margin.
1155    pub fn mb(mut self, v: f32) -> Self {
1156        self.style = self.style.mb(v);
1157        self
1158    }
1159
1160    /// Left margin.
1161    pub fn ml(mut self, v: f32) -> Self {
1162        self.style = self.style.ml(v);
1163        self
1164    }
1165
1166    /// Gap between children.
1167    pub fn gap(mut self, v: f32) -> Self {
1168        self.style = self.style.gap(v);
1169        self
1170    }
1171
1172    /// Preferred width (raw `f32` = logical px).
1173    pub fn w(mut self, v: impl Into<Length>) -> Self {
1174        self.style = self.style.w(v);
1175        self
1176    }
1177
1178    /// Preferred height.
1179    pub fn h(mut self, v: impl Into<Length>) -> Self {
1180        self.style = self.style.h(v);
1181        self
1182    }
1183
1184    /// Minimum width.
1185    pub fn min_w(mut self, v: impl Into<Length>) -> Self {
1186        self.style = self.style.min_w(v);
1187        self
1188    }
1189
1190    /// Maximum width.
1191    pub fn max_w(mut self, v: impl Into<Length>) -> Self {
1192        self.style = self.style.max_w(v);
1193        self
1194    }
1195
1196    /// Minimum height.
1197    pub fn min_h(mut self, v: impl Into<Length>) -> Self {
1198        self.style = self.style.min_h(v);
1199        self
1200    }
1201
1202    /// Maximum height.
1203    pub fn max_h(mut self, v: impl Into<Length>) -> Self {
1204        self.style = self.style.max_h(v);
1205        self
1206    }
1207
1208    /// Caps this element's width at a reading measure of `chars` characters
1209    /// (a `ch`-based `max-width`); see [`Style::measure`](crate::Style::measure).
1210    pub fn measure(mut self, chars: f32) -> Self {
1211        self.style = self.style.measure(chars);
1212        self
1213    }
1214
1215    /// Preferred width in `ch` units (see [`Length::Ch`](crate::Length::Ch)).
1216    pub fn w_ch(mut self, chars: f32) -> Self {
1217        self.style = self.style.w_ch(chars);
1218        self
1219    }
1220
1221    /// Minimum width in `ch` units.
1222    pub fn min_w_ch(mut self, chars: f32) -> Self {
1223        self.style = self.style.min_w_ch(chars);
1224        self
1225    }
1226
1227    /// Maximum width in `ch` units (alias of [`Element::measure`]).
1228    pub fn max_w_ch(mut self, chars: f32) -> Self {
1229        self.style = self.style.max_w_ch(chars);
1230        self
1231    }
1232
1233    /// Width 100%.
1234    pub fn w_full(mut self) -> Self {
1235        self.style = self.style.w_full();
1236        self
1237    }
1238
1239    /// Height 100%.
1240    pub fn h_full(mut self) -> Self {
1241        self.style = self.style.h_full();
1242        self
1243    }
1244
1245    /// Flex grow 1.
1246    pub fn grow(mut self) -> Self {
1247        self.style = self.style.grow();
1248        self
1249    }
1250
1251    /// Flex shrink 0.
1252    pub fn shrink0(mut self) -> Self {
1253        self.style = self.style.shrink0();
1254        self
1255    }
1256
1257    /// Align children to the cross-axis start.
1258    pub fn items_start(mut self) -> Self {
1259        self.style = self.style.items_start();
1260        self
1261    }
1262
1263    /// Center children on the cross axis.
1264    pub fn items_center(mut self) -> Self {
1265        self.style = self.style.items_center();
1266        self
1267    }
1268
1269    /// Align children to the cross-axis end.
1270    pub fn items_end(mut self) -> Self {
1271        self.style = self.style.items_end();
1272        self
1273    }
1274
1275    /// Align children on their first text baseline (rows only).
1276    pub fn items_baseline(mut self) -> Self {
1277        self.style = self.style.items_baseline();
1278        self
1279    }
1280
1281    /// Override the parent's cross-axis alignment for this element alone,
1282    /// hugging its content at the cross-axis start instead of stretching.
1283    pub fn self_start(mut self) -> Self {
1284        self.style = self.style.self_start();
1285        self
1286    }
1287
1288    /// Override the parent's cross-axis alignment for this element alone,
1289    /// centering it on the cross axis.
1290    pub fn self_center(mut self) -> Self {
1291        self.style = self.style.self_center();
1292        self
1293    }
1294
1295    /// Override the parent's cross-axis alignment for this element alone,
1296    /// packing it toward the cross-axis end.
1297    pub fn self_end(mut self) -> Self {
1298        self.style = self.style.self_end();
1299        self
1300    }
1301
1302    /// Override the parent's cross-axis alignment for this element alone,
1303    /// stretching it to fill the cross axis.
1304    pub fn self_stretch(mut self) -> Self {
1305        self.style = self.style.self_stretch();
1306        self
1307    }
1308
1309    /// Pack children toward the main-axis start.
1310    pub fn justify_start(mut self) -> Self {
1311        self.style = self.style.justify_start();
1312        self
1313    }
1314
1315    /// Center children on the main axis.
1316    pub fn justify_center(mut self) -> Self {
1317        self.style = self.style.justify_center();
1318        self
1319    }
1320
1321    /// Pack children toward the main-axis end.
1322    pub fn justify_end(mut self) -> Self {
1323        self.style = self.style.justify_end();
1324        self
1325    }
1326
1327    /// Distribute children with space between.
1328    pub fn justify_between(mut self) -> Self {
1329        self.style = self.style.justify_between();
1330        self
1331    }
1332
1333    /// Allow flex children to wrap.
1334    pub fn wrap(mut self) -> Self {
1335        self.style = self.style.wrap();
1336        self
1337    }
1338
1339    /// Position absolutely against the nearest relative ancestor.
1340    pub fn absolute(mut self) -> Self {
1341        self.style = self.style.absolute();
1342        self
1343    }
1344
1345    /// Offset from the top.
1346    pub fn top(mut self, v: f32) -> Self {
1347        self.style = self.style.top(v);
1348        self
1349    }
1350
1351    /// Offset from the right.
1352    pub fn right(mut self, v: f32) -> Self {
1353        self.style = self.style.right(v);
1354        self
1355    }
1356
1357    /// Offset from the bottom.
1358    pub fn bottom(mut self, v: f32) -> Self {
1359        self.style = self.style.bottom(v);
1360        self
1361    }
1362
1363    /// Offset from the left.
1364    pub fn left(mut self, v: f32) -> Self {
1365        self.style = self.style.left(v);
1366        self
1367    }
1368
1369    /// Clip children to the bounds.
1370    pub fn overflow_hidden(mut self) -> Self {
1371        self.style = self.style.overflow_hidden();
1372        self
1373    }
1374
1375    /// Vertical scrolling with clipped content.
1376    pub fn scroll_y(mut self) -> Self {
1377        self.style = self.style.scroll_y();
1378        self
1379    }
1380
1381    /// Horizontal scrolling with clipped content.
1382    pub fn scroll_x(mut self) -> Self {
1383        self.style = self.style.scroll_x();
1384        self
1385    }
1386
1387    /// Scrolling on both axes with clipped content.
1388    pub fn scroll_xy(mut self) -> Self {
1389        self.style = self.style.scroll_xy();
1390        self
1391    }
1392
1393    /// Sticks `offset` px below the scroll viewport's top edge (`position: sticky`).
1394    pub fn sticky_top(mut self, offset: f32) -> Self {
1395        self.style = self.style.sticky_top(offset);
1396        self
1397    }
1398
1399    /// Sticks `offset` px above the scroll viewport's bottom edge.
1400    pub fn sticky_bottom(mut self, offset: f32) -> Self {
1401        self.style = self.style.sticky_bottom(offset);
1402        self
1403    }
1404
1405    /// Sticks `offset` px right of the scroll viewport's left edge.
1406    pub fn sticky_left(mut self, offset: f32) -> Self {
1407        self.style = self.style.sticky_left(offset);
1408        self
1409    }
1410
1411    /// Sticks `offset` px left of the scroll viewport's right edge.
1412    pub fn sticky_right(mut self, offset: f32) -> Self {
1413        self.style = self.style.sticky_right(offset);
1414        self
1415    }
1416
1417    /// Keeps a scroll container pinned to its bottom edge while content
1418    /// grows — until the user scrolls away, and again once they return to
1419    /// the bottom (the chat/log pattern). Starts at the bottom.
1420    pub fn stick_to_bottom(mut self) -> Self {
1421        self.stick_bottom = true;
1422        self
1423    }
1424
1425    /// Background fill.
1426    pub fn bg(mut self, paint: impl Into<Paint>) -> Self {
1427        self.style = self.style.bg(paint);
1428        self
1429    }
1430
1431    /// Uniform border (a stroke on the element's edge).
1432    pub fn border(mut self, width: f32, color: Color) -> Self {
1433        self.style = self.style.border(width, color);
1434        self
1435    }
1436
1437    /// A border stroke on just the top edge — a straight hairline (square
1438    /// corners) for ruled layouts, no manual divider child needed. See
1439    /// [`Style::border_top`](crate::Style::border_top).
1440    pub fn border_top(mut self, width: f32, color: Color) -> Self {
1441        self.style = self.style.border_top(width, color);
1442        self
1443    }
1444
1445    /// A border stroke on just the right edge. See
1446    /// [`Style::border_right`](crate::Style::border_right).
1447    pub fn border_right(mut self, width: f32, color: Color) -> Self {
1448        self.style = self.style.border_right(width, color);
1449        self
1450    }
1451
1452    /// A border stroke on just the bottom edge — a header/row rule. See
1453    /// [`Style::border_bottom`](crate::Style::border_bottom).
1454    pub fn border_bottom(mut self, width: f32, color: Color) -> Self {
1455        self.style = self.style.border_bottom(width, color);
1456        self
1457    }
1458
1459    /// A border stroke on just the left edge — an accent rail. See
1460    /// [`Style::border_left`](crate::Style::border_left).
1461    pub fn border_left(mut self, width: f32, color: Color) -> Self {
1462        self.style = self.style.border_left(width, color);
1463        self
1464    }
1465
1466    /// A crisp `width`-px ring just outside the box, hugging the corner radius
1467    /// (see [`Style::ring`](crate::Style::ring)) — the "ring, not border" look:
1468    /// outside the element, zero layout cost, ideal for selection rings and
1469    /// sub-pixel hairlines.
1470    pub fn ring(mut self, width: f32, color: Color) -> Self {
1471        self.style = self.style.ring(width, color);
1472        self
1473    }
1474
1475    /// The same corner radius on all corners.
1476    pub fn rounded(mut self, r: f32) -> Self {
1477        self.style = self.style.rounded(r);
1478        self
1479    }
1480
1481    /// Fully-rounded corners.
1482    pub fn rounded_full(mut self) -> Self {
1483        self.style = self.style.rounded_full();
1484        self
1485    }
1486
1487    /// Rounds the top two corners only.
1488    pub fn rounded_t(mut self, r: f32) -> Self {
1489        self.style = self.style.rounded_t(r);
1490        self
1491    }
1492
1493    /// Rounds the bottom two corners only.
1494    pub fn rounded_b(mut self, r: f32) -> Self {
1495        self.style = self.style.rounded_b(r);
1496        self
1497    }
1498
1499    /// Rounds the left two corners only.
1500    pub fn rounded_l(mut self, r: f32) -> Self {
1501        self.style = self.style.rounded_l(r);
1502        self
1503    }
1504
1505    /// Rounds the right two corners only.
1506    pub fn rounded_r(mut self, r: f32) -> Self {
1507        self.style = self.style.rounded_r(r);
1508        self
1509    }
1510
1511    /// Sets each corner radius independently (top-left, top-right,
1512    /// bottom-right, bottom-left).
1513    pub fn corners(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
1514        self.style = self.style.corners(tl, tr, br, bl);
1515        self
1516    }
1517
1518    /// Paint-time translation in logical px (never affects layout). Animatable.
1519    pub fn translate(mut self, x: f32, y: f32) -> Self {
1520        self.style = self.style.translate(x, y);
1521        self
1522    }
1523
1524    /// Paint-time rotation in degrees about the element center. Animatable.
1525    pub fn rotate(mut self, degrees: f32) -> Self {
1526        self.style = self.style.rotate(degrees);
1527        self
1528    }
1529
1530    /// Paint-time skew in degrees `(x, y)` about the element center. Animatable.
1531    pub fn skew(mut self, x_degrees: f32, y_degrees: f32) -> Self {
1532        self.style = self.style.skew(x_degrees, y_degrees);
1533        self
1534    }
1535
1536    /// Non-uniform paint-time scale `(x, y)`, composed with the uniform
1537    /// press scale. Never disturbs layout; animatable.
1538    pub fn scale_xy(mut self, x: f32, y: f32) -> Self {
1539        self.style = self.style.scale_xy(x, y);
1540        self
1541    }
1542
1543    /// The pivot for this element's paint-time transforms, as a fraction of
1544    /// its rect (CSS `transform-origin`): `(0, 0)` top-left, `(0.5, 0.5)`
1545    /// center (the default).
1546    pub fn transform_origin(mut self, fx: f32, fy: f32) -> Self {
1547        self.style = self.style.transform_origin(fx, fy);
1548        self
1549    }
1550
1551    /// Continuous-curvature corner smoothing, `0.0..=1.0` (see
1552    /// [`Style::corner_smoothing`](crate::Style::corner_smoothing)). `0.0`
1553    /// (default) keeps exact circular arcs; higher values blend toward a
1554    /// fuller squircle.
1555    pub fn corner_smoothing(mut self, s: f32) -> Self {
1556        self.style = self.style.corner_smoothing(s);
1557        self
1558    }
1559
1560    /// A themed shadow elevation token.
1561    pub fn shadow(mut self, token: ShadowToken) -> Self {
1562        self.style = self.style.shadow(token);
1563        self
1564    }
1565
1566    /// Applies a [`Surface`](crate::Surface) material — the kit ergonomic. The
1567    /// fill, border, radius, shadow, and highlight resolve at theme-resolution
1568    /// time (so it needs no theme in `view()`), replacing the
1569    /// `.rounded(..).shadow(..).themed(|t, s| s.bg(..).border(..))` combo. Call
1570    /// it once as the element's material; chain a `.themed` after it to tweak a
1571    /// single property.
1572    pub fn surface(self, role: crate::surface::Surface) -> Self {
1573        self.themed(move |t, s| {
1574            let mut s = role.bundle().apply(t, s).rounded(role.radius_px(&t.radius));
1575            // Flat elevation drops the shadow on resting cards (border + tone
1576            // carry separation); floating roles always keep theirs.
1577            if t.elevation == crate::Elevation::Flat
1578                && matches!(
1579                    role,
1580                    crate::surface::Surface::Card | crate::surface::Surface::Raised
1581                )
1582            {
1583                s.shadow_token = None;
1584            }
1585            s
1586        })
1587    }
1588
1589    /// Subtree opacity.
1590    pub fn opacity(mut self, v: f32) -> Self {
1591        self.style = self.style.opacity(v);
1592        self
1593    }
1594
1595    /// Draw progress for path elements (0 = nothing, 1 = full path).
1596    pub fn trim(mut self, v: f32) -> Self {
1597        self.style = self.style.trim(v);
1598        self
1599    }
1600
1601    /// Frosted-glass backdrop blur in logical px: blurs the content *behind*
1602    /// this translucent element (see [`Style::backdrop_blur`](crate::Style::backdrop_blur)).
1603    /// [`Surface::Glass`](crate::Surface::Glass) sets this for you; reach for
1604    /// this builder to tune the radius or frost a custom translucent pane.
1605    pub fn backdrop_blur(mut self, radius: f32) -> Self {
1606        self.style = self.style.backdrop_blur(radius);
1607        self
1608    }
1609
1610    /// A foreground [`ElementFilter`](crate::ElementFilter) on this element's
1611    /// own content (blur / brightness / saturate).
1612    pub fn element_filter(mut self, filter: crate::ElementFilter) -> Self {
1613        self.style = self.style.element_filter(filter);
1614        self
1615    }
1616
1617    /// A luminous specular edge rim — the Liquid Glass perimeter light — on this
1618    /// element. [`Surface::Glass`](crate::Surface::Glass) sets it for you; reach
1619    /// for this builder to put the rim on a custom translucent pane. See
1620    /// [`SpecularEdge`](crate::SpecularEdge) (and [`SpecularEdge::glass`]).
1621    pub fn specular_edge(mut self, edge: crate::style::SpecularEdge) -> Self {
1622        self.style = self.style.specular_edge(edge);
1623        self
1624    }
1625
1626    /// A directional body sheen — the raking glass light — across this element's
1627    /// face. See [`Sheen`](crate::Sheen) (and [`Sheen::glass`]).
1628    pub fn sheen(mut self, sheen: crate::style::Sheen) -> Self {
1629        self.style = self.style.sheen(sheen);
1630        self
1631    }
1632
1633    /// Backdrop-adaptive vibrancy — shift the glass tint's lightness by the mean
1634    /// luminance of the frosted backdrop behind it (headless-only). See
1635    /// [`AdaptiveTint`](crate::AdaptiveTint) (and [`AdaptiveTint::glass`]).
1636    pub fn adaptive_tint(mut self, adaptive: crate::style::AdaptiveTint) -> Self {
1637        self.style = self.style.adaptive_tint(adaptive);
1638        self
1639    }
1640
1641    /// Text size.
1642    pub fn size(mut self, size: TextSize) -> Self {
1643        self.style = self.style.size(size);
1644        self
1645    }
1646
1647    /// Free-form text size in logical px (editorial display sizes).
1648    pub fn size_px(mut self, px: f32) -> Self {
1649        self.style = self.style.size_px(px);
1650        self
1651    }
1652
1653    /// Letter spacing in em (tracked-out eyebrows, small caps).
1654    pub fn tracking(mut self, em: f32) -> Self {
1655        self.style = self.style.tracking(em);
1656        self
1657    }
1658
1659    /// Line height as a multiple of the font size.
1660    pub fn leading(mut self, multiple: f32) -> Self {
1661        self.style = self.style.leading(multiple);
1662        self
1663    }
1664
1665    /// Tabular (fixed-width) numerals (`tnum`) — digits align in columns. For
1666    /// tables, timers, charts, and numeric data.
1667    pub fn tabular(mut self) -> Self {
1668        self.style = self.style.tabular();
1669        self
1670    }
1671
1672    /// Proportional numerals — individually spaced for prose (`pnum`).
1673    pub fn proportional_nums(mut self) -> Self {
1674        self.style = self.style.proportional_nums();
1675        self
1676    }
1677
1678    /// Old-style / text figures (`onum`): ascending and descending digits
1679    /// that sit naturally in serif prose.
1680    pub fn oldstyle_nums(mut self) -> Self {
1681        self.style = self.style.oldstyle_nums();
1682        self
1683    }
1684
1685    /// Lining figures (`lnum`): uniform cap-height digits for data and UI.
1686    pub fn lining_nums(mut self) -> Self {
1687        self.style = self.style.lining_nums();
1688        self
1689    }
1690
1691    /// Render lowercase letters as small capitals (`smcp`).
1692    pub fn small_caps(mut self) -> Self {
1693        self.style = self.style.small_caps();
1694        self
1695    }
1696
1697    /// Enable or disable standard ligatures (`liga`); most fonts default on.
1698    pub fn ligatures(mut self, on: bool) -> Self {
1699        self.style = self.style.ligatures(on);
1700        self
1701    }
1702
1703    /// Common fractions (`frac`): `1/2` becomes a single fraction glyph.
1704    pub fn fractions(mut self) -> Self {
1705        self.style = self.style.fractions();
1706        self
1707    }
1708
1709    /// Font family role (Sans, Mono, or a registered Display/Serif face).
1710    pub fn family(mut self, family: crate::tokens::FamilyRole) -> Self {
1711        self.style = self.style.family(family);
1712        self
1713    }
1714
1715    /// Font weight.
1716    pub fn weight(mut self, weight: Weight) -> Self {
1717        self.style = self.style.weight(weight);
1718        self
1719    }
1720
1721    /// Text color.
1722    pub fn color(mut self, color: Color) -> Self {
1723        self.style = self.style.color(color);
1724        self
1725    }
1726
1727    /// Mono family role.
1728    pub fn mono(mut self) -> Self {
1729        self.style = self.style.mono();
1730        self
1731    }
1732
1733    /// Truncate to one line with an ellipsis.
1734    pub fn truncate(mut self) -> Self {
1735        self.style = self.style.truncate();
1736        self
1737    }
1738
1739    /// Horizontal text alignment.
1740    pub fn text_align(mut self, align: TextAlign) -> Self {
1741        self.style = self.style.text_align(align);
1742        self
1743    }
1744
1745    /// Balance line lengths ([`TextWrap`](crate::TextWrap)`::Balance`) — for
1746    /// headings and titles.
1747    pub fn balance(mut self) -> Self {
1748        self.style = self.style.balance();
1749        self
1750    }
1751
1752    /// Avoid a stranded last word ([`TextWrap`](crate::TextWrap)`::Pretty`) —
1753    /// best-effort for paragraphs.
1754    pub fn pretty(mut self) -> Self {
1755        self.style = self.style.pretty();
1756        self
1757    }
1758
1759    /// Sets the line-breaking mode explicitly ([`TextWrap`](crate::TextWrap)).
1760    pub fn text_wrap(mut self, wrap: TextWrap) -> Self {
1761        self.style = self.style.text_wrap(wrap);
1762        self
1763    }
1764
1765    /// Sets optical sizing explicitly
1766    /// ([`OpticalSizing`](crate::OpticalSizing)) — how a variable font's
1767    /// `opsz` axis is driven.
1768    pub fn optical(mut self, optical: crate::style::OpticalSizing) -> Self {
1769        self.style = self.style.optical(optical);
1770        self
1771    }
1772
1773    /// Tracks the `opsz` axis to the rendered size
1774    /// ([`OpticalSizing::Auto`](crate::OpticalSizing::Auto)) — small text gets
1775    /// the text-optical master, large sizes the display master. A no-op on
1776    /// static faces (the embedded Inter / mono).
1777    pub fn optical_auto(mut self) -> Self {
1778        self.style = self.style.optical_auto();
1779        self
1780    }
1781}
1782
1783/// A plain container box (flex row by default, like taffy).
1784#[track_caller]
1785pub fn div<Msg>() -> Element<Msg> {
1786    Element::new(Kind::Box)
1787}
1788
1789/// A flex row.
1790#[track_caller]
1791pub fn row<Msg>() -> Element<Msg> {
1792    Element::new(Kind::Box)
1793}
1794
1795/// A flex column.
1796#[track_caller]
1797pub fn col<Msg>() -> Element<Msg> {
1798    let mut el = Element::new(Kind::Box);
1799    el.style.direction = crate::style::Direction::Column;
1800    el
1801}
1802
1803/// A z-stack: children occupy the same rect and paint in order.
1804#[track_caller]
1805pub fn stack<Msg>() -> Element<Msg> {
1806    let mut el = Element::new(Kind::Box);
1807    el.style.display = crate::style::Display::Grid;
1808    el.stack = true;
1809    el
1810}
1811
1812/// A paragraph of styled runs: spans wrap together as one layout, and
1813/// each [`span`] may override weight, color, size, family, or italic.
1814#[track_caller]
1815pub fn rich_text<Msg>(spans: impl IntoIterator<Item = Span>) -> Element<Msg> {
1816    Element::new(Kind::Rich(spans.into_iter().collect()))
1817}
1818
1819/// A text run.
1820#[track_caller]
1821pub fn text<Msg>(content: impl Into<String>) -> Element<Msg> {
1822    Element::new(Kind::Text(content.into()))
1823}
1824
1825/// Flexible empty space (flex grow 1).
1826#[track_caller]
1827pub fn spacer<Msg>() -> Element<Msg> {
1828    Element::new(Kind::Box).grow()
1829}
1830
1831/// A horizontal hairline rule in `border_subtle`, 1px tall and full width.
1832/// For a vertical rule, override with `.w(1.0)` and `.h_full()`.
1833#[track_caller]
1834pub fn divider<Msg>() -> Element<Msg> {
1835    Element::new(Kind::Divider).w_full().h(1.0).shrink0()
1836}
1837
1838/// A container that chooses its own layout from its measured size — a
1839/// **container query**, the counterpart of window-size
1840/// [`App::view_at`](crate::App::view_at). `f(available)` receives this
1841/// container's own content size in logical px *from the previous frame* (the
1842/// layout the motion system already records) and returns the element to lay
1843/// out in its place.
1844///
1845/// It is **one frame deferred**: the very first frame has no measured size, so
1846/// it builds at the hint `(0.0, 0.0)` — the "smallest" branch — and converges
1847/// on the next frame; a later resize re-converges one frame after it lands.
1848/// Reach for [`responsive_hinted`] to seed a first-frame size and skip that
1849/// flash.
1850///
1851/// The returned element is **transparent**: it replaces the `responsive(..)`
1852/// wrapper entirely, so style the element you return, not this call. Two rules
1853/// keep the feedback loop well-behaved:
1854/// - **Stable identity.** Give the wrapper a stable [`Element::id`] if its
1855///   position among its siblings can change, so the query keys off a stable
1856///   [`WidgetId`](crate::WidgetId) across frames (a fixed tree position is
1857///   already stable).
1858/// - **Monotone in width, and never self-wrapping.** `f` should not return
1859///   content that shrinks the container back below a threshold a wider size
1860///   crossed (otherwise it can flip every frame at a boundary — prefer
1861///   width-driven breakpoints on a parent-sized container). Nest a finer
1862///   `responsive(..)` as a *child* of the returned element, never as its root:
1863///   a closure that directly returns another `responsive(..)` chains under the
1864///   same id, and is expanded only a bounded number of times before being
1865///   flattened to empty (it degrades, it never overflows the stack).
1866///
1867/// Like a virtualized list, the generated subtree is not part of the *declared*
1868/// tree, so an [`overlay`](Element::overlay) inside it is silently skipped (the
1869/// overlay machinery indexes the declared tree). Mount overlays outside the
1870/// `responsive` wrapper.
1871#[track_caller]
1872pub fn responsive<Msg>(f: impl Fn((f32, f32)) -> Element<Msg> + 'static) -> Element<Msg> {
1873    responsive_hinted((0.0, 0.0), f)
1874}
1875
1876/// [`responsive`] with an explicit first-frame size: `f(hint)` builds the
1877/// initial frame (before any measurement exists), removing the one-frame
1878/// "smallest branch" flash when the rough size is known up front. See
1879/// [`responsive`] for the convergence model and the identity / monotonicity
1880/// caveats.
1881#[track_caller]
1882pub fn responsive_hinted<Msg>(
1883    hint: (f32, f32),
1884    f: impl Fn((f32, f32)) -> Element<Msg> + 'static,
1885) -> Element<Msg> {
1886    let mut el = Element::new(Kind::Box);
1887    el.responsive = Some(ResponsiveData {
1888        hint,
1889        f: std::rc::Rc::new(f),
1890    });
1891    el
1892}
1893
1894/// A bare single-line text input leaf. Most apps want the styled
1895/// `fenestra_kit` `text_input` instead; this is the primitive it wraps.
1896/// Focusable, shows the text I-beam, and emits `on_input` per edit.
1897#[track_caller]
1898pub fn raw_input<Msg>(value: impl Into<String>, placeholder: impl Into<String>) -> Element<Msg> {
1899    Element::new(Kind::Input(InputData {
1900        value: value.into(),
1901        placeholder: placeholder.into(),
1902        multiline: false,
1903    }))
1904    .focusable(true)
1905    .cursor(Cursor::Text)
1906}
1907
1908/// A bare multiline text area leaf: text wraps to the element width, Enter
1909/// inserts a newline, arrows move by line, and the measured height grows
1910/// with the wrapped content (constrain it with `.min_h`/`.max_h` plus an
1911/// outer scroll container). Most apps want the styled `fenestra_kit`
1912/// `text_area` instead; this is the primitive it wraps.
1913#[track_caller]
1914pub fn raw_text_area<Msg>(
1915    value: impl Into<String>,
1916    placeholder: impl Into<String>,
1917) -> Element<Msg> {
1918    Element::new(Kind::Input(InputData {
1919        value: value.into(),
1920        placeholder: placeholder.into(),
1921        multiline: true,
1922    }))
1923    .focusable(true)
1924    .cursor(Cursor::Text)
1925}
1926
1927/// Package already-decoded straight-alpha RGBA8 pixels (row-major, 4 bytes per
1928/// pixel) into a shareable [`ImageData`]: the buffer becomes an atomically
1929/// reference-counted blob, so cloning the result and handing it to
1930/// [`image_from_data`] shares the one allocation instead of re-decoding or
1931/// copying — the reuse path a decoded-image cache wants. If `pixels` holds
1932/// fewer than `width * height` complete rows, the payload shrinks to the rows
1933/// provided (its `height` reflects that), matching [`image_rgba8`].
1934#[must_use]
1935pub fn image_payload(width: u32, height: u32, mut pixels: Vec<u8>) -> ImageData {
1936    let row = width as usize * 4;
1937    let rows = pixels
1938        .len()
1939        .checked_div(row)
1940        .unwrap_or(0)
1941        .min(height as usize);
1942    pixels.truncate(row * rows);
1943    #[expect(clippy::cast_possible_truncation, reason = "rows <= height: u32")]
1944    let height = rows as u32;
1945    ImageData {
1946        image: peniko::ImageData {
1947            data: pixels.into(),
1948            format: peniko::ImageFormat::Rgba8,
1949            alpha_type: peniko::ImageAlphaType::Alpha,
1950            width,
1951            height,
1952        },
1953    }
1954}
1955
1956/// An image leaf rendering an already-decoded [`ImageData`] payload (from
1957/// [`image_payload`]), sized to it. Building many elements or frames from
1958/// clones of one payload shares a single reference-counted pixel blob, so a
1959/// decoded-image cache never re-decodes or re-copies. See [`image_rgba8`] for
1960/// the owned-`Vec` convenience wrapper.
1961#[must_use]
1962pub fn image_from_data<Msg>(data: ImageData) -> Element<Msg> {
1963    let (width, height) = (data.image.width, data.image.height);
1964    #[expect(clippy::cast_precision_loss, reason = "image sizes fit in f32")]
1965    Element::new(Kind::Image(data))
1966        .w(width as f32)
1967        .h(height as f32)
1968        .shrink0()
1969}
1970
1971/// An image leaf showing straight-alpha RGBA8 pixels (row-major, 4 bytes
1972/// per pixel). Sized to the image by default, stretched when styled
1973/// otherwise, and painted clipped to the corner radius — so
1974/// `.rounded_full()` crops a square source into a round avatar. If `pixels`
1975/// holds fewer than `width * height` complete rows, the element shrinks to
1976/// the rows actually provided instead of panicking.
1977#[track_caller]
1978pub fn image_rgba8<Msg>(width: u32, height: u32, pixels: Vec<u8>) -> Element<Msg> {
1979    image_from_data(image_payload(width, height, pixels))
1980}
1981
1982/// A vector path drawn in `viewbox` coordinates and scaled to the element
1983/// rect (sized to the viewbox by default). `stroke` is a width in viewbox
1984/// units; `None` fills instead. Painted in the resolved text color.
1985#[track_caller]
1986pub fn path<Msg>(bez: kurbo::BezPath, viewbox: (f64, f64), stroke: Option<f64>) -> Element<Msg> {
1987    #[expect(clippy::cast_possible_truncation, reason = "viewbox sizes are small")]
1988    Element::new(Kind::Path(PathData {
1989        path: std::sync::Arc::new(bez),
1990        viewbox,
1991        stroke,
1992        optical: OpticalCorrection::default(),
1993    }))
1994    .w(viewbox.0 as f32)
1995    .h(viewbox.1 as f32)
1996    .shrink0()
1997}
1998
1999impl<Msg: 'static> Element<Msg> {
2000    /// Converts every message this subtree can emit with `f`, so a
2001    /// component written around its own message type drops into any parent:
2002    /// the Elm composition tool.
2003    ///
2004    /// ```
2005    /// use fenestra_core::{Element, div, text};
2006    ///
2007    /// #[derive(Clone)]
2008    /// enum CardMsg { Open }
2009    /// #[derive(Clone)]
2010    /// enum AppMsg { Card(usize, CardMsg) }
2011    ///
2012    /// fn card() -> Element<CardMsg> {
2013    ///     div().on_click(CardMsg::Open).child(text("open"))
2014    /// }
2015    ///
2016    /// let el: Element<AppMsg> = card().map(|m| AppMsg::Card(0, m));
2017    /// ```
2018    pub fn map<B: 'static>(self, f: impl Fn(Msg) -> B + Clone + 'static) -> Element<B> {
2019        Element {
2020            kind: self.kind,
2021            style: self.style,
2022            children: self
2023                .children
2024                .into_iter()
2025                .map(|c| c.map(f.clone()))
2026                .collect(),
2027            key: self.key,
2028            source: self.source,
2029            stack: self.stack,
2030            focusable: self.focusable,
2031            autofocus: self.autofocus,
2032            stick_bottom: self.stick_bottom,
2033            cursor: self.cursor,
2034            disabled: self.disabled,
2035            on_click: self.on_click.map(&f),
2036            on_double_click: self.on_double_click.map(&f),
2037            on_right_click: self.on_right_click.map(&f),
2038            on_hover: self.on_hover.map(&f),
2039            on_key: self.on_key.map(|k| {
2040                let f = f.clone();
2041                Box::new(move |key: &KeyInput| k(key).map(&f)) as KeyFn<B>
2042            }),
2043            on_type_ahead: self.on_type_ahead.map(|k| {
2044                let f = f.clone();
2045                Box::new(move |buffer: &str| k(buffer).map(&f)) as TypeAheadFn<B>
2046            }),
2047            on_drag: self.on_drag.map(|d| {
2048                let f = f.clone();
2049                Box::new(move |x: f32, y: f32| d(x, y).map(&f)) as DragFn<B>
2050            }),
2051            on_drag_end: self.on_drag_end.map(&f),
2052            on_swipe: self.on_swipe.map(|s| {
2053                let f = f.clone();
2054                Box::new(move |d: SwipeDir| f(s(d))) as SwipeFn<B>
2055            }),
2056            on_input: self.on_input.map(|i| {
2057                let f = f.clone();
2058                Box::new(move |s: &str| f(i(s))) as InputFn<B>
2059            }),
2060            on_close: self.on_close.map(&f),
2061            on_file_drop: self.on_file_drop.map(|d| {
2062                let f = f.clone();
2063                Box::new(move |p: &std::path::Path| f(d(p))) as FileDropFn<B>
2064            }),
2065            drag_source: self.drag_source,
2066            on_drop: self.on_drop.map(|d| {
2067                let f = f.clone();
2068                Box::new(move |payload: &str| d(payload).map(&f)) as DropFn<B>
2069            }),
2070            overlay: self.overlay,
2071            spin: self.spin,
2072            keyframes: self.keyframes,
2073            virtual_rows: self.virtual_rows.map(|v| {
2074                let f = f.clone();
2075                let builder = v.builder;
2076                VirtualData {
2077                    count: v.count,
2078                    row_height: v.row_height,
2079                    variable: v.variable,
2080                    builder: std::rc::Rc::new(move |i| builder(i).map(f.clone())),
2081                }
2082            }),
2083            responsive: self.responsive.map(|r| {
2084                let f = f.clone();
2085                let inner = r.f;
2086                ResponsiveData {
2087                    hint: r.hint,
2088                    f: std::rc::Rc::new(move |sz| inner(sz).map(f.clone())),
2089                }
2090            }),
2091            semantics: self.semantics,
2092            access_value: self.access_value,
2093            live: self.live,
2094            selectable: self.selectable,
2095            enter: self.enter,
2096            exit: self.exit,
2097            animate_layout: self.animate_layout,
2098            label: self.label,
2099            themed: self.themed,
2100            hover_style: self.hover_style,
2101            active_style: self.active_style,
2102            focus_style: self.focus_style,
2103            state_layer: self.state_layer,
2104            press_scale: self.press_scale,
2105            invalid: self.invalid,
2106            transition: self.transition,
2107        }
2108    }
2109}
2110
2111impl<Msg> Element<Msg> {
2112    /// Grid template columns (switches display to grid). Accepts plain
2113    /// [`Track`](crate::style::Track)s or full
2114    /// [`GridTemplate`](crate::style::GridTemplate) entries (e.g. `repeat(...)`).
2115    pub fn grid_cols<T: Into<crate::style::GridTemplate>>(
2116        mut self,
2117        tracks: impl IntoIterator<Item = T>,
2118    ) -> Self {
2119        self.style = self.style.grid_cols(tracks);
2120        self
2121    }
2122
2123    /// Grid template rows (switches display to grid). Accepts plain
2124    /// [`Track`](crate::style::Track)s or full
2125    /// [`GridTemplate`](crate::style::GridTemplate) entries (e.g. `repeat(...)`).
2126    pub fn grid_rows<T: Into<crate::style::GridTemplate>>(
2127        mut self,
2128        tracks: impl IntoIterator<Item = T>,
2129    ) -> Self {
2130        self.style = self.style.grid_rows(tracks);
2131        self
2132    }
2133
2134    /// Places this element at a 1-based grid column, spanning `span` tracks.
2135    pub fn grid_col(mut self, start: i16, span: u16) -> Self {
2136        self.style = self.style.grid_col(start, span);
2137        self
2138    }
2139
2140    /// Places this element at a 1-based grid row, spanning `span` tracks.
2141    pub fn grid_row(mut self, start: i16, span: u16) -> Self {
2142        self.style = self.style.grid_row(start, span);
2143        self
2144    }
2145
2146    /// `grid-template-areas` (CSS): each row is a string of whitespace-separated
2147    /// area names, `.` for an empty cell. Place children with
2148    /// [`Element::grid_area`]. Implies a grid of `auto` tracks matching the area
2149    /// shape when no explicit tracks are given.
2150    pub fn grid_template_areas<R: AsRef<str>>(mut self, rows: impl IntoIterator<Item = R>) -> Self {
2151        self.style = self.style.grid_template_areas(rows);
2152        self
2153    }
2154
2155    /// Places this element in a named grid area (CSS `grid-area`).
2156    pub fn grid_area(mut self, name: impl Into<String>) -> Self {
2157        self.style = self.style.grid_area(name);
2158        self
2159    }
2160
2161    /// Places this element's columns between two named grid lines
2162    /// (CSS `grid-column: start / end`).
2163    pub fn grid_col_lines(mut self, start: impl Into<String>, end: impl Into<String>) -> Self {
2164        self.style = self.style.grid_col_lines(start, end);
2165        self
2166    }
2167
2168    /// Places this element's rows between two named grid lines
2169    /// (CSS `grid-row: start / end`).
2170    pub fn grid_row_lines(mut self, start: impl Into<String>, end: impl Into<String>) -> Self {
2171        self.style = self.style.grid_row_lines(start, end);
2172        self
2173    }
2174
2175    /// Names the column grid lines positionally: the i-th name labels the
2176    /// (i+1)-th line. Reference them from [`Element::grid_col_lines`].
2177    pub fn grid_col_names<S: Into<String>>(mut self, names: impl IntoIterator<Item = S>) -> Self {
2178        self.style = self.style.grid_col_names(names);
2179        self
2180    }
2181
2182    /// Names the row grid lines positionally: the i-th name labels the (i+1)-th
2183    /// line. Reference them from [`Element::grid_row_lines`].
2184    pub fn grid_row_names<S: Into<String>>(mut self, names: impl IntoIterator<Item = S>) -> Self {
2185        self.style = self.style.grid_row_names(names);
2186        self
2187    }
2188}
2189
2190#[cfg(test)]
2191mod surface_tests {
2192    use super::div;
2193    use crate::surface::Surface;
2194
2195    #[test]
2196    fn element_surface_defers_to_resolution() {
2197        // The fill is installed by the deferred `themed`, not the base style,
2198        // so `.surface(..)` needs no theme at build time.
2199        let el = div::<()>().surface(Surface::Card);
2200        assert!(el.style().fill.is_none());
2201    }
2202}