Skip to main content

compose_rust/ui/
mod.rs

1//! The widget vocabulary as composables: the same widgets, the same properties and the
2//! same Modifier slots the `rsx!` layer writes, with Compose's names and Compose's shape.
3//!
4//! ```ignore
5//! use compose_rust::runtime::*;
6//! use compose_rust::ui::*;
7//!
8//! #[Composable]
9//! fn Greeting(name: String) {
10//!     Column().modifier(Modifier.padding(16.0).fill_max_width()).content(|| {
11//!         Text(format!("Hello, {name}")).type_role(TypeRole::Title);
12//!         Button("Wave").on_click(|| show_message("Hello back"));
13//!     });
14//! }
15//! ```
16//!
17//! **A widget is written as a statement.** `Text("Hello")` creates a description and the
18//! statement's end hands it to the composition; the methods in between set what Compose
19//! would take as named arguments. A container's children are the closure given to
20//! `content`, which runs at once. Binding a widget to a variable delays it to the end of
21//! the variable's scope, so a widget is not bound.
22//!
23//! **Nothing here defines a widget.** Each composable writes exactly the attributes its
24//! `rsx!` counterpart writes, through the same translation into wire records, so a screen
25//! written either way is the same tree of the same nodes for the Renderer. A Modifier the
26//! `rsx!` counterpart does not accept is still written when it is set, because the wire
27//! carries every Modifier slot on every node.
28
29pub(crate) mod focus;
30mod lazy;
31mod widgets;
32
33pub use crate::boundary::LaunchBuilder;
34pub use crate::brush::{Brush, Stop, brush};
35pub use crate::clipboard::{Clipboard, use_clipboard};
36pub use crate::drawing::{DrawCommand, DrawList, DrawListBuilder};
37pub use crate::font_features::FontFeature;
38pub use crate::message::{Message, show_message};
39pub use crate::runtime::{application, launch};
40pub use crate::schema::SemanticsRole as Role;
41pub use crate::schema::{
42    Alignment, Arrangement, BackgroundRepeat, ButtonKind, ButtonVariant, Color, ColorRole,
43    ColorScheme, CompactNavigation, ContentScale, DesignSystem, HorizontalAlignment, IconRole, Key,
44    MaterialRole, MessageDuration, MessageHorizontal, MessagePlacement, MessageVertical,
45    MotionRole, Paint, PopoverAlign, PopoverPlacement, SemanticsRole, ShapeRole, Snap, SpaceRole,
46    TextAlign, TextOverflow, Theme, TypeRole, VerticalAlignment, WindowHeightClass,
47    WindowSizeClass,
48};
49pub use crate::spans::TextSpans;
50pub use crate::theme::{ThemeHandle, current_theme};
51
52/// The application's theme, to read and to change while it runs. A change is one
53/// `SetTheme` record in the batch the call that made it produces.
54pub fn theme() -> ThemeHandle {
55    ThemeHandle::default()
56}
57pub use crate::window::{NodeSize, WindowSafeArea, WindowSize};
58pub use crate::{
59    Cursor, FileDrop, KeyEvent, PointerEvent, PointerKind, PointerKinds, RangeRequest, UserSelect,
60    asset, pointer_button,
61};
62pub use focus::{FocusHandle, use_focus_handle};
63pub use lazy::{LazyColumn, LazyGrid, LazyList, LazyListScope, LazyRow};
64pub use widgets::*;
65
66use crate::runtime::composer::{EventCallback, OwnedAttr};
67use crate::runtime::{group_guard, with_composer, with_composer_if_idle};
68use crate::schema::WidgetKind;
69
70/// The most shadow layers one node can carry through this API.
71pub const MAX_SHADOWS: usize = 4;
72
73/// One layer of a drop shadow, as CSS `box-shadow` writes it: an offset, a blur, a spread
74/// and a colour with its alpha. It follows the corners of the node that casts it.
75#[derive(Clone, Copy, Debug, PartialEq)]
76pub struct Shadow {
77    pub x: f32,
78    pub y: f32,
79    pub blur: f32,
80    pub spread: f32,
81    pub paint: Paint,
82}
83
84impl Shadow {
85    pub const fn new(x: f32, y: f32, blur: f32, spread: f32, paint: Paint) -> Self {
86        Self {
87            x,
88            y,
89            blur,
90            spread,
91            paint,
92        }
93    }
94}
95
96/// The most background brush layers one node can carry through this API.
97pub const MAX_BACKGROUND_BRUSHES: usize = 4;
98
99/// One background layer, as CSS draws a `background-image` with its position, size and
100/// repeat: a brush laid into a rectangle of the node, in dp from its top left corner, and
101/// repeated from there. A gradient is measured against the rectangle, not the node. The
102/// layer is cut to the node's outline and painted over its background colour.
103#[derive(Clone, Copy, Debug, PartialEq)]
104pub struct BackgroundBrush {
105    pub x: f32,
106    pub y: f32,
107    pub width: f32,
108    pub height: f32,
109    pub paint: Paint,
110    pub repeat: BackgroundRepeat,
111    /// How far apart the copies are, in dp. The rectangle's own size is CSS `repeat`.
112    pub tile_width: f32,
113    pub tile_height: f32,
114}
115
116impl BackgroundBrush {
117    /// One copy of `paint` in the rectangle, not repeated.
118    pub const fn new(x: f32, y: f32, width: f32, height: f32, paint: Paint) -> Self {
119        Self {
120            x,
121            y,
122            width,
123            height,
124            paint,
125            repeat: BackgroundRepeat::NoRepeat,
126            tile_width: width,
127            tile_height: height,
128        }
129    }
130
131    /// Repeats the rectangle the given ways, a rectangle's size apart unless
132    /// [`BackgroundBrush::step`] says otherwise.
133    pub const fn repeat(mut self, repeat: BackgroundRepeat) -> Self {
134        self.repeat = repeat;
135        self
136    }
137
138    /// How far apart the copies are, in dp, for CSS `space` and `round`. A step of zero or
139    /// less is not a step, and a layer with one is not sent.
140    pub const fn step(mut self, width: f32, height: f32) -> Self {
141        self.tile_width = width;
142        self.tile_height = height;
143        self
144    }
145}
146
147/// How a node presents itself to assistive technology. Each tri-state field is unset until
148/// it is given a value, and an unset one sends nothing. The flags are off until set.
149#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
150pub struct SemanticsState {
151    pub checked: Option<bool>,
152    pub selected: Option<bool>,
153    pub expanded: Option<bool>,
154    pub disabled: Option<bool>,
155    pub focused: Option<bool>,
156    pub hidden: bool,
157    pub click_action: bool,
158    pub focus_action: bool,
159}
160
161impl SemanticsState {
162    const NONE: Self = Self {
163        checked: None,
164        selected: None,
165        expanded: None,
166        disabled: None,
167        focused: None,
168        hidden: false,
169        click_action: false,
170        focus_action: false,
171    };
172
173    pub const fn checked(checked: bool) -> Self {
174        Self {
175            checked: Some(checked),
176            ..Self::NONE
177        }
178    }
179
180    pub const fn selected(selected: bool) -> Self {
181        Self {
182            selected: Some(selected),
183            ..Self::NONE
184        }
185    }
186
187    pub const fn expanded(expanded: bool) -> Self {
188        Self {
189            expanded: Some(expanded),
190            ..Self::NONE
191        }
192    }
193
194    pub const fn disabled(disabled: bool) -> Self {
195        Self {
196            disabled: Some(disabled),
197            ..Self::NONE
198        }
199    }
200
201    pub const fn focused(focused: bool) -> Self {
202        Self {
203            focused: Some(focused),
204            ..Self::NONE
205        }
206    }
207
208    pub const fn hidden(hidden: bool) -> Self {
209        Self {
210            hidden,
211            ..Self::NONE
212        }
213    }
214
215    pub const fn click_action(exposed: bool) -> Self {
216        Self {
217            click_action: exposed,
218            ..Self::NONE
219        }
220    }
221
222    pub const fn focus_action(exposed: bool) -> Self {
223        Self {
224            focus_action: exposed,
225            ..Self::NONE
226        }
227    }
228
229    /// The state word the `Semantics` modifier carries.
230    pub(crate) fn bits(self) -> u32 {
231        use crate::schema::*;
232        let tri = |value: Option<bool>| value.map_or(0_u32, |on| if on { 2 } else { 1 });
233        (tri(self.checked) << SEMANTICS_CHECKED_SHIFT)
234            | (tri(self.selected) << SEMANTICS_SELECTED_SHIFT)
235            | (tri(self.expanded) << SEMANTICS_EXPANDED_SHIFT)
236            | (tri(self.disabled) << SEMANTICS_DISABLED_SHIFT)
237            | (tri(self.focused) << SEMANTICS_FOCUSED_SHIFT)
238            | if self.hidden { SEMANTICS_HIDDEN_BIT } else { 0 }
239            | if self.click_action {
240                SEMANTICS_CLICK_ACTION_BIT
241            } else {
242                0
243            }
244            | if self.focus_action {
245                SEMANTICS_FOCUS_ACTION_BIT
246            } else {
247                0
248            }
249    }
250}
251
252/// The most distinct accessible names a program can keep. Names are kept for the life of
253/// the program so a `Modifier` can stay `Copy`; the cap keeps a name built from data
254/// (an id, a counter) from growing that memory without end.
255pub(crate) const MAX_LABELS: usize = 4096;
256
257/// The distinct accessible names seen so far, each leaked once.
258struct LabelTable {
259    kept: std::sync::RwLock<std::collections::HashSet<&'static str>>,
260    warned: std::sync::atomic::AtomicBool,
261}
262
263impl LabelTable {
264    fn new() -> Self {
265        Self {
266            kept: Default::default(),
267            warned: Default::default(),
268        }
269    }
270
271    /// The kept copy of `label`. A name already kept is found under a shared lock with no
272    /// allocation. A new name is kept until the table holds `cap` names; past that it is
273    /// dropped (and reported in debug builds) so memory stays bounded.
274    fn intern(&self, label: &str, cap: usize) -> Option<&'static str> {
275        use std::sync::atomic::Ordering;
276
277        if label.is_empty() {
278            return None;
279        }
280        {
281            let kept = self.kept.read().unwrap_or_else(|p| p.into_inner());
282            if let Some(found) = kept.get(label) {
283                return Some(*found);
284            }
285        }
286        let mut kept = self.kept.write().unwrap_or_else(|p| p.into_inner());
287        if let Some(found) = kept.get(label) {
288            return Some(*found);
289        }
290        if kept.len() >= cap {
291            if cfg!(debug_assertions) && !self.warned.swap(true, Ordering::Relaxed) {
292                eprintln!(
293                    "compose-rust: more than {cap} distinct accessible names; \
294                     names past the limit are left off. Use a fixed set of names."
295                );
296            }
297            return None;
298        }
299        let leaked: &'static str = Box::leak(label.to_owned().into_boxed_str());
300        kept.insert(leaked);
301        Some(leaked)
302    }
303
304    #[cfg(test)]
305    fn len(&self) -> usize {
306        self.kept.read().unwrap_or_else(|p| p.into_inner()).len()
307    }
308}
309
310fn keep_label(label: &str) -> Option<&'static str> {
311    use std::cell::Cell;
312    use std::sync::OnceLock;
313
314    static LABELS: OnceLock<LabelTable> = OnceLock::new();
315    thread_local! {
316        // The name this thread kept last. A name written on every frame is found here
317        // without taking any lock.
318        static LAST: Cell<Option<&'static str>> = const { Cell::new(None) };
319    }
320    if let Some(last) = LAST.with(Cell::get) {
321        if last == label {
322            return Some(last);
323        }
324    }
325    let kept = LABELS
326        .get_or_init(LabelTable::new)
327        .intern(label, MAX_LABELS);
328    if kept.is_some() {
329        LAST.with(|last| last.set(kept));
330    }
331    kept
332}
333
334/// One side of a node, for [`Modifier::border_side`].
335#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
336pub enum Side {
337    Top,
338    /// The side a line of text ends on: the right side.
339    End,
340    Bottom,
341    /// The side a line of text starts on: the left side.
342    Start,
343}
344
345/// What sits around a widget: its size, its surface, its outline, how far its content
346/// is inset, and what the Renderer reports about it. Compose's `Modifier`.
347///
348/// Written the way Compose writes it, from the empty value `Modifier`:
349///
350/// ```ignore
351/// Card().modifier(Modifier.fill_max_width().padding(16.0).background(Paint::Role(ColorRole::Surface)))
352/// ```
353///
354/// The order of the calls does not matter. Each kind of Modifier has a fixed slot in the
355/// node's chain, outside in: size, shape, fill, outline, lift, press, inset. That is what
356/// the `rsx!` attributes do too, so the same Modifier draws the same either way.
357#[derive(Clone, Copy, Debug, Default, PartialEq)]
358pub struct Modifier {
359    observe_size: Option<i64>,
360    observe_size_every_dp: bool,
361    motion: Option<MotionRole>,
362    material: Option<MaterialRole>,
363    weight: Option<f32>,
364    width: Option<f32>,
365    height: Option<f32>,
366    padding: Option<f32>,
367    padding_role: Option<SpaceRole>,
368    background: Option<Paint>,
369    shape_role: Option<ShapeRole>,
370    corner_radius: Option<f32>,
371    corner_radii: Option<[f32; 4]>,
372    corner_curve: Option<f32>,
373    border_width: Option<f32>,
374    border_color: Option<Paint>,
375    border_each: Option<[(f32, Paint); 4]>,
376    elevation: Option<f32>,
377    alpha: Option<f32>,
378    press_feedback: Option<(f32, f32)>,
379    semantics_role: Option<SemanticsRole>,
380    semantics_label: Option<&'static str>,
381    semantics_state: SemanticsState,
382    transform: Option<[f32; 8]>,
383    animated_alpha: Option<crate::Animated<f32>>,
384    animated_background: Option<crate::Animated<Paint>>,
385    animated_offset: Option<crate::Animated<(f32, f32)>>,
386    cursor: Option<crate::Cursor>,
387    user_select: Option<crate::UserSelect>,
388    clip: bool,
389    window_drag: Option<bool>,
390    offset: Option<(f32, f32)>,
391    shadows: [Option<Shadow>; MAX_SHADOWS],
392    background_brushes: [Option<BackgroundBrush>; MAX_BACKGROUND_BRUSHES],
393    fill_max_width: bool,
394    match_parent_size: bool,
395    fill_max_height: bool,
396}
397
398/// The empty Modifier, to start a chain from: `Modifier.padding(8.0)`.
399#[allow(non_upper_case_globals)]
400pub const Modifier: Modifier = Modifier::new();
401
402impl Modifier {
403    /// The empty Modifier. The same as the value `Modifier`.
404    pub const fn new() -> Self {
405        Self {
406            observe_size: None,
407            observe_size_every_dp: false,
408            motion: None,
409            material: None,
410            weight: None,
411            width: None,
412            height: None,
413            padding: None,
414            padding_role: None,
415            background: None,
416            shape_role: None,
417            corner_radius: None,
418            corner_radii: None,
419            corner_curve: None,
420            border_width: None,
421            border_color: None,
422            border_each: None,
423            elevation: None,
424            alpha: None,
425            press_feedback: None,
426            semantics_role: None,
427            semantics_label: None,
428            semantics_state: SemanticsState::NONE,
429            transform: None,
430            animated_alpha: None,
431            animated_background: None,
432            animated_offset: None,
433            cursor: None,
434            user_select: None,
435            clip: false,
436            window_drag: None,
437            offset: None,
438            shadows: [None; MAX_SHADOWS],
439            background_brushes: [None; MAX_BACKGROUND_BRUSHES],
440            fill_max_width: false,
441            match_parent_size: false,
442            fill_max_height: false,
443        }
444    }
445
446    /// A share of the space left in a `Row` or `Column`.
447    pub const fn weight(mut self, weight: f32) -> Self {
448        self.weight = Some(weight);
449        self
450    }
451
452    pub const fn width(mut self, dp: f32) -> Self {
453        self.width = Some(dp);
454        self
455    }
456
457    pub const fn height(mut self, dp: f32) -> Self {
458        self.height = Some(dp);
459        self
460    }
461
462    /// Both at once.
463    pub const fn size(self, width: f32, height: f32) -> Self {
464        self.width(width).height(height)
465    }
466
467    /// A width that may be unset, for a screen whose measure depends on the window.
468    pub const fn width_if(mut self, dp: Option<f32>) -> Self {
469        self.width = dp;
470        self
471    }
472
473    /// Matches the bounds of a Box parent without affecting its size.
474    pub const fn match_parent_size(mut self) -> Self {
475        self.match_parent_size = true;
476        self
477    }
478
479    pub const fn fill_max_width(mut self) -> Self {
480        self.fill_max_width = true;
481        self
482    }
483
484    pub const fn fill_max_height(mut self) -> Self {
485        self.fill_max_height = true;
486        self
487    }
488
489    pub const fn fill_max_size(self) -> Self {
490        self.fill_max_width().fill_max_height()
491    }
492
493    /// Fills the width only when `fill` is true.
494    pub const fn fill_max_width_if(mut self, fill: bool) -> Self {
495        self.fill_max_width = fill;
496        self
497    }
498
499    /// The same inset on every side, in dp.
500    pub const fn padding(mut self, dp: f32) -> Self {
501        self.padding = Some(dp);
502        self
503    }
504
505    /// An inset from the design system's spacing ladder. Wins over `padding`.
506    pub const fn padding_role(mut self, role: SpaceRole) -> Self {
507        self.padding_role = Some(role);
508        self
509    }
510
511    /// The same, when the inset may be unset.
512    pub const fn padding_role_if(mut self, role: Option<SpaceRole>) -> Self {
513        self.padding_role = role;
514        self
515    }
516
517    pub const fn background(mut self, paint: Paint) -> Self {
518        self.background = Some(paint);
519        self
520    }
521
522    /// The same, when there may be no background.
523    pub const fn background_if(mut self, paint: Option<Paint>) -> Self {
524        self.background = paint;
525        self
526    }
527
528    /// A shape from the design system's ladder.
529    pub const fn shape_role(mut self, role: ShapeRole) -> Self {
530        self.shape_role = Some(role);
531        self
532    }
533
534    /// A literal corner, in dp. Wins over `shape_role`.
535    pub const fn corner_radius(mut self, dp: f32) -> Self {
536        self.corner_radius = Some(dp);
537        self.corner_radii = None;
538        self
539    }
540
541    /// Individual corner radii `[top_start, top_end, bottom_end, bottom_start]`, in dp.
542    /// Wins over `shape_role` and `corner_radius`.
543    pub const fn corner_radii(mut self, radii: [f32; 4]) -> Self {
544        self.corner_radii = Some(radii);
545        self.corner_radius = None;
546        self
547    }
548
549    /// Clips the node and its children to its shape, or to its rectangle when it has none.
550    pub const fn clip(mut self, clip: bool) -> Self {
551        self.clip = clip;
552        self
553    }
554
555    /// Includes or excludes this node from window dragging.
556    pub const fn window_drag(mut self, enabled: bool) -> Self {
557        self.window_drag = Some(enabled);
558        self
559    }
560
561    /// The same border on every side. It takes the place of `border_each` and
562    /// `border_side`.
563    pub const fn border(mut self, width: f32, paint: Paint) -> Self {
564        self.border_width = Some(width);
565        self.border_color = Some(paint);
566        self.border_each = None;
567        self
568    }
569
570    /// A border whose sides each have their own width and paint, in CSS order: top,
571    /// right, bottom, left. A side of width 0 draws nothing. It takes the place of
572    /// `border`.
573    ///
574    /// The sides are the screen's, not the reading direction's: `right` is the right side
575    /// in a right-to-left window too.
576    pub const fn border_each(
577        mut self,
578        top: (f32, Paint),
579        right: (f32, Paint),
580        bottom: (f32, Paint),
581        left: (f32, Paint),
582    ) -> Self {
583        self.border_each = Some([top, right, bottom, left]);
584        self.border_width = None;
585        self.border_color = None;
586        self
587    }
588
589    /// A border on one side only, such as the guide line beside an indented row. The
590    /// other sides have width 0, unless an earlier `border_side` or `border_each` in the
591    /// same chain gave them one, so `border_side(Side::Top, ..).border_side(Side::Start,
592    /// ..)` draws two sides. It takes the place of `border`.
593    ///
594    /// `Side::Start` is the left side and `Side::End` the right one. The Host lays every
595    /// screen out left to right and the Renderer draws a border on the screen's sides, so
596    /// a right-to-left window does not move a start border to the right.
597    pub const fn border_side(mut self, side: Side, width: f32, paint: Paint) -> Self {
598        let mut sides = match self.border_each {
599            Some(sides) => sides,
600            None => [(0.0, paint); 4],
601        };
602        let index = match side {
603            Side::Top => 0,
604            Side::End => 1,
605            Side::Bottom => 2,
606            Side::Start => 3,
607        };
608        sides[index] = (width, paint);
609        self.border_each = Some(sides);
610        self.border_width = None;
611        self.border_color = None;
612        self
613    }
614
615    /// A resting height above the default, in dp. How it is drawn is the design system's.
616    pub const fn elevation(mut self, dp: f32) -> Self {
617        self.elevation = Some(dp);
618        self
619    }
620
621    /// Asks the Renderer to report this node's size under the token from
622    /// [`crate::runtime::remember_node_size`].
623    ///
624    /// A report goes out on the first measurement and then whenever the width or height
625    /// class changes. A resize inside one class says nothing; use
626    /// [`Modifier::observe_size_every_dp`] for that.
627    pub const fn observe_size(mut self, token: i64) -> Self {
628        self.observe_size = Some(token);
629        self.observe_size_every_dp = false;
630        self
631    }
632
633    /// Like [`Modifier::observe_size`], and also reports whenever the width or height,
634    /// rounded to the nearest whole dp, changes inside one class. Several changes in one
635    /// frame are one report carrying the last size.
636    pub const fn observe_size_every_dp(mut self, token: i64) -> Self {
637        self.observe_size = Some(token);
638        self.observe_size_every_dp = true;
639        self
640    }
641
642    /// How important this node's changes are. The curve is the design system's.
643    pub const fn motion(mut self, role: MotionRole) -> Self {
644        self.motion = Some(role);
645        self
646    }
647
648    /// What this node's surface is made of. The effect is the design system's.
649    pub const fn material(mut self, role: MaterialRole) -> Self {
650        self.material = Some(role);
651        self
652    }
653
654    /// Moves the node from where it was laid out, in dp, without moving anything else.
655    pub const fn offset(mut self, x: f32, y: f32) -> Self {
656        self.offset = Some((x, y));
657        self
658    }
659
660    /// Adds a shadow layer. Layers are drawn in the order they are added, the first at
661    /// the bottom. A node takes at most [`MAX_SHADOWS`]; a layer past that is ignored.
662    pub const fn shadow(mut self, shadow: Shadow) -> Self {
663        let mut index = 0;
664        while index < MAX_SHADOWS {
665            if self.shadows[index].is_none() {
666                self.shadows[index] = Some(shadow);
667                break;
668            }
669            index += 1;
670        }
671        self
672    }
673
674    /// Adds several shadow layers, in order.
675    pub fn shadows(mut self, shadows: impl IntoIterator<Item = Shadow>) -> Self {
676        for shadow in shadows {
677            self = self.shadow(shadow);
678        }
679        self
680    }
681
682    /// Adds a background brush layer. Layers are drawn in the order they are added, the
683    /// first at the bottom, all of them over the background colour. A node takes at most
684    /// [`MAX_BACKGROUND_BRUSHES`]; a layer past that is ignored.
685    pub const fn background_brush(mut self, layer: BackgroundBrush) -> Self {
686        let mut index = 0;
687        while index < MAX_BACKGROUND_BRUSHES {
688            if self.background_brushes[index].is_none() {
689                self.background_brushes[index] = Some(layer);
690                break;
691            }
692            index += 1;
693        }
694        self
695    }
696
697    /// Adds several background brush layers, in order. CSS writes its list top first, so
698    /// a list taken from CSS is added last to first.
699    pub fn background_brushes(mut self, layers: impl IntoIterator<Item = BackgroundBrush>) -> Self {
700        for layer in layers {
701            self = self.background_brush(layer);
702        }
703        self
704    }
705
706    /// An opacity for the node and everything in it, faded as one group.
707    pub const fn alpha(mut self, alpha: f32) -> Self {
708        self.alpha = Some(alpha);
709        self
710    }
711
712    /// How the node's rounded corners curve, from 0 (a circular arc, the default) to 1.
713    /// Above 0 the curve eases in from each side with continuously changing curvature, as
714    /// Figma's corner smoothing does; 0.6 is the look of iOS. The radius stays whatever
715    /// `corner_radius`, `corner_radii` or `shape_role` made it, and the background, border,
716    /// shadow and clip all follow the same curve. A value outside 0..=1 is taken as the
717    /// nearer end, and NaN as 0.
718    pub fn corner_curve(mut self, smoothing: f32) -> Self {
719        self.corner_curve = Some(crate::schema::corner_smoothing(smoothing));
720        self
721    }
722
723    /// Sets the pressed scale and tint opacity.
724    pub const fn press_feedback(mut self, scale: f32, tint_alpha: f32) -> Self {
725        self.press_feedback = Some((scale, tint_alpha));
726        self
727    }
728
729    /// The name assistive technology reads for this node. On a widget with a visible label
730    /// it replaces that label; an empty name is the same as none.
731    ///
732    /// Names are kept for the life of the program, and at most 4096 distinct names are
733    /// kept. A name past that limit is left off (debug builds print a warning once), so
734    /// use a fixed set of names rather than one built from changing data.
735    pub fn label(mut self, label: &str) -> Self {
736        self.semantics_label = keep_label(label);
737        self
738    }
739
740    /// What this node is to assistive technology, for a node that is not a widget with
741    /// a role of its own: a `Box` that is pressed is a button.
742    pub const fn role(mut self, role: Role) -> Self {
743        self.semantics_role = Some(role);
744        self
745    }
746
747    /// Whether the node is checked, as a toggle announces it.
748    pub const fn checked(mut self, checked: bool) -> Self {
749        self.semantics_state.checked = Some(checked);
750        self
751    }
752
753    /// Whether the node is the selected one of its group.
754    pub const fn selected(mut self, selected: bool) -> Self {
755        self.semantics_state.selected = Some(selected);
756        self
757    }
758
759    /// Whether the node is expanded, as a disclosure or a tree item announces it.
760    pub const fn expanded(mut self, expanded: bool) -> Self {
761        self.semantics_state.expanded = Some(expanded);
762        self
763    }
764
765    /// Whether the node is disabled for assistive technology.
766    pub const fn disabled(mut self, disabled: bool) -> Self {
767        self.semantics_state.disabled = Some(disabled);
768        self
769    }
770
771    /// Whether the node holds the focus, as assistive technology reads it.
772    pub const fn focused(mut self, focused: bool) -> Self {
773        self.semantics_state.focused = Some(focused);
774        self
775    }
776
777    /// Leaves the node out of the accessibility tree when true.
778    pub const fn hidden(mut self, hidden: bool) -> Self {
779        self.semantics_state.hidden = hidden;
780        self
781    }
782
783    /// Exposes a click action on the node, which runs the node's click handler.
784    pub const fn click_action(mut self, exposed: bool) -> Self {
785        self.semantics_state.click_action = exposed;
786        self
787    }
788
789    /// Exposes a focus action on the node.
790    pub const fn focus_action(mut self, exposed: bool) -> Self {
791        self.semantics_state.focus_action = exposed;
792        self
793    }
794
795    /// The role, the name and the state in one call.
796    pub fn semantics(self, role: Role, label: &str, state: SemanticsState) -> Self {
797        let mut named = self.role(role).label(label);
798        named.semantics_state = state;
799        named
800    }
801
802    /// Compose's `graphicsLayer`: a rotation, a scale and a translation about an origin,
803    /// drawn without moving the layout, and an opacity for the whole group. A pointer is
804    /// tested against the shape that is drawn.
805    pub fn graphics_layer(mut self, layer: crate::GraphicsLayer) -> Self {
806        if let crate::schema::Modifier::Transform {
807            a,
808            b,
809            c,
810            d,
811            e,
812            f,
813            origin_x,
814            origin_y,
815        } = layer.transform()
816        {
817            self.transform = Some([a, b, c, d, e, f, origin_x, origin_y]);
818        }
819        self.alpha = Some(layer.alpha);
820        self
821    }
822
823    /// An opacity the Renderer animates to, from [`crate::animate_float_as_state`]. It
824    /// takes the place of `alpha`.
825    pub const fn animated_alpha(mut self, value: crate::Animated<f32>) -> Self {
826        self.animated_alpha = Some(value);
827        self
828    }
829
830    /// A background the Renderer animates to, from [`crate::animate_color_as_state`]. It
831    /// takes the place of `background`.
832    pub const fn animated_background(mut self, value: crate::Animated<Paint>) -> Self {
833        self.animated_background = Some(value);
834        self
835    }
836
837    /// A translation the Renderer animates to, from [`crate::animate_offset_as_state`].
838    /// It takes the place of the transform a `graphics_layer` would draw.
839    pub const fn animated_offset(mut self, value: crate::Animated<(f32, f32)>) -> Self {
840        self.animated_offset = Some(value);
841        self
842    }
843
844    /// The cursor the platform shows over the node. CSS `cursor`: the deepest node under
845    /// the pointer that names one other than `Cursor::Auto` wins, and while a node holds a
846    /// pointer capture its cursor stays.
847    pub const fn cursor(mut self, cursor: crate::Cursor) -> Self {
848        self.cursor = Some(cursor);
849        self
850    }
851
852    /// Whether the text under the node can be selected. CSS `user-select`; `Auto` follows
853    /// the parent.
854    pub const fn user_select(mut self, user_select: crate::UserSelect) -> Self {
855        self.user_select = Some(user_select);
856        self
857    }
858}
859
860/// Which Modifier attributes a widget's `rsx!` counterpart always writes. The others are
861/// written only when they are set.
862#[derive(Clone, Copy, PartialEq, Eq)]
863pub(crate) enum Mods {
864    /// The containers that take every Modifier: Column, Row, Box, Card, Surface.
865    Container,
866    /// The thirteen every other widget takes.
867    Standard,
868    /// A chip's six.
869    Chip,
870    /// A Scaffold's background and material.
871    Scaffold,
872    /// None.
873    None,
874}
875
876pub(crate) type Attrs = Vec<(&'static str, OwnedAttr)>;
877
878fn float(value: Option<f32>) -> OwnedAttr {
879    value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
880}
881
882fn role(value: Option<impl Into<u16>>) -> OwnedAttr {
883    value.map_or(OwnedAttr::None, |value| {
884        OwnedAttr::Int(i64::from(value.into()))
885    })
886}
887
888fn paint(value: Option<Paint>) -> OwnedAttr {
889    value.map_or(OwnedAttr::None, |paint| {
890        OwnedAttr::Int(paint.to_bits() as i64)
891    })
892}
893
894/// A role that was not set is tag 0, which means "not sent".
895pub(crate) fn role_or_zero(value: Option<impl Into<u16>>) -> OwnedAttr {
896    OwnedAttr::Int(value.map_or(0, |value| i64::from(value.into())))
897}
898
899/// A dp value that was not set is 0.
900pub(crate) fn dp_or_zero(value: Option<f32>) -> OwnedAttr {
901    OwnedAttr::Float(f64::from(value.unwrap_or(0.0)))
902}
903
904/// A paint that was not set is 0.
905pub(crate) fn paint_or_zero(value: Option<Paint>) -> OwnedAttr {
906    OwnedAttr::Int(value.map_or(0, |paint| paint.to_bits() as i64))
907}
908
909pub(crate) fn optional_text(value: Option<String>) -> OwnedAttr {
910    value.map_or(OwnedAttr::None, OwnedAttr::Text)
911}
912
913/// The role and state of a Modifier as the one integer the `semantics` attribute carries,
914/// or none when neither was given.
915fn semantics_bits(modifier: &Modifier) -> Option<i64> {
916    let state = modifier.semantics_state;
917    if modifier.semantics_role.is_none() && state == SemanticsState::NONE {
918        return None;
919    }
920    let role = i64::from(u16::from(
921        modifier.semantics_role.unwrap_or(SemanticsRole::None),
922    ));
923    Some(role | (i64::from(state.bits()) << 16))
924}
925
926/// Writes the Modifier attributes, in the order the `rsx!` widgets write them.
927pub(crate) fn push_modifiers(modifier: &Modifier, mods: Mods, out: &mut Attrs) {
928    let container = mods == Mods::Container;
929    let standard = container || mods == Mods::Standard;
930    let chip = standard || mods == Mods::Chip;
931    let scaffold = mods == Mods::Scaffold;
932    let mut push = |always: bool, name: &'static str, value: OwnedAttr| {
933        let set = !matches!(value, OwnedAttr::None | OwnedAttr::Bool(false));
934        if always || set {
935            out.push((name, value));
936        }
937    };
938    // One slot, two names: whichever is not in use goes out unset, which clears nothing
939    // because only the name that wrote a slot may clear it.
940    let (plain, every_dp) = match modifier.observe_size {
941        Some(token) if modifier.observe_size_every_dp => (OwnedAttr::None, OwnedAttr::Int(token)),
942        Some(token) => (OwnedAttr::Int(token), OwnedAttr::None),
943        None => (OwnedAttr::None, OwnedAttr::None),
944    };
945    push(container, "observe_size", plain);
946    push(container, "observe_size_every_dp", every_dp);
947    push(container, "motion", role(modifier.motion));
948    push(container || scaffold, "material", role(modifier.material));
949    push(chip, "weight", float(modifier.weight));
950    push(chip, "width", float(modifier.width));
951    push(chip, "height", float(modifier.height));
952    push(chip, "padding", float(modifier.padding));
953    push(chip, "padding_role", role(modifier.padding_role));
954    // An animated background owns the background's slot while it is set.
955    push(
956        standard || scaffold,
957        "background",
958        paint(if modifier.animated_background.is_some() {
959            None
960        } else {
961            modifier.background
962        }),
963    );
964    push(standard, "shape_role", role(modifier.shape_role));
965    push(standard, "corner_radius", float(modifier.corner_radius));
966    push(
967        false,
968        "corner_radii",
969        modifier.corner_radii.map_or(OwnedAttr::None, |values| {
970            OwnedAttr::Bytes(
971                values
972                    .iter()
973                    .flat_map(|value| value.to_le_bytes())
974                    .collect(),
975            )
976        }),
977    );
978    push(false, "corner_curve", float(modifier.corner_curve));
979    push(false, "clip", OwnedAttr::Bool(modifier.clip));
980    push(
981        true,
982        "window_drag",
983        modifier
984            .window_drag
985            .map_or(OwnedAttr::None, OwnedAttr::Bool),
986    );
987    push(standard, "border_width", float(modifier.border_width));
988    push(standard, "border_color", paint(modifier.border_color));
989    // Four widths and then four paints, written only when set. It shares the border's
990    // slot, after the plain border, so whichever of the two the chain holds is the one
991    // the slot ends up with.
992    push(
993        false,
994        "border_each",
995        modifier.border_each.map_or(OwnedAttr::None, |sides| {
996            let mut bytes = Vec::with_capacity(48);
997            for (width, _) in sides {
998                bytes.extend_from_slice(&width.to_le_bytes());
999            }
1000            for (_, paint) in sides {
1001                bytes.extend_from_slice(&paint.to_bits().to_le_bytes());
1002            }
1003            OwnedAttr::Bytes(bytes)
1004        }),
1005    );
1006    push(standard, "elevation", float(modifier.elevation));
1007    push(
1008        false,
1009        "match_parent_size",
1010        OwnedAttr::Bool(modifier.match_parent_size),
1011    );
1012    push(
1013        chip,
1014        "fill_max_width",
1015        OwnedAttr::Bool(modifier.fill_max_width),
1016    );
1017    push(
1018        standard,
1019        "fill_max_height",
1020        OwnedAttr::Bool(modifier.fill_max_height),
1021    );
1022    // The layer and the values the Renderer animates, written only when they are set. An
1023    // animated value owns its slot while it is set, as an animated background does above.
1024    push(
1025        false,
1026        "press_feedback",
1027        modifier
1028            .press_feedback
1029            .map_or(OwnedAttr::None, |(scale, alpha)| {
1030                OwnedAttr::Bytes([scale.to_le_bytes(), alpha.to_le_bytes()].concat())
1031            }),
1032    );
1033    // The role and state travel as one integer, the role in the low 16 bits; the name is a
1034    // property of the node because a string does not fit in a modifier's words.
1035    push(
1036        false,
1037        "semantics",
1038        semantics_bits(modifier).map_or(OwnedAttr::None, OwnedAttr::Int),
1039    );
1040    push(
1041        false,
1042        "accessible_name",
1043        modifier
1044            .semantics_label
1045            .map_or(OwnedAttr::None, OwnedAttr::Label),
1046    );
1047    push(
1048        false,
1049        "alpha",
1050        float(if modifier.animated_alpha.is_some() {
1051            None
1052        } else {
1053            modifier.alpha
1054        }),
1055    );
1056    push(
1057        false,
1058        "transform",
1059        match (modifier.animated_offset, modifier.transform) {
1060            (None, Some(values)) => OwnedAttr::Bytes(
1061                values
1062                    .iter()
1063                    .flat_map(|value| value.to_le_bytes())
1064                    .collect(),
1065            ),
1066            _ => OwnedAttr::None,
1067        },
1068    );
1069    // Written only when set, and `Auto` is the same as not set.
1070    push(
1071        false,
1072        "cursor",
1073        match modifier.cursor {
1074            Some(crate::Cursor::Auto) | None => OwnedAttr::None,
1075            Some(cursor) => OwnedAttr::Int(i64::from(u16::from(cursor))),
1076        },
1077    );
1078    push(
1079        false,
1080        "user_select",
1081        match modifier.user_select {
1082            Some(crate::UserSelect::Auto) | None => OwnedAttr::None,
1083            Some(select) => OwnedAttr::Int(i64::from(u16::from(select))),
1084        },
1085    );
1086    // The layers and the displacement, written only when they are set.
1087    push(
1088        false,
1089        "offset",
1090        modifier.offset.map_or(OwnedAttr::None, |(x, y)| {
1091            let mut bytes = Vec::with_capacity(8);
1092            bytes.extend_from_slice(&x.to_le_bytes());
1093            bytes.extend_from_slice(&y.to_le_bytes());
1094            OwnedAttr::Bytes(bytes)
1095        }),
1096    );
1097    push(
1098        false,
1099        "shadows",
1100        if modifier.shadows[0].is_none() {
1101            OwnedAttr::None
1102        } else {
1103            let mut bytes = Vec::new();
1104            for shadow in modifier.shadows.iter().flatten() {
1105                for value in [shadow.x, shadow.y, shadow.blur, shadow.spread] {
1106                    bytes.extend_from_slice(&value.to_le_bytes());
1107                }
1108                bytes.extend_from_slice(&shadow.paint.to_bits().to_le_bytes());
1109            }
1110            OwnedAttr::Bytes(bytes)
1111        },
1112    );
1113    push(
1114        false,
1115        "background_brushes",
1116        if modifier.background_brushes[0].is_none() {
1117            OwnedAttr::None
1118        } else {
1119            let mut bytes = Vec::new();
1120            for layer in modifier.background_brushes.iter().flatten() {
1121                for value in [layer.x, layer.y, layer.width, layer.height] {
1122                    bytes.extend_from_slice(&value.to_le_bytes());
1123                }
1124                bytes.extend_from_slice(&layer.paint.to_bits().to_le_bytes());
1125                bytes.extend_from_slice(&u32::from(u16::from(layer.repeat)).to_le_bytes());
1126                bytes.extend_from_slice(&layer.tile_width.to_le_bytes());
1127                bytes.extend_from_slice(&layer.tile_height.to_le_bytes());
1128                // Padding to the layer's 40 bytes, so a layer starts on an eight byte
1129                // boundary as the writer reads it.
1130                bytes.extend_from_slice(&[0; 4]);
1131            }
1132            OwnedAttr::Bytes(bytes)
1133        },
1134    );
1135    push(
1136        false,
1137        "animated_alpha",
1138        modifier.animated_alpha.map_or(OwnedAttr::None, |value| {
1139            OwnedAttr::Bytes(crate::animation::animated_bytes(
1140                &[value.target.to_bits()],
1141                &value.spec,
1142            ))
1143        }),
1144    );
1145    push(
1146        false,
1147        "animated_background",
1148        modifier
1149            .animated_background
1150            .map_or(OwnedAttr::None, |value| {
1151                let bits = value.target.to_bits();
1152                OwnedAttr::Bytes(crate::animation::animated_bytes(
1153                    &[bits as u32, (bits >> 32) as u32],
1154                    &value.spec,
1155                ))
1156            }),
1157    );
1158    push(
1159        false,
1160        "animated_offset",
1161        modifier.animated_offset.map_or(OwnedAttr::None, |value| {
1162            OwnedAttr::Bytes(crate::animation::animated_bytes(
1163                &[value.target.0.to_bits(), value.target.1.to_bits()],
1164                &value.spec,
1165            ))
1166        }),
1167    );
1168}
1169
1170/// Composes one widget node: its group, its attributes and listeners, and its children.
1171///
1172/// `fill` writes the attributes and runs while the composer is held, so it only moves
1173/// values out of the builder. `content` runs afterwards, with the node as the parent of
1174/// whatever it composes.
1175pub(crate) fn emit(
1176    key: u64,
1177    widget: WidgetKind,
1178    fill: impl FnOnce(&mut Attrs),
1179    listeners: Vec<(&'static str, EventCallback)>,
1180    content: impl FnOnce(),
1181) {
1182    let _group = group_guard(key);
1183    emit_node(widget, fill, listeners, content);
1184}
1185
1186/// The same, inside a group the caller has already opened, for a widget that remembers
1187/// something of its own before its node.
1188pub(crate) fn emit_node(
1189    widget: WidgetKind,
1190    fill: impl FnOnce(&mut Attrs),
1191    listeners: Vec<(&'static str, EventCallback)>,
1192    content: impl FnOnce(),
1193) {
1194    let node = with_composer_if_idle(|composer| {
1195        if !composer.composing() {
1196            return None;
1197        }
1198        let mut attrs = composer.take_attrs();
1199        fill(&mut attrs);
1200        Some(composer.update_node(widget, attrs, listeners))
1201    })
1202    .flatten();
1203    let Some(node) = node else {
1204        return;
1205    };
1206    with_composer(|composer| composer.push_node_context(node));
1207    let _context = NodeContextGuard;
1208    content();
1209}
1210
1211/// Closes a widget's node context on every way out of its content, an unwind included.
1212struct NodeContextGuard;
1213
1214impl Drop for NodeContextGuard {
1215    fn drop(&mut self) {
1216        with_composer_if_idle(|composer| composer.pop_node_context());
1217    }
1218}
1219
1220/// A call site key for a widget, from its name.
1221pub(crate) const fn widget_key(name: &str) -> u64 {
1222    crate::runtime::composer::call_site(name, 0)
1223}
1224
1225#[cfg(test)]
1226mod fr19_9_tests {
1227    use super::*;
1228
1229    #[test]
1230    fn fr19_9_window_drag_builder_preserves_false() {
1231        for enabled in [false, true] {
1232            let mut attrs = Vec::new();
1233            push_modifiers(
1234                &Modifier::new().window_drag(enabled),
1235                Mods::Standard,
1236                &mut attrs,
1237            );
1238            assert!(attrs.iter().any(|(name, value)| *name == "window_drag"
1239                && matches!(value, OwnedAttr::Bool(value) if *value == enabled)));
1240        }
1241    }
1242}
1243
1244#[cfg(test)]
1245mod fr68_label_tests {
1246    use super::*;
1247
1248    #[test]
1249    fn fr68_repeated_labels_do_not_grow_the_table() {
1250        let table = LabelTable::new();
1251        let first = table.intern("Refresh", MAX_LABELS).unwrap();
1252        for _ in 0..10_000 {
1253            let again = table.intern("Refresh", MAX_LABELS).unwrap();
1254            assert!(std::ptr::eq(first, again));
1255        }
1256        assert_eq!(table.len(), 1);
1257    }
1258
1259    #[test]
1260    fn fr68_labels_past_the_cap_are_dropped() {
1261        let table = LabelTable::new();
1262        for n in 0..MAX_LABELS {
1263            assert!(table.intern(&format!("label {n}"), MAX_LABELS).is_some());
1264        }
1265        assert_eq!(table.len(), MAX_LABELS);
1266        assert_eq!(table.intern("one too many", MAX_LABELS), None);
1267        assert_eq!(table.len(), MAX_LABELS);
1268        // A name already kept is still found.
1269        assert!(table.intern("label 0", MAX_LABELS).is_some());
1270    }
1271}