Skip to main content

compose_rust/ui/
widgets.rs

1//! One composable per widget in the schema.
2//!
3//! Each is a function with the widget's Compose name that takes what the widget cannot do
4//! without, and returns a description whose methods set the rest. The description is
5//! composed when the statement it is written in ends, or at once by `content` for a
6//! widget that has children.
7//!
8//! Every attribute list below is the `rsx!` widget's, in its order, with its defaults. A
9//! difference would be a widget that draws differently depending on which API wrote it.
10
11#![allow(non_snake_case)]
12
13use super::{
14    Attrs, Modifier, Mods, dp_or_zero, emit, optional_text, paint_or_zero, push_modifiers,
15    role_or_zero, widget_key,
16};
17use crate::drawing::DrawList;
18use crate::fonts::FontRefs;
19use crate::runtime::composer::{EventCallback, OwnedAttr};
20use crate::schema::{
21    Alignment, Arrangement, ButtonKind, ButtonVariant, ColorRole, IconRole, Paint, SlotRole,
22    SpaceRole, TextAlign, TextOverflow, TypeRole, WidgetKind, WordBreak,
23};
24use crate::spans::TextSpans;
25use crate::{FileDrop, KeyEvent, PointerEvent, PointerKinds};
26use std::cell::RefCell;
27use std::rc::Rc;
28
29/// The empty handler every listener has until one is given. The `rsx!` widgets write
30/// their listeners whether or not a handler was supplied, so these do too.
31fn unit(handler: Option<Box<dyn FnMut()>>) -> EventCallback {
32    match handler {
33        Some(handler) => EventCallback::Unit(Rc::new(RefCell::new(handler))),
34        None => EventCallback::Unit(Rc::new(RefCell::new(|| {}))),
35    }
36}
37
38fn text_handler(handler: Option<Box<dyn FnMut(String)>>) -> EventCallback {
39    match handler {
40        Some(handler) => EventCallback::Text(Rc::new(RefCell::new(handler))),
41        None => EventCallback::Text(Rc::new(RefCell::new(|_: String| {}))),
42    }
43}
44
45fn value_handler(handler: Option<Box<dyn FnMut(f64)>>) -> EventCallback {
46    match handler {
47        Some(handler) => EventCallback::Value(Rc::new(RefCell::new(handler))),
48        None => EventCallback::Value(Rc::new(RefCell::new(|_: f64| {}))),
49    }
50}
51
52/// A node's subscription to pointer input: the kinds it wants and the handler that gets
53/// them. A node without one has no pointer input at all.
54struct PointerSubscription {
55    kinds: PointerKinds,
56    handler: Box<dyn FnMut(PointerEvent)>,
57}
58
59/// Writes a subscription as the two things the wire needs: the handler, and the mask. A
60/// subscription of no kinds writes neither, so it is not a subscription.
61fn pointer_input(
62    subscription: Option<PointerSubscription>,
63    attrs: &mut Attrs,
64    listeners: &mut Vec<(&'static str, EventCallback)>,
65) {
66    let Some(subscription) = subscription else {
67        return;
68    };
69    if subscription.kinds == PointerKinds::NONE {
70        return;
71    }
72    attrs.push((
73        "pointer_kinds",
74        OwnedAttr::Int(i64::from(subscription.kinds.bits())),
75    ));
76    listeners.push((
77        "onpointer",
78        EventCallback::Pointer(Rc::new(RefCell::new(subscription.handler))),
79    ));
80}
81
82/// What a node says about windows that is not a window's own: the key it is torn
83/// off under, and the handler for a dragged window passing over it.
84struct TearOff {
85    key: Option<String>,
86    drag_over: Option<Box<dyn FnMut(crate::windows::WindowDragOver)>>,
87}
88
89/// Writes a node's tear-off key and drag-over handler as what the wire needs. A node with
90/// neither writes nothing.
91fn tear_off_input(
92    tear_off: Option<TearOff>,
93    attrs: &mut Attrs,
94    listeners: &mut Vec<(&'static str, EventCallback)>,
95) {
96    let Some(tear_off) = tear_off else {
97        return;
98    };
99    if let Some(key) = tear_off.key {
100        attrs.push(("tear_off_key", OwnedAttr::Text(key)));
101    }
102    if let Some(mut handler) = tear_off.drag_over {
103        listeners.push((
104            "onwindowdragover",
105            EventCallback::Window(Rc::new(RefCell::new(
106                move |event: crate::windows::WindowEvent| {
107                    if let crate::windows::WindowEvent::DragOver { kind, window, x, y } = event {
108                        handler(crate::windows::WindowDragOver { kind, window, x, y });
109                    }
110                },
111            ))),
112        ));
113    }
114}
115
116/// Adds `tear_off_key` and `on_window_drag_over` to a builder that has a `tear_off` field.
117macro_rules! tear_off {
118    ($name:ident) => {
119        impl $name {
120            /// Lets the user pull this node out into a window of its own, named by `key`:
121            /// the `RestoreKey` of the window that results.
122            pub fn tear_off_key(mut self, key: impl Into<String>) -> Self {
123                self.tear_off
124                    .get_or_insert_with(|| TearOff {
125                        key: None,
126                        drag_over: None,
127                    })
128                    .key = Some(key.into());
129                self
130            }
131
132            /// Hears a dragged window pass over this node: where a tab row shows the place
133            /// a dropped tab would go.
134            pub fn on_window_drag_over(
135                mut self,
136                handler: impl FnMut(crate::windows::WindowDragOver) + 'static,
137            ) -> Self {
138                self.tear_off
139                    .get_or_insert_with(|| TearOff {
140                        key: None,
141                        drag_over: None,
142                    })
143                    .drag_over = Some(std::boxed::Box::new(handler));
144                self
145            }
146        }
147    };
148}
149
150/// Adds `on_pointer` to a builder that has a `pointer` field.
151macro_rules! pointer_subscription {
152    ($name:ident) => {
153        impl $name {
154            /// Subscribes the node to the kinds of pointer input in `kinds`. The handler runs
155            /// on the same call as the input; calling `consume` on the event keeps it from
156            /// the nearest subscribed ancestor and from the Renderer's own handling.
157            ///
158            /// A node that never calls this has no pointer input at all, and costs nothing.
159            pub fn on_pointer(
160                mut self,
161                kinds: PointerKinds,
162                handler: impl FnMut(PointerEvent) + 'static,
163            ) -> Self {
164                self.pointer = Some(PointerSubscription {
165                    kinds,
166                    handler: std::boxed::Box::new(handler),
167                });
168                self
169            }
170        }
171    };
172}
173
174/// Writes a container's click handler as the listener the wire needs. A container that was
175/// given none has no `OnClick` at all, so it is not a control: it takes no focus, no key and
176/// no pointer press, and says nothing to assistive technology.
177fn click_input(
178    on_click: Option<Box<dyn FnMut()>>,
179    listeners: &mut Vec<(&'static str, EventCallback)>,
180) {
181    if let Some(handler) = on_click {
182        listeners.push(("onclick", unit(Some(handler))));
183    }
184}
185
186/// Adds `on_click` to a builder that has an `on_click` field.
187macro_rules! click_handler {
188    ($name:ident) => {
189        impl $name {
190            /// Makes the container a button. The Renderer gives it keyboard focus, runs
191            /// the handler on a click and on Enter or Space, announces it to assistive
192            /// technology as a button, and shows hover, pressed and focus state.
193            ///
194            /// A container that never calls this is not a control and costs nothing.
195            pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
196                self.on_click = Some(std::boxed::Box::new(handler));
197                self
198            }
199        }
200    };
201}
202
203/// Composes the description when the statement it was written in ends.
204macro_rules! composed_on_drop {
205    ($name:ident $(<$lifetime:lifetime>)?) => {
206        impl$(<$lifetime>)? Drop for $name$(<$lifetime>)? {
207            fn drop(&mut self) {
208                // A panic is already unwinding the composition, and composing on the way
209                // out would only risk a second one.
210                if !self.done && !std::thread::panicking() {
211                    self.done = true;
212                    self.compose(|| {});
213                }
214            }
215        }
216
217        impl$(<$lifetime>)? $name$(<$lifetime>)? {
218            /// What sits around it. Compose's `modifier` parameter.
219            pub fn modifier(mut self, modifier: Modifier) -> Self {
220                self.modifier = modifier;
221                self
222            }
223        }
224    };
225}
226
227/// A container's `content`: composes it now, with these children.
228macro_rules! container {
229    ($name:ident $(<$lifetime:lifetime>)?) => {
230        composed_on_drop!($name $(<$lifetime>)?);
231
232        impl$(<$lifetime>)? $name$(<$lifetime>)? {
233            /// Composes this widget with `content` as its children.
234            pub fn content(mut self, content: impl FnOnce()) {
235                self.done = true;
236                self.compose(content);
237            }
238        }
239    };
240}
241
242// ----- layout ---------------------------------------------------------------------------
243
244/// The properties a linear layout takes.
245#[derive(Default)]
246struct Linear {
247    arrangement: Option<Arrangement>,
248    spacing: Option<f32>,
249    space_role: Option<SpaceRole>,
250    alignment: Option<Alignment>,
251    intrinsic_size: Option<bool>,
252}
253
254impl Linear {
255    fn push(&self, attrs: &mut Attrs) {
256        attrs.push(("arrangement", role_or_zero(self.arrangement)));
257        attrs.push(("spacing", dp_or_zero(self.spacing)));
258        attrs.push(("space_role", role_or_zero(self.space_role)));
259        attrs.push(("alignment", role_or_zero(self.alignment)));
260        if let Some(intrinsic) = self.intrinsic_size {
261            attrs.push(("collapsible", OwnedAttr::Bool(intrinsic)));
262        }
263    }
264}
265
266macro_rules! linear {
267    ($name:ident, $widget:ident, $key:literal) => {
268        #[doc = concat!("A `", stringify!($widget), "` and its builder.")]
269        pub struct $name {
270            modifier: Modifier,
271            linear: Linear,
272            pointer: Option<PointerSubscription>,
273            on_click: Option<Box<dyn FnMut()>>,
274            tear_off: Option<TearOff>,
275            done: bool,
276        }
277
278        #[doc = concat!("Lays its children out one after another. Compose's `", stringify!($widget), "`.")]
279        pub fn $widget() -> $name {
280            $name {
281                modifier: Modifier::new(),
282                linear: Linear::default(),
283                pointer: None,
284                on_click: None,
285                tear_off: None,
286                done: false,
287            }
288        }
289
290        impl $name {
291            /// How the children share the main axis.
292            pub fn arrangement(mut self, arrangement: Arrangement) -> Self {
293                self.linear.arrangement = Some(arrangement);
294                self
295            }
296
297            /// A literal gap between children, in dp.
298            pub fn spacing(mut self, dp: f32) -> Self {
299                self.linear.spacing = Some(dp);
300                self
301            }
302
303            /// A gap from the design system's spacing ladder.
304            pub fn space_role(mut self, role: SpaceRole) -> Self {
305                self.linear.space_role = Some(role);
306                self
307            }
308
309            /// Where the children sit on the cross axis.
310            pub fn alignment(mut self, alignment: Alignment) -> Self {
311                self.linear.alignment = Some(alignment);
312                self
313            }
314
315            /// Sizes the cross axis to the tallest child in a `Row` or the widest child in
316            /// a `Column`, so children with `fill_max_height` or `fill_max_width` match the
317            /// largest sibling without an explicit container size.
318            pub fn intrinsic_size(mut self, enabled: bool) -> Self {
319                self.linear.intrinsic_size = Some(enabled);
320                self
321            }
322
323            fn compose(&mut self, content: impl FnOnce()) {
324                let modifier = self.modifier;
325                let linear = std::mem::take(&mut self.linear);
326                let pointer = self.pointer.take();
327                let tear_off = self.tear_off.take();
328                let mut listeners = Vec::new();
329                let mut subscription = Attrs::new();
330                pointer_input(pointer, &mut subscription, &mut listeners);
331                click_input(self.on_click.take(), &mut listeners);
332                tear_off_input(tear_off, &mut subscription, &mut listeners);
333                emit(
334                    widget_key($key),
335                    WidgetKind::$widget,
336                    |attrs| {
337                        push_modifiers(&modifier, Mods::Container, attrs);
338                        linear.push(attrs);
339                        attrs.append(&mut subscription);
340                    },
341                    listeners,
342                    content,
343                );
344            }
345        }
346
347        container!($name);
348        pointer_subscription!($name);
349        click_handler!($name);
350        tear_off!($name);
351    };
352}
353
354linear!(ColumnScope, Column, "compose_rust::ui::Column");
355linear!(RowScope, Row, "compose_rust::ui::Row");
356
357impl RowScope {
358    /// Sizes the row to its tallest child so children with `fill_max_height` match their
359    /// siblings' height.
360    pub fn equal_height(self, enabled: bool) -> Self {
361        self.intrinsic_size(enabled)
362    }
363}
364
365impl ColumnScope {
366    /// Sizes the column to its widest child so children with `fill_max_width` match their
367    /// siblings' width.
368    pub fn equal_width(self, enabled: bool) -> Self {
369        self.intrinsic_size(enabled)
370    }
371}
372
373/// A `Box` and its builder. Named apart from `std::boxed::Box`, which a glob import of
374/// this module would otherwise hide.
375pub struct ComposeBox {
376    modifier: Modifier,
377    alignment: Option<Alignment>,
378    pointer: Option<PointerSubscription>,
379    on_click: Option<Box<dyn FnMut()>>,
380    tear_off: Option<TearOff>,
381    done: bool,
382}
383
384/// Stacks its children on top of each other. Compose's `Box`.
385pub fn Box() -> ComposeBox {
386    ComposeBox {
387        modifier: Modifier::new(),
388        alignment: None,
389        pointer: None,
390        on_click: None,
391        tear_off: None,
392        done: false,
393    }
394}
395
396impl ComposeBox {
397    /// Where the children sit inside it.
398    pub fn alignment(mut self, alignment: Alignment) -> Self {
399        self.alignment = Some(alignment);
400        self
401    }
402
403    fn compose(&mut self, content: impl FnOnce()) {
404        let (modifier, alignment) = (self.modifier, self.alignment);
405        let pointer = self.pointer.take();
406        let tear_off = self.tear_off.take();
407        let mut listeners = Vec::new();
408        let mut subscription = Attrs::new();
409        pointer_input(pointer, &mut subscription, &mut listeners);
410        click_input(self.on_click.take(), &mut listeners);
411        tear_off_input(tear_off, &mut subscription, &mut listeners);
412        emit(
413            widget_key("compose_rust::ui::Box"),
414            WidgetKind::Box,
415            |attrs| {
416                push_modifiers(&modifier, Mods::Container, attrs);
417                attrs.push(("alignment", role_or_zero(alignment)));
418                attrs.append(&mut subscription);
419            },
420            listeners,
421            content,
422        );
423    }
424}
425
426container!(ComposeBox);
427pointer_subscription!(ComposeBox);
428click_handler!(ComposeBox);
429tear_off!(ComposeBox);
430
431/// A plain container with the standard Modifiers and nothing of its own.
432macro_rules! plain_container {
433    ($name:ident, $widget:ident, $mods:expr, $key:literal, $doc:literal) => {
434        #[doc = concat!("A `", stringify!($widget), "` and its builder.")]
435        pub struct $name {
436            modifier: Modifier,
437            pointer: Option<PointerSubscription>,
438            on_click: Option<Box<dyn FnMut()>>,
439            done: bool,
440        }
441
442        #[doc = $doc]
443        pub fn $widget() -> $name {
444            $name {
445                modifier: Modifier::new(),
446                pointer: None,
447                on_click: None,
448                done: false,
449            }
450        }
451
452        impl $name {
453            fn compose(&mut self, content: impl FnOnce()) {
454                let modifier = self.modifier;
455                let pointer = self.pointer.take();
456                let mut listeners = Vec::new();
457                let mut subscription = Attrs::new();
458                pointer_input(pointer, &mut subscription, &mut listeners);
459                click_input(self.on_click.take(), &mut listeners);
460                emit(
461                    widget_key($key),
462                    WidgetKind::$widget,
463                    |attrs| {
464                        push_modifiers(&modifier, $mods, attrs);
465                        attrs.append(&mut subscription);
466                    },
467                    listeners,
468                    content,
469                );
470            }
471        }
472
473        container!($name);
474        pointer_subscription!($name);
475        click_handler!($name);
476    };
477}
478
479/// The scroll position of a [`ScrollColumn`] or [`ScrollRow`], and a handle to move it.
480///
481/// The Renderer owns the scroll physics and the scrollbar; this handle reads the current
482/// offset and maximum offset in dp as observable states and sends target scroll requests.
483#[derive(Clone, Debug, PartialEq, Eq)]
484pub struct ScrollState {
485    offset: crate::runtime::MutableState<f32>,
486    max_offset: crate::runtime::MutableState<f32>,
487    command: crate::runtime::MutableState<(i64, f32, bool)>,
488}
489
490impl Default for ScrollState {
491    fn default() -> Self {
492        Self::new()
493    }
494}
495
496impl ScrollState {
497    /// Creates a new scroll state starting at 0 dp.
498    pub fn new() -> Self {
499        Self {
500            offset: crate::runtime::mutable_state_of(0.0),
501            max_offset: crate::runtime::mutable_state_of(0.0),
502            command: crate::runtime::mutable_state_of((0, 0.0, false)),
503        }
504    }
505
506    /// The current scroll offset in dp. A composable that reads this runs again when the
507    /// user or a programmatic scroll moves the container.
508    pub fn offset(&self) -> f32 {
509        self.offset.get()
510    }
511
512    /// The furthest offset in dp the container can scroll to (`0.0` when the content fits
513    /// inside the viewport). A composable that reads this runs again when the content or
514    /// viewport size changes the scrollable distance.
515    pub fn max_offset(&self) -> f32 {
516        self.max_offset.get()
517    }
518
519    /// Jumps to `dp` (clamped by the Renderer to `0.0..=max_offset`).
520    pub fn scroll_to(&self, dp: f32) {
521        let (revision, _, _) = self.command.get_untracked();
522        self.command
523            .set((revision.wrapping_add(1).max(1), dp.max(0.0), false));
524    }
525
526    /// Smoothly scrolls to `dp` (clamped by the Renderer to `0.0..=max_offset`).
527    pub fn animate_scroll_to(&self, dp: f32) {
528        let (revision, _, _) = self.command.get_untracked();
529        self.command
530            .set((revision.wrapping_add(1).max(1), dp.max(0.0), true));
531    }
532}
533
534/// Remembers a [`ScrollState`] across recompositions. Compose's `rememberScrollState`.
535pub fn remember_scroll_state() -> ScrollState {
536    crate::runtime::remember(ScrollState::new)
537}
538
539fn scroll_state_input(
540    state: Option<ScrollState>,
541    attrs: &mut Attrs,
542    listeners: &mut Vec<(&'static str, EventCallback)>,
543) {
544    let Some(state) = state else {
545        return;
546    };
547    let (revision, target, animate) = state.command.get();
548    if revision > 0 {
549        attrs.push(("value", OwnedAttr::Float(f64::from(target))));
550        attrs.push(("determinate", OwnedAttr::Bool(animate)));
551        attrs.push(("count", OwnedAttr::Int(revision)));
552    }
553    let offset = state.offset.clone();
554    let max_offset = state.max_offset.clone();
555    listeners.push((
556        "onrangerequest",
557        EventCallback::Range(Rc::new(RefCell::new(move |range: crate::RangeRequest| {
558            let next_offset = f32::from_bits(range.start() as u32);
559            let next_max = f32::from_bits(range.count() as u32);
560            if next_offset.is_finite() {
561                offset.set(next_offset);
562            }
563            if next_max.is_finite() {
564                max_offset.set(next_max);
565            }
566        }))),
567    ));
568}
569
570macro_rules! scroll_container {
571    ($name:ident, $widget:ident, $key:literal, $doc:literal) => {
572        #[doc = concat!("A `", stringify!($widget), "` and its builder.")]
573        pub struct $name {
574            modifier: Modifier,
575            scroll_state: Option<ScrollState>,
576            pointer: Option<PointerSubscription>,
577            on_click: Option<Box<dyn FnMut()>>,
578            done: bool,
579        }
580
581        #[doc = $doc]
582        pub fn $widget() -> $name {
583            $name {
584                modifier: Modifier::new(),
585                scroll_state: None,
586                pointer: None,
587                on_click: None,
588                done: false,
589            }
590        }
591
592        impl $name {
593            /// Binds a [`ScrollState`] so the application can read `offset()` and
594            /// `max_offset()` or call `scroll_to` and `animate_scroll_to`.
595            pub fn scroll_state(mut self, state: &ScrollState) -> Self {
596                self.scroll_state = Some(state.clone());
597                self
598            }
599
600            fn compose(&mut self, content: impl FnOnce()) {
601                let modifier = self.modifier;
602                let scroll_state = self.scroll_state.take();
603                let pointer = self.pointer.take();
604                let mut listeners = Vec::new();
605                let mut subscription = Attrs::new();
606                scroll_state_input(scroll_state, &mut subscription, &mut listeners);
607                pointer_input(pointer, &mut subscription, &mut listeners);
608                click_input(self.on_click.take(), &mut listeners);
609                emit(
610                    widget_key($key),
611                    WidgetKind::$widget,
612                    |attrs| {
613                        push_modifiers(&modifier, Mods::Standard, attrs);
614                        attrs.append(&mut subscription);
615                    },
616                    listeners,
617                    content,
618                );
619            }
620        }
621
622        container!($name);
623        pointer_subscription!($name);
624        click_handler!($name);
625    };
626}
627
628scroll_container!(
629    ScrollColumnScope,
630    ScrollColumn,
631    "compose_rust::ui::ScrollColumn",
632    "All of its content, with a vertical scroll the Renderer owns."
633);
634scroll_container!(
635    ScrollRowScope,
636    ScrollRow,
637    "compose_rust::ui::ScrollRow",
638    "All of its content, with a horizontal scroll the Renderer owns."
639);
640plain_container!(
641    CardScope,
642    Card,
643    Mods::Container,
644    "compose_rust::ui::Card",
645    "A grouped container. What it looks like is the design system's."
646);
647plain_container!(
648    SurfaceScope,
649    Surface,
650    Mods::Container,
651    "compose_rust::ui::Surface",
652    "A plain background and elevation, without a card's meaning."
653);
654plain_container!(
655    SelectionContainerScope,
656    SelectionContainer,
657    Mods::Standard,
658    "compose_rust::ui::SelectionContainer",
659    "Every `Text` inside can be selected and copied as one selection."
660);
661plain_container!(
662    SpacerScope,
663    Spacer,
664    Mods::Standard,
665    "compose_rust::ui::Spacer",
666    "Empty space, sized by its Modifier."
667);
668
669// ----- text -----------------------------------------------------------------------------
670
671/// A font list as the property blob, or nothing when it is empty.
672fn font_bytes(font: &FontRefs) -> OwnedAttr {
673    if font.is_empty() {
674        OwnedAttr::None
675    } else {
676        OwnedAttr::Bytes(font.as_bytes().to_vec())
677    }
678}
679
680/// A `Text` and its builder.
681pub struct TextScope {
682    modifier: Modifier,
683    text: String,
684    spans: TextSpans,
685    type_role: Option<TypeRole>,
686    font_size: Option<f32>,
687    font_weight: Option<u16>,
688    line_height: Option<f32>,
689    letter_spacing: Option<f32>,
690    color: Option<Paint>,
691    text_align: Option<TextAlign>,
692    max_lines: Option<u32>,
693    overflow: Option<TextOverflow>,
694    font: FontRefs,
695    word_break: Option<WordBreak>,
696    pointer: Option<PointerSubscription>,
697    done: bool,
698}
699
700/// Text. Compose's `Text`.
701///
702/// `type_role` alone takes the design system's size, weight, line height and letter
703/// spacing; each other style method overrides one axis of it.
704pub fn Text(text: impl Into<String>) -> TextScope {
705    TextScope {
706        modifier: Modifier::new(),
707        text: text.into(),
708        spans: TextSpans::default(),
709        type_role: None,
710        font_size: None,
711        font_weight: None,
712        line_height: None,
713        letter_spacing: None,
714        color: None,
715        text_align: None,
716        max_lines: None,
717        overflow: None,
718        font: FontRefs::default(),
719        word_break: None,
720        pointer: None,
721        done: false,
722    }
723}
724
725impl TextScope {
726    /// Runs of different treatment inside the text: a bold phrase, a link, some code.
727    pub fn spans(mut self, spans: TextSpans) -> Self {
728        self.spans = spans;
729        self
730    }
731
732    pub fn type_role(mut self, role: TypeRole) -> Self {
733        self.type_role = Some(role);
734        self
735    }
736
737    pub fn font_size(mut self, sp: f32) -> Self {
738        self.font_size = Some(sp);
739        self
740    }
741
742    pub fn font_weight(mut self, weight: u16) -> Self {
743        self.font_weight = Some(weight);
744        self
745    }
746
747    pub fn line_height(mut self, sp: f32) -> Self {
748        self.line_height = Some(sp);
749        self
750    }
751
752    pub fn letter_spacing(mut self, sp: f32) -> Self {
753        self.letter_spacing = Some(sp);
754        self
755    }
756
757    pub fn color(mut self, paint: Paint) -> Self {
758        self.color = Some(paint);
759        self
760    }
761
762    pub fn text_align(mut self, align: TextAlign) -> Self {
763        self.text_align = Some(align);
764        self
765    }
766
767    pub fn max_lines(mut self, lines: u32) -> Self {
768        self.max_lines = Some(lines);
769        self
770    }
771
772    pub fn overflow(mut self, overflow: TextOverflow) -> Self {
773        self.overflow = Some(overflow);
774        self
775    }
776
777    /// The fonts to try, in order. Only text with `TypeRole::None` takes its own fonts;
778    /// text with a role is set in the theme's font for that role.
779    pub fn font(mut self, font: FontRefs) -> Self {
780        self.font = font;
781        self
782    }
783
784    /// Where a line may break inside a word. CSS `word-break`.
785    pub fn word_break(mut self, word_break: WordBreak) -> Self {
786        self.word_break = Some(word_break);
787        self
788    }
789
790    fn compose(&mut self, content: impl FnOnce()) {
791        let modifier = self.modifier;
792        let text = std::mem::take(&mut self.text);
793        let spans = std::mem::take(&mut self.spans);
794        let font = std::mem::take(&mut self.font);
795        let word_break = self.word_break;
796        let (type_role, font_size, font_weight, line_height, letter_spacing) = (
797            self.type_role,
798            self.font_size,
799            self.font_weight,
800            self.line_height,
801            self.letter_spacing,
802        );
803        let (color, text_align, max_lines, overflow) =
804            (self.color, self.text_align, self.max_lines, self.overflow);
805        let pointer = self.pointer.take();
806        let mut listeners = Vec::new();
807        let mut subscription = Attrs::new();
808        pointer_input(pointer, &mut subscription, &mut listeners);
809        emit(
810            widget_key("compose_rust::ui::Text"),
811            WidgetKind::Text,
812            |attrs| {
813                push_modifiers(&modifier, Mods::Standard, attrs);
814                // Left out entirely when there are none, as the rsx widget leaves it out.
815                let span_fonts = spans.font_table().to_vec();
816                attrs.push((
817                    "spans",
818                    if spans.is_empty() {
819                        OwnedAttr::None
820                    } else {
821                        OwnedAttr::Spans(spans)
822                    },
823                ));
824                // The fonts each styled run names travel beside the runs, and are left out
825                // when no run names any.
826                attrs.push((
827                    "span_fonts",
828                    if span_fonts.is_empty() {
829                        OwnedAttr::None
830                    } else {
831                        OwnedAttr::Bytes(span_fonts)
832                    },
833                ));
834                attrs.push(("font", font_bytes(&font)));
835                attrs.push(("word_break", super::role(word_break)));
836                attrs.push(("text", OwnedAttr::Text(text)));
837                attrs.push(("type_role", role_or_zero(type_role)));
838                attrs.push(("font_size", dp_or_zero(font_size)));
839                attrs.push((
840                    "font_weight",
841                    OwnedAttr::Int(i64::from(font_weight.unwrap_or(0))),
842                ));
843                attrs.push(("line_height", dp_or_zero(line_height)));
844                attrs.push(("letter_spacing", dp_or_zero(letter_spacing)));
845                attrs.push(("color", paint_or_zero(color)));
846                attrs.push(("text_align", role_or_zero(text_align)));
847                attrs.push((
848                    "max_lines",
849                    OwnedAttr::Int(i64::from(max_lines.unwrap_or(0))),
850                ));
851                attrs.push(("overflow", role_or_zero(overflow)));
852                attrs.append(&mut subscription);
853            },
854            listeners,
855            content,
856        );
857    }
858}
859
860composed_on_drop!(TextScope);
861pointer_subscription!(TextScope);
862
863/// A `TextField` and its builder.
864pub struct TextFieldScope {
865    modifier: Modifier,
866    text: String,
867    placeholder: String,
868    enabled: bool,
869    multiline: bool,
870    type_role: Option<TypeRole>,
871    font: FontRefs,
872    on_value_change: Option<Box<dyn FnMut(String)>>,
873    on_submit: Option<Box<dyn FnMut(String)>>,
874    on_focus_lost: Option<Box<dyn FnMut()>>,
875    on_key_down: Option<Box<dyn FnMut(KeyEvent)>>,
876    done: bool,
877}
878
879/// An uncontrolled text field. Compose's `TextField`, with the value kept where the
880/// platform's text input keeps it: the Renderer reports each change and the Host never
881/// writes the value back while the user types.
882pub fn TextField() -> TextFieldScope {
883    TextFieldScope {
884        modifier: Modifier::new(),
885        text: String::new(),
886        placeholder: String::new(),
887        enabled: true,
888        multiline: false,
889        type_role: None,
890        font: FontRefs::default(),
891        on_value_change: None,
892        on_submit: None,
893        on_focus_lost: None,
894        on_key_down: None,
895        done: false,
896    }
897}
898
899impl TextFieldScope {
900    /// The text the field starts with. Only the start: the field stays uncontrolled, so
901    /// what the user types is never written back, and a later value here changes nothing
902    /// on the field that is already there. To show other text, such as another document,
903    /// give the field a new identity with `key`, and the new field starts with this text.
904    pub fn text(mut self, text: impl Into<String>) -> Self {
905        self.text = text.into();
906        self
907    }
908
909    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
910        self.placeholder = placeholder.into();
911        self
912    }
913
914    pub fn enabled(mut self, enabled: bool) -> Self {
915        self.enabled = enabled;
916        self
917    }
918
919    pub fn multiline(mut self, multiline: bool) -> Self {
920        self.multiline = multiline;
921        self
922    }
923
924    /// The rung of the type ladder the field's own text is set in.
925    pub fn type_role(mut self, role: TypeRole) -> Self {
926        self.type_role = Some(role);
927        self
928    }
929
930    /// The fonts to try for the field's text, in order. Taken only with `TypeRole::None`.
931    pub fn font(mut self, font: FontRefs) -> Self {
932        self.font = font;
933        self
934    }
935
936    pub fn on_value_change(mut self, handler: impl FnMut(String) + 'static) -> Self {
937        self.on_value_change = Some(std::boxed::Box::new(handler));
938        self
939    }
940
941    pub fn on_submit(mut self, handler: impl FnMut(String) + 'static) -> Self {
942        self.on_submit = Some(std::boxed::Box::new(handler));
943        self
944    }
945
946    pub fn on_focus_lost(mut self, handler: impl FnMut() + 'static) -> Self {
947        self.on_focus_lost = Some(std::boxed::Box::new(handler));
948        self
949    }
950
951    /// Sees each key the field is pressed with; calling `consume` on the event keeps the
952    /// field from acting on it.
953    pub fn on_key_down(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
954        self.on_key_down = Some(std::boxed::Box::new(handler));
955        self
956    }
957
958    fn compose(&mut self, content: impl FnOnce()) {
959        let modifier = self.modifier;
960        let text = std::mem::take(&mut self.text);
961        let placeholder = std::mem::take(&mut self.placeholder);
962        let (enabled, multiline, type_role) = (self.enabled, self.multiline, self.type_role);
963        let font = std::mem::take(&mut self.font);
964        let key_down: EventCallback = match self.on_key_down.take() {
965            Some(handler) => EventCallback::Key(Rc::new(RefCell::new(handler))),
966            None => EventCallback::Key(Rc::new(RefCell::new(|_: KeyEvent| {}))),
967        };
968        let listeners = vec![
969            ("onvaluechange", text_handler(self.on_value_change.take())),
970            ("onsubmit", text_handler(self.on_submit.take())),
971            ("onfocuslost", unit(self.on_focus_lost.take())),
972            ("onkeydown", key_down),
973        ];
974        emit(
975            widget_key("compose_rust::ui::TextField"),
976            WidgetKind::TextField,
977            |attrs| {
978                push_modifiers(&modifier, Mods::Standard, attrs);
979                // Written only when there is a starting text, so a field without one sends
980                // the same records it always did.
981                if !text.is_empty() {
982                    attrs.push(("text", OwnedAttr::Text(text)));
983                }
984                attrs.push(("placeholder", OwnedAttr::Text(placeholder)));
985                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
986                attrs.push(("multiline", OwnedAttr::Bool(multiline)));
987                attrs.push(("type_role", role_or_zero(type_role)));
988                attrs.push(("font", font_bytes(&font)));
989            },
990            listeners,
991            content,
992        );
993    }
994}
995
996composed_on_drop!(TextFieldScope);
997
998// ----- buttons and controls ---------------------------------------------------------------
999
1000/// The label's line limit and overflow, written only when they were set, so a widget that
1001/// never asks for them sends what it always sent.
1002fn lines_and_overflow(max_lines: Option<u32>, overflow: Option<TextOverflow>, attrs: &mut Attrs) {
1003    if let Some(lines) = max_lines {
1004        attrs.push(("max_lines", OwnedAttr::Int(i64::from(lines))));
1005    }
1006    if overflow.is_some() {
1007        attrs.push(("overflow", role_or_zero(overflow)));
1008    }
1009}
1010
1011/// A `Button` and its builder.
1012pub struct ButtonScope {
1013    modifier: Modifier,
1014    text: String,
1015    icon: Option<IconRole>,
1016    enabled: bool,
1017    variant: Option<ButtonVariant>,
1018    kind: Option<ButtonKind>,
1019    color: Option<Paint>,
1020    max_lines: Option<u32>,
1021    overflow: Option<TextOverflow>,
1022    on_click: Option<Box<dyn FnMut()>>,
1023    done: bool,
1024}
1025
1026/// A button with a label. Compose's `Button`; the variant is the design system's seam,
1027/// so `ButtonVariant::Text` is Compose's `TextButton` and `Outlined` its `OutlinedButton`.
1028///
1029/// An empty label with an icon is an icon button, named for assistive technology by its
1030/// icon's meaning.
1031pub fn Button(text: impl Into<String>) -> ButtonScope {
1032    ButtonScope {
1033        modifier: Modifier::new(),
1034        text: text.into(),
1035        icon: None,
1036        enabled: true,
1037        variant: None,
1038        kind: None,
1039        color: None,
1040        max_lines: None,
1041        overflow: None,
1042        on_click: None,
1043        done: false,
1044    }
1045}
1046
1047impl ButtonScope {
1048    /// The meaning of the glyph on it, never a picture.
1049    pub fn icon(mut self, icon: IconRole) -> Self {
1050        self.icon = Some(icon);
1051        self
1052    }
1053
1054    pub fn enabled(mut self, enabled: bool) -> Self {
1055        self.enabled = enabled;
1056        self
1057    }
1058
1059    pub fn variant(mut self, variant: ButtonVariant) -> Self {
1060        self.variant = Some(variant);
1061        self
1062    }
1063
1064    /// What the button is, apart from how loud it is: `ButtonKind::ActionKey` is one key of
1065    /// a dense grid, drawn in the design system's own calculator-key shape.
1066    pub fn kind(mut self, kind: ButtonKind) -> Self {
1067        self.kind = Some(kind);
1068        self
1069    }
1070
1071    /// How many lines the label may take. A label is one line unless this says more, so a
1072    /// Korean label is never broken between syllables by a squeezed parent.
1073    pub fn max_lines(mut self, lines: u32) -> Self {
1074        self.max_lines = Some(lines);
1075        self
1076    }
1077
1078    /// What happens to a label longer than its lines.
1079    pub fn overflow(mut self, overflow: TextOverflow) -> Self {
1080        self.overflow = Some(overflow);
1081        self
1082    }
1083
1084    /// The label's colour, for the rare button whose meaning is not its variant's.
1085    pub fn color(mut self, paint: Paint) -> Self {
1086        self.color = Some(paint);
1087        self
1088    }
1089
1090    /// The same, when there may be no colour of its own.
1091    pub fn color_if(mut self, paint: Option<Paint>) -> Self {
1092        self.color = paint;
1093        self
1094    }
1095
1096    pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
1097        self.on_click = Some(std::boxed::Box::new(handler));
1098        self
1099    }
1100
1101    fn compose(&mut self, content: impl FnOnce()) {
1102        let modifier = self.modifier;
1103        let text = std::mem::take(&mut self.text);
1104        let (icon, enabled, variant, kind, color) =
1105            (self.icon, self.enabled, self.variant, self.kind, self.color);
1106        let (max_lines, overflow) = (self.max_lines, self.overflow);
1107        let listeners = vec![("onclick", unit(self.on_click.take()))];
1108        emit(
1109            widget_key("compose_rust::ui::Button"),
1110            WidgetKind::Button,
1111            |attrs| {
1112                push_modifiers(&modifier, Mods::Standard, attrs);
1113                attrs.push(("text", OwnedAttr::Text(text)));
1114                attrs.push(("icon", super::role(icon)));
1115                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1116                attrs.push(("variant", role_or_zero(variant)));
1117                attrs.push(("button_kind", role_or_zero(kind)));
1118                attrs.push(("color", super::paint(color)));
1119                lines_and_overflow(max_lines, overflow, attrs);
1120            },
1121            listeners,
1122            content,
1123        );
1124    }
1125}
1126
1127composed_on_drop!(ButtonScope);
1128
1129/// A two-state control, for Checkbox, RadioButton and Switch alike: the Host owns the
1130/// value, the control draws exactly what it is told and reports what the user asked for.
1131macro_rules! toggle {
1132    ($name:ident, $widget:ident, $setter:ident, $key:literal, $doc:literal) => {
1133        #[doc = concat!("A `", stringify!($widget), "` and its builder.")]
1134        pub struct $name {
1135            modifier: Modifier,
1136            checked: bool,
1137            enabled: bool,
1138            on_change: Option<Box<dyn FnMut(bool)>>,
1139            done: bool,
1140        }
1141
1142        #[doc = $doc]
1143        pub fn $widget($setter: bool) -> $name {
1144            $name {
1145                modifier: Modifier::new(),
1146                checked: $setter,
1147                enabled: true,
1148                on_change: None,
1149                done: false,
1150            }
1151        }
1152
1153        impl $name {
1154            pub fn enabled(mut self, enabled: bool) -> Self {
1155                self.enabled = enabled;
1156                self
1157            }
1158
1159            /// Called with the state the user asked for. The control changes only when the
1160            /// value passed in does.
1161            pub fn on_change(mut self, handler: impl FnMut(bool) + 'static) -> Self {
1162                self.on_change = Some(std::boxed::Box::new(handler));
1163                self
1164            }
1165
1166            fn compose(&mut self, content: impl FnOnce()) {
1167                let modifier = self.modifier;
1168                let (checked, enabled) = (self.checked, self.enabled);
1169                let handler: EventCallback = match self.on_change.take() {
1170                    Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(
1171                        // Off is 0.0 and on is anything else, so a Renderer that reports a
1172                        // half-finished transition still reads as on.
1173                        move |value: f64| handler(value != 0.0),
1174                    ))),
1175                    None => value_handler(None),
1176                };
1177                emit(
1178                    widget_key($key),
1179                    WidgetKind::$widget,
1180                    |attrs| {
1181                        push_modifiers(&modifier, Mods::Standard, attrs);
1182                        attrs.push(("checked", OwnedAttr::Bool(checked)));
1183                        attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1184                    },
1185                    vec![("onchange", handler)],
1186                    content,
1187                );
1188            }
1189        }
1190
1191        composed_on_drop!($name);
1192    };
1193}
1194
1195toggle!(
1196    CheckboxScope,
1197    Checkbox,
1198    checked,
1199    "compose_rust::ui::Checkbox",
1200    "A box that is ticked or not. Compose's `Checkbox(checked, onCheckedChange)`."
1201);
1202toggle!(
1203    RadioButtonScope,
1204    RadioButton,
1205    selected,
1206    "compose_rust::ui::RadioButton",
1207    "One choice out of several. Compose's `RadioButton(selected, onClick)`."
1208);
1209toggle!(
1210    SwitchScope,
1211    Switch,
1212    checked,
1213    "compose_rust::ui::Switch",
1214    "An on and off control. Compose's `Switch(checked, onCheckedChange)`."
1215);
1216
1217/// A `Slider` and its builder.
1218pub struct SliderScope {
1219    modifier: Modifier,
1220    color: Option<Paint>,
1221    value: f32,
1222    min: f32,
1223    max: f32,
1224    steps: u32,
1225    enabled: bool,
1226    on_change: Option<Box<dyn FnMut(f32)>>,
1227    done: bool,
1228}
1229
1230/// A value picked from a range. Compose's `Slider`. The drag is the Renderer's; `value`
1231/// seeds it and moves it when the change came from elsewhere.
1232pub fn Slider(value: f32) -> SliderScope {
1233    SliderScope {
1234        modifier: Modifier::new(),
1235        color: None,
1236        value,
1237        min: 0.0,
1238        max: 1.0,
1239        steps: 0,
1240        enabled: true,
1241        on_change: None,
1242        done: false,
1243    }
1244}
1245
1246impl SliderScope {
1247    /// The two ends. Compose's `valueRange`.
1248    pub fn range(mut self, min: f32, max: f32) -> Self {
1249        self.min = min;
1250        self.max = max;
1251        self
1252    }
1253
1254    /// Stops between the ends; zero is continuous.
1255    pub fn steps(mut self, steps: u32) -> Self {
1256        self.steps = steps;
1257        self
1258    }
1259
1260    pub fn enabled(mut self, enabled: bool) -> Self {
1261        self.enabled = enabled;
1262        self
1263    }
1264
1265    /// The filled part of the track and the thumb.
1266    pub fn color(mut self, paint: Paint) -> Self {
1267        self.color = Some(paint);
1268        self
1269    }
1270
1271    pub fn on_change(mut self, handler: impl FnMut(f32) + 'static) -> Self {
1272        self.on_change = Some(std::boxed::Box::new(handler));
1273        self
1274    }
1275
1276    fn compose(&mut self, content: impl FnOnce()) {
1277        let modifier = self.modifier;
1278        let (color, value, min, max, steps, enabled) = (
1279            self.color,
1280            self.value,
1281            self.min,
1282            self.max,
1283            self.steps,
1284            self.enabled,
1285        );
1286        let handler = match self.on_change.take() {
1287            Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
1288                handler(value as f32)
1289            }))),
1290            None => value_handler(None),
1291        };
1292        emit(
1293            widget_key("compose_rust::ui::Slider"),
1294            WidgetKind::Slider,
1295            |attrs| {
1296                push_modifiers(&modifier, Mods::Standard, attrs);
1297                attrs.push(("color", super::paint(color)));
1298                attrs.push(("value", OwnedAttr::Float(f64::from(value))));
1299                attrs.push(("min", OwnedAttr::Float(f64::from(min))));
1300                attrs.push(("max", OwnedAttr::Float(f64::from(max))));
1301                attrs.push(("steps", OwnedAttr::Int(i64::from(steps))));
1302                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1303            },
1304            vec![("onchange", handler)],
1305            content,
1306        );
1307    }
1308}
1309
1310composed_on_drop!(SliderScope);
1311
1312/// A `ProgressIndicator` and its builder.
1313pub struct ProgressIndicatorScope {
1314    modifier: Modifier,
1315    value: f32,
1316    determinate: bool,
1317    circular: bool,
1318    done: bool,
1319}
1320
1321/// How far along something is, as a fraction. Compose's `LinearProgressIndicator(progress)`.
1322pub fn ProgressIndicator(value: f32) -> ProgressIndicatorScope {
1323    ProgressIndicatorScope {
1324        modifier: Modifier::new(),
1325        value,
1326        determinate: true,
1327        circular: false,
1328        done: false,
1329    }
1330}
1331
1332/// Work in progress of no known length.
1333pub fn IndeterminateProgressIndicator() -> ProgressIndicatorScope {
1334    ProgressIndicator(0.0).determinate(false)
1335}
1336
1337impl ProgressIndicatorScope {
1338    pub fn determinate(mut self, determinate: bool) -> Self {
1339        self.determinate = determinate;
1340        self
1341    }
1342
1343    /// The circular form rather than the linear one.
1344    pub fn circular(mut self, circular: bool) -> Self {
1345        self.circular = circular;
1346        self
1347    }
1348
1349    fn compose(&mut self, content: impl FnOnce()) {
1350        let modifier = self.modifier;
1351        let (value, determinate, circular) = (self.value, self.determinate, self.circular);
1352        emit(
1353            widget_key("compose_rust::ui::ProgressIndicator"),
1354            WidgetKind::ProgressIndicator,
1355            |attrs| {
1356                push_modifiers(&modifier, Mods::Standard, attrs);
1357                attrs.push(("value", OwnedAttr::Float(f64::from(value))));
1358                attrs.push(("determinate", OwnedAttr::Bool(determinate)));
1359                attrs.push(("circular", OwnedAttr::Bool(circular)));
1360            },
1361            Vec::new(),
1362            content,
1363        );
1364    }
1365}
1366
1367composed_on_drop!(ProgressIndicatorScope);
1368
1369/// A `Divider` and its builder.
1370pub struct DividerScope {
1371    modifier: Modifier,
1372    vertical: bool,
1373    done: bool,
1374}
1375
1376/// A line between two things. Compose's `HorizontalDivider`; `vertical` makes it the other.
1377pub fn Divider() -> DividerScope {
1378    DividerScope {
1379        modifier: Modifier::new(),
1380        vertical: false,
1381        done: false,
1382    }
1383}
1384
1385/// A hairline between two rows of a grouped list, in the design system's quiet edge or
1386/// the given colour. The same node as `Divider().modifier(Modifier.background(color))`.
1387pub fn Separator(color: Option<Paint>) -> DividerScope {
1388    Divider().modifier(Modifier::new().background_if(color))
1389}
1390
1391impl DividerScope {
1392    pub fn vertical(mut self, vertical: bool) -> Self {
1393        self.vertical = vertical;
1394        self
1395    }
1396
1397    fn compose(&mut self, content: impl FnOnce()) {
1398        let modifier = self.modifier;
1399        let vertical = self.vertical;
1400        emit(
1401            widget_key("compose_rust::ui::Divider"),
1402            WidgetKind::Divider,
1403            |attrs| {
1404                push_modifiers(&modifier, Mods::Standard, attrs);
1405                attrs.push(("vertical", OwnedAttr::Bool(vertical)));
1406            },
1407            Vec::new(),
1408            content,
1409        );
1410    }
1411}
1412
1413composed_on_drop!(DividerScope);
1414
1415// ----- pictures -------------------------------------------------------------------------
1416
1417/// An `Image` or an `Icon` and its builder.
1418pub struct AssetScope {
1419    modifier: Modifier,
1420    widget: WidgetKind,
1421    asset_id: u32,
1422    color: Option<Paint>,
1423    done: bool,
1424}
1425
1426/// A registered asset drawn as a picture. Compose's `Image`.
1427pub fn Image(asset_id: u32) -> AssetScope {
1428    AssetScope {
1429        modifier: Modifier::new(),
1430        widget: WidgetKind::Image,
1431        asset_id,
1432        color: None,
1433        done: false,
1434    }
1435}
1436
1437/// A registered icon, tinted. Compose's `Icon`.
1438pub fn Icon(asset_id: u32) -> AssetScope {
1439    AssetScope {
1440        modifier: Modifier::new(),
1441        widget: WidgetKind::Icon,
1442        asset_id,
1443        color: None,
1444        done: false,
1445    }
1446}
1447
1448impl AssetScope {
1449    /// The tint. An icon only; an image ignores it, as the rsx widget has no such property.
1450    pub fn color(mut self, paint: Paint) -> Self {
1451        self.color = Some(paint);
1452        self
1453    }
1454
1455    fn compose(&mut self, content: impl FnOnce()) {
1456        let modifier = self.modifier;
1457        let (widget, asset_id, color) = (self.widget, self.asset_id, self.color);
1458        let key = if widget == WidgetKind::Icon {
1459            widget_key("compose_rust::ui::Icon")
1460        } else {
1461            widget_key("compose_rust::ui::Image")
1462        };
1463        emit(
1464            key,
1465            widget,
1466            |attrs| {
1467                push_modifiers(&modifier, Mods::Standard, attrs);
1468                attrs.push(("asset", OwnedAttr::Int(i64::from(asset_id))));
1469                if widget == WidgetKind::Icon {
1470                    attrs.push(("color", paint_or_zero(color)));
1471                }
1472            },
1473            Vec::new(),
1474            content,
1475        );
1476    }
1477}
1478
1479composed_on_drop!(AssetScope);
1480
1481/// A `Canvas` and its builder.
1482pub struct CanvasScope {
1483    modifier: Modifier,
1484    commands: DrawList,
1485    view_box: Option<(f32, f32)>,
1486    done: bool,
1487}
1488
1489/// Pixels the widget vocabulary cannot produce. Compose's `Canvas`, with the drawing as a
1490/// value rather than a callback: drawing code cannot cross into the Renderer, and an equal
1491/// list costs no record.
1492pub fn Canvas(commands: DrawList) -> CanvasScope {
1493    let view_box = commands.view_box();
1494    CanvasScope {
1495        modifier: Modifier::new(),
1496        commands,
1497        view_box,
1498        done: false,
1499    }
1500}
1501
1502impl CanvasScope {
1503    /// Scales the command list's coordinates from `(width, height)` to the `Canvas`'s
1504    /// measured width and height. Stroke widths stay in dp.
1505    pub fn view_box(mut self, width: f32, height: f32) -> Self {
1506        self.view_box = Some((width, height));
1507        self
1508    }
1509
1510    /// Scales the command list's coordinates as `0.0..=1.0` fractions of the `Canvas`'s
1511    /// measured width and height. Stroke widths stay in dp.
1512    pub fn fractional(self) -> Self {
1513        self.view_box(1.0, 1.0)
1514    }
1515
1516    fn compose(&mut self, content: impl FnOnce()) {
1517        let modifier = self.modifier;
1518        let commands = std::mem::take(&mut self.commands);
1519        let view_box = self.view_box;
1520        emit(
1521            widget_key("compose_rust::ui::Canvas"),
1522            WidgetKind::Canvas,
1523            |attrs| {
1524                push_modifiers(&modifier, Mods::Standard, attrs);
1525                attrs.push(("commands", OwnedAttr::Draw(commands)));
1526                if let Some((width, height)) = view_box {
1527                    attrs.push(("min", OwnedAttr::Float(f64::from(width))));
1528                    attrs.push(("max", OwnedAttr::Float(f64::from(height))));
1529                }
1530            },
1531            Vec::new(),
1532            content,
1533        );
1534    }
1535}
1536
1537composed_on_drop!(CanvasScope);
1538
1539// ----- pickers ----------------------------------------------------------------------------
1540
1541/// A `DatePicker` and its builder.
1542pub struct DatePickerScope {
1543    modifier: Modifier,
1544    value: i64,
1545    min: Option<i64>,
1546    max: Option<i64>,
1547    enabled: bool,
1548    on_change: Option<Box<dyn FnMut(i64)>>,
1549    done: bool,
1550}
1551
1552/// A date, as whole days since 1970-01-01. How it is picked is the design system's.
1553pub fn DatePicker(value: i64) -> DatePickerScope {
1554    DatePickerScope {
1555        modifier: Modifier::new(),
1556        value,
1557        min: None,
1558        max: None,
1559        enabled: true,
1560        on_change: None,
1561        done: false,
1562    }
1563}
1564
1565impl DatePickerScope {
1566    pub fn min(mut self, days: i64) -> Self {
1567        self.min = Some(days);
1568        self
1569    }
1570
1571    pub fn max(mut self, days: i64) -> Self {
1572        self.max = Some(days);
1573        self
1574    }
1575
1576    pub fn enabled(mut self, enabled: bool) -> Self {
1577        self.enabled = enabled;
1578        self
1579    }
1580
1581    pub fn on_change(mut self, handler: impl FnMut(i64) + 'static) -> Self {
1582        self.on_change = Some(std::boxed::Box::new(handler));
1583        self
1584    }
1585
1586    fn compose(&mut self, content: impl FnOnce()) {
1587        let modifier = self.modifier;
1588        let (value, min, max, enabled) = (self.value, self.min, self.max, self.enabled);
1589        let handler = match self.on_change.take() {
1590            Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
1591                handler(value as i64)
1592            }))),
1593            None => value_handler(None),
1594        };
1595        emit(
1596            widget_key("compose_rust::ui::DatePicker"),
1597            WidgetKind::DatePicker,
1598            |attrs| {
1599                push_modifiers(&modifier, Mods::Standard, attrs);
1600                attrs.push(("value", OwnedAttr::Int(value)));
1601                attrs.push(("min", OwnedAttr::Int(min.unwrap_or(i64::MIN))));
1602                attrs.push(("max", OwnedAttr::Int(max.unwrap_or(i64::MAX))));
1603                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1604            },
1605            vec![("onchange", handler)],
1606            content,
1607        );
1608    }
1609}
1610
1611composed_on_drop!(DatePickerScope);
1612
1613const MINUTES_IN_A_DAY: u32 = 24 * 60;
1614
1615/// A `TimePicker` and its builder.
1616pub struct TimePickerScope {
1617    modifier: Modifier,
1618    value: u32,
1619    min: Option<u32>,
1620    max: Option<u32>,
1621    enabled: bool,
1622    on_change: Option<Box<dyn FnMut(u32)>>,
1623    done: bool,
1624}
1625
1626/// A time of day, as minutes since midnight.
1627pub fn TimePicker(value: u32) -> TimePickerScope {
1628    TimePickerScope {
1629        modifier: Modifier::new(),
1630        value,
1631        min: None,
1632        max: None,
1633        enabled: true,
1634        on_change: None,
1635        done: false,
1636    }
1637}
1638
1639impl TimePickerScope {
1640    pub fn min(mut self, minutes: u32) -> Self {
1641        self.min = Some(minutes);
1642        self
1643    }
1644
1645    pub fn max(mut self, minutes: u32) -> Self {
1646        self.max = Some(minutes);
1647        self
1648    }
1649
1650    pub fn enabled(mut self, enabled: bool) -> Self {
1651        self.enabled = enabled;
1652        self
1653    }
1654
1655    pub fn on_change(mut self, handler: impl FnMut(u32) + 'static) -> Self {
1656        self.on_change = Some(std::boxed::Box::new(handler));
1657        self
1658    }
1659
1660    fn compose(&mut self, content: impl FnOnce()) {
1661        let modifier = self.modifier;
1662        let (value, min, max, enabled) = (self.value, self.min, self.max, self.enabled);
1663        let handler = match self.on_change.take() {
1664            Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
1665                handler((value as i64).clamp(0, i64::from(MINUTES_IN_A_DAY - 1)) as u32)
1666            }))),
1667            None => value_handler(None),
1668        };
1669        emit(
1670            widget_key("compose_rust::ui::TimePicker"),
1671            WidgetKind::TimePicker,
1672            |attrs| {
1673                push_modifiers(&modifier, Mods::Standard, attrs);
1674                attrs.push(("value", OwnedAttr::Int(i64::from(value))));
1675                attrs.push(("min", OwnedAttr::Int(i64::from(min.unwrap_or(0)))));
1676                attrs.push((
1677                    "max",
1678                    OwnedAttr::Int(i64::from(max.unwrap_or(MINUTES_IN_A_DAY - 1))),
1679                ));
1680                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1681            },
1682            vec![("onchange", handler)],
1683            content,
1684        );
1685    }
1686}
1687
1688composed_on_drop!(TimePickerScope);
1689
1690/// A `Dropdown` and its builder.
1691pub struct DropdownScope {
1692    modifier: Modifier,
1693    selected_index: usize,
1694    enabled: bool,
1695    closed: bool,
1696    on_change: Option<Box<dyn FnMut(usize)>>,
1697    done: bool,
1698}
1699
1700/// One choice out of a list; each child is one option.
1701pub fn Dropdown(selected_index: usize) -> DropdownScope {
1702    DropdownScope {
1703        modifier: Modifier::new(),
1704        selected_index,
1705        enabled: true,
1706        closed: false,
1707        on_change: None,
1708        done: false,
1709    }
1710}
1711
1712/// A compact closed-field selector that always rests as a single line and opens its
1713/// options in a popup below the field on every design system.
1714pub fn Select(selected_index: usize) -> DropdownScope {
1715    Dropdown(selected_index).closed(true)
1716}
1717
1718impl DropdownScope {
1719    pub fn enabled(mut self, enabled: bool) -> Self {
1720        self.enabled = enabled;
1721        self
1722    }
1723
1724    /// Keeps the control as a closed single-line field that opens a popup menu on click,
1725    /// even on design systems whose default picker presentation is a wheel.
1726    pub fn closed(mut self, closed: bool) -> Self {
1727        self.closed = closed;
1728        self
1729    }
1730
1731    pub fn on_change(mut self, handler: impl FnMut(usize) + 'static) -> Self {
1732        self.on_change = Some(std::boxed::Box::new(handler));
1733        self
1734    }
1735
1736    fn compose(&mut self, content: impl FnOnce()) {
1737        let modifier = self.modifier;
1738        let (selected_index, enabled, closed) = (self.selected_index, self.enabled, self.closed);
1739        let handler = match self.on_change.take() {
1740            Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
1741                handler((value as i64).max(0) as usize)
1742            }))),
1743            None => value_handler(None),
1744        };
1745        emit(
1746            widget_key("compose_rust::ui::Dropdown"),
1747            WidgetKind::Dropdown,
1748            |attrs| {
1749                push_modifiers(&modifier, Mods::Standard, attrs);
1750                attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
1751                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1752                if closed {
1753                    attrs.push(("collapsible", OwnedAttr::Bool(true)));
1754                }
1755            },
1756            vec![("onchange", handler)],
1757            content,
1758        );
1759    }
1760}
1761
1762container!(DropdownScope);
1763
1764/// A `MenuItem` and its builder.
1765pub struct MenuItemScope {
1766    modifier: Modifier,
1767    text: String,
1768    icon: Option<IconRole>,
1769    selected: bool,
1770    enabled: bool,
1771    color: Option<Paint>,
1772    on_click: Option<Box<dyn FnMut()>>,
1773    done: bool,
1774}
1775
1776/// A single row in a `Menu` or action list: left-aligned label, optional leading icon,
1777/// and optional selected state.
1778pub fn MenuItem(text: impl Into<String>) -> MenuItemScope {
1779    MenuItemScope {
1780        modifier: Modifier::new(),
1781        text: text.into(),
1782        icon: None,
1783        selected: false,
1784        enabled: true,
1785        color: None,
1786        on_click: None,
1787        done: false,
1788    }
1789}
1790
1791impl MenuItemScope {
1792    /// Leading semantic icon for this menu row.
1793    pub fn icon(mut self, icon: IconRole) -> Self {
1794        self.icon = Some(icon);
1795        self
1796    }
1797
1798    /// Marks this row as the currently selected option.
1799    pub fn selected(mut self, selected: bool) -> Self {
1800        self.selected = selected;
1801        self
1802    }
1803
1804    pub fn enabled(mut self, enabled: bool) -> Self {
1805        self.enabled = enabled;
1806        self
1807    }
1808
1809    /// Overrides the row's label and icon colour (for example, a destructive action).
1810    pub fn color(mut self, paint: Paint) -> Self {
1811        self.color = Some(paint);
1812        self
1813    }
1814
1815    /// Conditional colour override.
1816    pub fn color_if(mut self, paint: Option<Paint>) -> Self {
1817        self.color = paint;
1818        self
1819    }
1820
1821    pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
1822        self.on_click = Some(std::boxed::Box::new(handler));
1823        self
1824    }
1825
1826    fn compose(&mut self, content: impl FnOnce()) {
1827        let modifier = self.modifier;
1828        let text = std::mem::take(&mut self.text);
1829        let (icon, selected, enabled, color) = (self.icon, self.selected, self.enabled, self.color);
1830        let listeners = vec![("onclick", unit(self.on_click.take()))];
1831        emit(
1832            widget_key("compose_rust::ui::MenuItem"),
1833            WidgetKind::Button,
1834            |attrs| {
1835                push_modifiers(&modifier, Mods::Standard, attrs);
1836                attrs.push(("text", OwnedAttr::Text(text)));
1837                attrs.push(("icon", super::role(icon)));
1838                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1839                attrs.push(("variant", role_or_zero(Some(ButtonVariant::Text))));
1840                attrs.push(("text_align", role_or_zero(Some(TextAlign::Start))));
1841                attrs.push(("checked", OwnedAttr::Bool(selected)));
1842                attrs.push(("color", super::paint(color)));
1843            },
1844            listeners,
1845            content,
1846        );
1847    }
1848}
1849
1850composed_on_drop!(MenuItemScope);
1851
1852// ----- surfaces that come and go ----------------------------------------------------------
1853
1854/// A `Dialog` or a `Sheet` and its builder: `open` seeds the Renderer's own open state,
1855/// and `on_dismiss` says once that the user asked to close it.
1856pub struct ModalScope {
1857    modifier: Modifier,
1858    widget: WidgetKind,
1859    open: bool,
1860    on_dismiss: Option<Box<dyn FnMut()>>,
1861    done: bool,
1862}
1863
1864/// A modal. Compose's `Dialog`, kept in the tree and opened by `open`, so its enter and
1865/// exit belong to the Renderer.
1866pub fn Dialog(open: bool) -> ModalScope {
1867    ModalScope {
1868        modifier: Modifier::new(),
1869        widget: WidgetKind::Dialog,
1870        open,
1871        on_dismiss: None,
1872        done: false,
1873    }
1874}
1875
1876/// A surface that slides in from the edge the Renderer chooses. Compose's
1877/// `ModalBottomSheet`, without a promise about which edge.
1878pub fn Sheet(open: bool) -> ModalScope {
1879    ModalScope {
1880        modifier: Modifier::new(),
1881        widget: WidgetKind::Sheet,
1882        open,
1883        on_dismiss: None,
1884        done: false,
1885    }
1886}
1887
1888impl ModalScope {
1889    pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
1890        self.on_dismiss = Some(std::boxed::Box::new(handler));
1891        self
1892    }
1893
1894    fn compose(&mut self, content: impl FnOnce()) {
1895        let modifier = self.modifier;
1896        let (widget, open) = (self.widget, self.open);
1897        let key = if widget == WidgetKind::Dialog {
1898            widget_key("compose_rust::ui::Dialog")
1899        } else {
1900            widget_key("compose_rust::ui::Sheet")
1901        };
1902        emit(
1903            key,
1904            widget,
1905            |attrs| {
1906                push_modifiers(&modifier, Mods::Standard, attrs);
1907                attrs.push(("open", OwnedAttr::Bool(open)));
1908            },
1909            vec![("ondismiss", unit(self.on_dismiss.take()))],
1910            content,
1911        );
1912    }
1913}
1914
1915container!(ModalScope);
1916
1917/// A `Menu` and its builder.
1918pub struct MenuScope<'a> {
1919    modifier: Modifier,
1920    expanded: bool,
1921    on_dismiss: Option<Box<dyn FnMut()>>,
1922    anchor: Option<Box<dyn FnOnce() + 'a>>,
1923    done: bool,
1924}
1925
1926/// A popup hanging off its anchor. Compose's `DropdownMenu`, with the anchor said rather
1927/// than positioned: the Renderer places the popup.
1928pub fn Menu<'a>(expanded: bool) -> MenuScope<'a> {
1929    MenuScope {
1930        modifier: Modifier::new(),
1931        expanded,
1932        on_dismiss: None,
1933        anchor: None,
1934        done: false,
1935    }
1936}
1937
1938impl<'a> MenuScope<'a> {
1939    pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
1940        self.on_dismiss = Some(std::boxed::Box::new(handler));
1941        self
1942    }
1943
1944    /// The widget the menu hangs off. It is the first child on the wire.
1945    pub fn anchor(mut self, anchor: impl FnOnce() + 'a) -> Self {
1946        self.anchor = Some(std::boxed::Box::new(anchor));
1947        self
1948    }
1949
1950    fn compose(&mut self, content: impl FnOnce()) {
1951        let modifier = self.modifier;
1952        let expanded = self.expanded;
1953        let anchor = self.anchor.take();
1954        emit(
1955            widget_key("compose_rust::ui::Menu"),
1956            WidgetKind::Menu,
1957            |attrs| {
1958                push_modifiers(&modifier, Mods::Standard, attrs);
1959                attrs.push(("open", OwnedAttr::Bool(expanded)));
1960            },
1961            vec![("ondismiss", unit(self.on_dismiss.take()))],
1962            || {
1963                if let Some(anchor) = anchor {
1964                    let _anchor =
1965                        crate::runtime::group_guard(widget_key("compose_rust::ui::Menu::anchor"));
1966                    anchor();
1967                }
1968                let _entries =
1969                    crate::runtime::group_guard(widget_key("compose_rust::ui::Menu::entries"));
1970                content();
1971            },
1972        );
1973    }
1974}
1975
1976container!(MenuScope<'a>);
1977
1978/// A `Tabs` and its builder.
1979pub struct TabsScope {
1980    modifier: Modifier,
1981    color: Option<Paint>,
1982    selected_index: usize,
1983    done: bool,
1984}
1985
1986/// A row of tabs; each child is one tab, and a tap is that child's own click. Compose's
1987/// `TabRow`.
1988pub fn Tabs(selected_index: usize) -> TabsScope {
1989    TabsScope {
1990        modifier: Modifier::new(),
1991        color: None,
1992        selected_index,
1993        done: false,
1994    }
1995}
1996
1997impl TabsScope {
1998    /// The mark that shows which tab is selected.
1999    pub fn color(mut self, paint: Paint) -> Self {
2000        self.color = Some(paint);
2001        self
2002    }
2003
2004    fn compose(&mut self, content: impl FnOnce()) {
2005        let modifier = self.modifier;
2006        let (color, selected_index) = (self.color, self.selected_index);
2007        emit(
2008            widget_key("compose_rust::ui::Tabs"),
2009            WidgetKind::Tabs,
2010            |attrs| {
2011                push_modifiers(&modifier, Mods::Standard, attrs);
2012                attrs.push(("color", super::paint(color)));
2013                attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
2014            },
2015            Vec::new(),
2016            content,
2017        );
2018    }
2019}
2020
2021container!(TabsScope);
2022
2023/// A `TopAppBar` and its builder.
2024pub struct TopAppBarScope {
2025    modifier: Modifier,
2026    title: String,
2027    done: bool,
2028}
2029
2030/// The bar across the top of a screen; its children are its content. Compose's
2031/// `TopAppBar`, with the title a property so the Renderer can find it.
2032pub fn TopAppBar() -> TopAppBarScope {
2033    TopAppBarScope {
2034        modifier: Modifier::new(),
2035        title: String::new(),
2036        done: false,
2037    }
2038}
2039
2040impl TopAppBarScope {
2041    pub fn title(mut self, title: impl Into<String>) -> Self {
2042        self.title = title.into();
2043        self
2044    }
2045
2046    fn compose(&mut self, content: impl FnOnce()) {
2047        let modifier = self.modifier;
2048        let title = std::mem::take(&mut self.title);
2049        emit(
2050            widget_key("compose_rust::ui::TopAppBar"),
2051            WidgetKind::TopAppBar,
2052            |attrs| {
2053                push_modifiers(&modifier, Mods::Standard, attrs);
2054                attrs.push(("text", OwnedAttr::Text(title)));
2055            },
2056            Vec::new(),
2057            content,
2058        );
2059    }
2060}
2061
2062container!(TopAppBarScope);
2063
2064/// A `Tooltip` and its builder.
2065pub struct TooltipScope {
2066    modifier: Modifier,
2067    text: String,
2068    done: bool,
2069}
2070
2071/// An explanation attached to its child, which assistive technology reads too. Compose's
2072/// `TooltipBox`.
2073pub fn Tooltip(text: impl Into<String>) -> TooltipScope {
2074    TooltipScope {
2075        modifier: Modifier::new(),
2076        text: text.into(),
2077        done: false,
2078    }
2079}
2080
2081impl TooltipScope {
2082    fn compose(&mut self, content: impl FnOnce()) {
2083        let modifier = self.modifier;
2084        let text = std::mem::take(&mut self.text);
2085        emit(
2086            widget_key("compose_rust::ui::Tooltip"),
2087            WidgetKind::Tooltip,
2088            |attrs| {
2089                push_modifiers(&modifier, Mods::Standard, attrs);
2090                attrs.push(("text", OwnedAttr::Text(text)));
2091            },
2092            Vec::new(),
2093            content,
2094        );
2095    }
2096}
2097
2098container!(TooltipScope);
2099
2100// ----- frames and navigation --------------------------------------------------------------
2101
2102/// Composes one slot of a frame: a ScaffoldSlot node holding `content`, in a group of its
2103/// own so that a slot coming and going leaves the others where they were.
2104fn slot(role: SlotRole, key: u64, content: impl FnOnce()) {
2105    emit(
2106        key,
2107        WidgetKind::ScaffoldSlot,
2108        |attrs| attrs.push(("slot", OwnedAttr::Int(i64::from(u16::from(role))))),
2109        Vec::new(),
2110        content,
2111    );
2112}
2113
2114type Slot<'a> = Option<Box<dyn FnOnce() + 'a>>;
2115
2116/// A `Scaffold` and its builder.
2117pub struct ScaffoldScope<'a> {
2118    modifier: Modifier,
2119    top_bar: Slot<'a>,
2120    bottom_bar: Slot<'a>,
2121    floating_action: Slot<'a>,
2122    done: bool,
2123}
2124
2125/// The screen's frame: which slots it has filled, and the page. Compose's `Scaffold`.
2126/// What each slot becomes on this platform at this width is the Renderer's decision.
2127///
2128/// The Modifier carries the two things a frame takes: `background` and `material`.
2129pub fn Scaffold<'a>() -> ScaffoldScope<'a> {
2130    ScaffoldScope {
2131        modifier: Modifier::new(),
2132        top_bar: None,
2133        bottom_bar: None,
2134        floating_action: None,
2135        done: false,
2136    }
2137}
2138
2139impl<'a> ScaffoldScope<'a> {
2140    pub fn top_bar(mut self, bar: impl FnOnce() + 'a) -> Self {
2141        self.top_bar = Some(std::boxed::Box::new(bar));
2142        self
2143    }
2144
2145    pub fn bottom_bar(mut self, bar: impl FnOnce() + 'a) -> Self {
2146        self.bottom_bar = Some(std::boxed::Box::new(bar));
2147        self
2148    }
2149
2150    pub fn floating_action(mut self, action: impl FnOnce() + 'a) -> Self {
2151        self.floating_action = Some(std::boxed::Box::new(action));
2152        self
2153    }
2154
2155    fn compose(&mut self, content: impl FnOnce()) {
2156        let modifier = self.modifier;
2157        let (top_bar, bottom_bar, floating_action) = (
2158            self.top_bar.take(),
2159            self.bottom_bar.take(),
2160            self.floating_action.take(),
2161        );
2162        emit(
2163            widget_key("compose_rust::ui::Scaffold"),
2164            WidgetKind::Scaffold,
2165            |attrs| push_modifiers(&modifier, Mods::Scaffold, attrs),
2166            Vec::new(),
2167            || {
2168                if let Some(bar) = top_bar {
2169                    slot(
2170                        SlotRole::TopBar,
2171                        widget_key("compose_rust::ui::Scaffold::top_bar"),
2172                        bar,
2173                    );
2174                }
2175                if let Some(bar) = bottom_bar {
2176                    slot(
2177                        SlotRole::BottomBar,
2178                        widget_key("compose_rust::ui::Scaffold::bottom_bar"),
2179                        bar,
2180                    );
2181                }
2182                if let Some(action) = floating_action {
2183                    slot(
2184                        SlotRole::FloatingAction,
2185                        widget_key("compose_rust::ui::Scaffold::floating_action"),
2186                        action,
2187                    );
2188                }
2189                slot(
2190                    SlotRole::Content,
2191                    widget_key("compose_rust::ui::Scaffold::content"),
2192                    content,
2193                );
2194            },
2195        );
2196    }
2197}
2198
2199container!(ScaffoldScope<'a>);
2200
2201/// A `Navigation` and its builder.
2202pub struct NavigationScope<'a> {
2203    modifier: Modifier,
2204    selected_index: usize,
2205    head: Slot<'a>,
2206    foot: Slot<'a>,
2207    done: bool,
2208}
2209
2210/// A set of destinations and the screen they lead to. The children that are
2211/// `NavigationItem`s are the destinations; whether they are a bar, a rail or a drawer is
2212/// the Renderer's choice. Compose's `NavigationSuiteScaffold`.
2213pub fn Navigation<'a>(selected_index: usize) -> NavigationScope<'a> {
2214    NavigationScope {
2215        modifier: Modifier::new(),
2216        selected_index,
2217        head: None,
2218        foot: None,
2219        done: false,
2220    }
2221}
2222
2223impl<'a> NavigationScope<'a> {
2224    /// What sits above the destinations in a strip.
2225    pub fn head(mut self, head: impl FnOnce() + 'a) -> Self {
2226        self.head = Some(std::boxed::Box::new(head));
2227        self
2228    }
2229
2230    /// What sits below them.
2231    pub fn foot(mut self, foot: impl FnOnce() + 'a) -> Self {
2232        self.foot = Some(std::boxed::Box::new(foot));
2233        self
2234    }
2235
2236    fn compose(&mut self, content: impl FnOnce()) {
2237        let modifier = self.modifier;
2238        let selected_index = self.selected_index;
2239        let (head, foot) = (self.head.take(), self.foot.take());
2240        emit(
2241            widget_key("compose_rust::ui::Navigation"),
2242            WidgetKind::Navigation,
2243            |attrs| {
2244                push_modifiers(&modifier, Mods::Standard, attrs);
2245                attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
2246            },
2247            Vec::new(),
2248            || {
2249                if let Some(head) = head {
2250                    slot(
2251                        SlotRole::TopBar,
2252                        widget_key("compose_rust::ui::Navigation::head"),
2253                        head,
2254                    );
2255                }
2256                if let Some(foot) = foot {
2257                    slot(
2258                        SlotRole::BottomBar,
2259                        widget_key("compose_rust::ui::Navigation::foot"),
2260                        foot,
2261                    );
2262                }
2263                let _destinations = crate::runtime::group_guard(widget_key(
2264                    "compose_rust::ui::Navigation::content",
2265                ));
2266                content();
2267            },
2268        );
2269    }
2270}
2271
2272container!(NavigationScope<'a>);
2273
2274/// A `NavigationItem` and its builder.
2275pub struct NavigationItemScope {
2276    modifier: Modifier,
2277    text: String,
2278    icon: Option<IconRole>,
2279    color: Option<Paint>,
2280    enabled: bool,
2281    section: Option<String>,
2282    on_click: Option<Box<dyn FnMut()>>,
2283    done: bool,
2284}
2285
2286/// One destination inside a `Navigation`. Compose's `NavigationSuiteItem`.
2287pub fn NavigationItem(text: impl Into<String>) -> NavigationItemScope {
2288    NavigationItemScope {
2289        modifier: Modifier::new(),
2290        text: text.into(),
2291        icon: None,
2292        color: None,
2293        enabled: true,
2294        section: None,
2295        on_click: None,
2296        done: false,
2297    }
2298}
2299
2300impl NavigationItemScope {
2301    pub fn icon(mut self, icon: IconRole) -> Self {
2302        self.icon = Some(icon);
2303        self
2304    }
2305
2306    pub fn color(mut self, paint: Paint) -> Self {
2307        self.color = Some(paint);
2308        self
2309    }
2310
2311    pub fn enabled(mut self, enabled: bool) -> Self {
2312        self.enabled = enabled;
2313        self
2314    }
2315
2316    /// The named group of the strip this destination belongs to.
2317    pub fn section(mut self, section: impl Into<String>) -> Self {
2318        self.section = Some(section.into());
2319        self
2320    }
2321
2322    pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
2323        self.on_click = Some(std::boxed::Box::new(handler));
2324        self
2325    }
2326
2327    fn compose(&mut self, content: impl FnOnce()) {
2328        let modifier = self.modifier;
2329        let text = std::mem::take(&mut self.text);
2330        let section = self.section.take();
2331        let (icon, color, enabled) = (self.icon, self.color, self.enabled);
2332        emit(
2333            widget_key("compose_rust::ui::NavigationItem"),
2334            WidgetKind::NavigationItem,
2335            |attrs| {
2336                push_modifiers(&modifier, Mods::Standard, attrs);
2337                attrs.push(("text", OwnedAttr::Text(text)));
2338                attrs.push(("icon", super::role(icon)));
2339                attrs.push(("color", super::paint(color)));
2340                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
2341                attrs.push(("section", optional_text(section)));
2342            },
2343            vec![("onclick", unit(self.on_click.take()))],
2344            content,
2345        );
2346    }
2347}
2348
2349composed_on_drop!(NavigationItemScope);
2350
2351// ----- small things ---------------------------------------------------------------------
2352
2353/// A `FileDropTarget` and its builder.
2354pub struct FileDropTargetScope {
2355    modifier: Modifier,
2356    alignment: Option<Alignment>,
2357    on_files_entered: Option<Box<dyn FnMut()>>,
2358    on_files_dropped: Option<Box<dyn FnMut(FileDrop)>>,
2359    done: bool,
2360}
2361
2362/// A place files may be dropped. Desktop only in effect.
2363pub fn FileDropTarget() -> FileDropTargetScope {
2364    FileDropTargetScope {
2365        modifier: Modifier::new(),
2366        alignment: None,
2367        on_files_entered: None,
2368        on_files_dropped: None,
2369        done: false,
2370    }
2371}
2372
2373impl FileDropTargetScope {
2374    pub fn alignment(mut self, alignment: Alignment) -> Self {
2375        self.alignment = Some(alignment);
2376        self
2377    }
2378
2379    pub fn on_files_entered(mut self, handler: impl FnMut() + 'static) -> Self {
2380        self.on_files_entered = Some(std::boxed::Box::new(handler));
2381        self
2382    }
2383
2384    pub fn on_files_dropped(mut self, handler: impl FnMut(FileDrop) + 'static) -> Self {
2385        self.on_files_dropped = Some(std::boxed::Box::new(handler));
2386        self
2387    }
2388
2389    fn compose(&mut self, content: impl FnOnce()) {
2390        let modifier = self.modifier;
2391        let alignment = self.alignment;
2392        let dropped: EventCallback = match self.on_files_dropped.take() {
2393            Some(handler) => EventCallback::Files(Rc::new(RefCell::new(handler))),
2394            None => EventCallback::Files(Rc::new(RefCell::new(|_: FileDrop| {}))),
2395        };
2396        emit(
2397            widget_key("compose_rust::ui::FileDropTarget"),
2398            WidgetKind::FileDropTarget,
2399            |attrs| {
2400                push_modifiers(&modifier, Mods::Standard, attrs);
2401                attrs.push(("alignment", super::role(alignment)));
2402            },
2403            vec![
2404                ("onfilesentered", unit(self.on_files_entered.take())),
2405                ("onfilesdropped", dropped),
2406            ],
2407            content,
2408        );
2409    }
2410}
2411
2412container!(FileDropTargetScope);
2413
2414/// A `Chip` and its builder.
2415pub struct ChipScope {
2416    modifier: Modifier,
2417    text: String,
2418    icon: Option<IconRole>,
2419    selected: bool,
2420    enabled: bool,
2421    max_lines: Option<u32>,
2422    overflow: Option<TextOverflow>,
2423    on_click: Option<Box<dyn FnMut()>>,
2424    done: bool,
2425}
2426
2427/// A small token that is chosen or filters. Compose's `FilterChip`.
2428pub fn Chip(text: impl Into<String>) -> ChipScope {
2429    ChipScope {
2430        modifier: Modifier::new(),
2431        text: text.into(),
2432        icon: None,
2433        selected: false,
2434        enabled: true,
2435        max_lines: None,
2436        overflow: None,
2437        on_click: None,
2438        done: false,
2439    }
2440}
2441
2442impl ChipScope {
2443    pub fn icon(mut self, icon: IconRole) -> Self {
2444        self.icon = Some(icon);
2445        self
2446    }
2447
2448    pub fn selected(mut self, selected: bool) -> Self {
2449        self.selected = selected;
2450        self
2451    }
2452
2453    pub fn enabled(mut self, enabled: bool) -> Self {
2454        self.enabled = enabled;
2455        self
2456    }
2457
2458    /// How many lines the label may take. A label is one line unless this says more, so a
2459    /// chip label is one line unless this says more.
2460    pub fn max_lines(mut self, lines: u32) -> Self {
2461        self.max_lines = Some(lines);
2462        self
2463    }
2464
2465    /// What happens to a label longer than its lines.
2466    pub fn overflow(mut self, overflow: TextOverflow) -> Self {
2467        self.overflow = Some(overflow);
2468        self
2469    }
2470
2471    pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
2472        self.on_click = Some(std::boxed::Box::new(handler));
2473        self
2474    }
2475
2476    fn compose(&mut self, content: impl FnOnce()) {
2477        let modifier = self.modifier;
2478        let text = std::mem::take(&mut self.text);
2479        let (max_lines, overflow) = (self.max_lines, self.overflow);
2480        let (icon, selected, enabled) = (self.icon, self.selected, self.enabled);
2481        emit(
2482            widget_key("compose_rust::ui::Chip"),
2483            WidgetKind::Chip,
2484            |attrs| {
2485                push_modifiers(&modifier, Mods::Chip, attrs);
2486                attrs.push(("text", OwnedAttr::Text(text)));
2487                attrs.push(("icon", super::role(icon)));
2488                attrs.push(("checked", OwnedAttr::Bool(selected)));
2489                attrs.push(("enabled", OwnedAttr::Bool(enabled)));
2490                lines_and_overflow(max_lines, overflow, attrs);
2491            },
2492            vec![("onclick", unit(self.on_click.take()))],
2493            content,
2494        );
2495    }
2496}
2497
2498composed_on_drop!(ChipScope);
2499
2500/// A `FloatingAction` and its builder.
2501pub struct FloatingActionScope {
2502    modifier: Modifier,
2503    text: String,
2504    icon: Option<IconRole>,
2505    on_click: Option<Box<dyn FnMut()>>,
2506    done: bool,
2507}
2508
2509/// The one action a screen is about. Compose's `ExtendedFloatingActionButton`; where it
2510/// goes is the design system's answer.
2511pub fn FloatingAction(text: impl Into<String>) -> FloatingActionScope {
2512    FloatingActionScope {
2513        modifier: Modifier::new(),
2514        text: text.into(),
2515        icon: None,
2516        on_click: None,
2517        done: false,
2518    }
2519}
2520
2521impl FloatingActionScope {
2522    pub fn icon(mut self, icon: IconRole) -> Self {
2523        self.icon = Some(icon);
2524        self
2525    }
2526
2527    pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
2528        self.on_click = Some(std::boxed::Box::new(handler));
2529        self
2530    }
2531
2532    fn compose(&mut self, content: impl FnOnce()) {
2533        let modifier = self.modifier;
2534        let text = std::mem::take(&mut self.text);
2535        let icon = self.icon;
2536        emit(
2537            widget_key("compose_rust::ui::FloatingAction"),
2538            WidgetKind::FloatingAction,
2539            |attrs| {
2540                push_modifiers(&modifier, Mods::None, attrs);
2541                attrs.push(("text", OwnedAttr::Text(text)));
2542                attrs.push(("icon", super::role(icon)));
2543            },
2544            vec![("onclick", unit(self.on_click.take()))],
2545            content,
2546        );
2547    }
2548}
2549
2550composed_on_drop!(FloatingActionScope);
2551
2552/// A `Badge` and its builder.
2553pub struct BadgeScope {
2554    modifier: Modifier,
2555    value: Option<u32>,
2556    text: Option<String>,
2557    color: Option<ColorRole>,
2558    done: bool,
2559}
2560
2561/// A count, a word or a dot, on its child or on its own. Compose's `BadgedBox` and `Badge`.
2562pub fn Badge() -> BadgeScope {
2563    BadgeScope {
2564        modifier: Modifier::new(),
2565        value: None,
2566        text: None,
2567        color: None,
2568        done: false,
2569    }
2570}
2571
2572impl BadgeScope {
2573    /// How many.
2574    pub fn value(mut self, value: u32) -> Self {
2575        self.value = Some(value);
2576        self
2577    }
2578
2579    /// One short word, shown where a count would be.
2580    pub fn text(mut self, text: impl Into<String>) -> Self {
2581        self.text = Some(text.into());
2582        self
2583    }
2584
2585    /// The role the mark is filled with; the error role where it is not given.
2586    pub fn color(mut self, role: ColorRole) -> Self {
2587        self.color = Some(role);
2588        self
2589    }
2590
2591    fn compose(&mut self, content: impl FnOnce()) {
2592        let modifier = self.modifier;
2593        let text = self.text.take();
2594        let (value, color) = (self.value, self.color);
2595        emit(
2596            widget_key("compose_rust::ui::Badge"),
2597            WidgetKind::Badge,
2598            |attrs| {
2599                push_modifiers(&modifier, Mods::Standard, attrs);
2600                attrs.push((
2601                    "count",
2602                    value.map_or(OwnedAttr::None, |value| OwnedAttr::Int(i64::from(value))),
2603                ));
2604                attrs.push(("text", optional_text(text)));
2605                attrs.push(("color", super::paint(color.map(Paint::Role))));
2606            },
2607            Vec::new(),
2608            content,
2609        );
2610    }
2611}
2612
2613container!(BadgeScope);
2614
2615/// A `SplitPane` and its builder.
2616pub struct SplitPaneScope {
2617    modifier: Modifier,
2618    value: Option<f32>,
2619    min: Option<f32>,
2620    max: Option<f32>,
2621    collapsible: bool,
2622    selected_index: usize,
2623    label: Option<String>,
2624    on_change: Option<Box<dyn FnMut(f32)>>,
2625    on_dismiss: Option<Box<dyn FnMut()>>,
2626    done: bool,
2627}
2628
2629/// A side pane and a body with a divider the user drags. Exactly two children, side pane
2630/// first. The drag is reported once, when it ends.
2631pub fn SplitPane() -> SplitPaneScope {
2632    SplitPaneScope {
2633        modifier: Modifier::new(),
2634        value: None,
2635        min: None,
2636        max: None,
2637        collapsible: false,
2638        selected_index: 0,
2639        label: None,
2640        on_change: None,
2641        on_dismiss: None,
2642        done: false,
2643    }
2644}
2645
2646impl SplitPaneScope {
2647    /// The side pane's width in dp, or 0 to open it folded.
2648    pub fn value(mut self, dp: f32) -> Self {
2649        self.value = Some(dp);
2650        self
2651    }
2652
2653    pub fn min(mut self, dp: f32) -> Self {
2654        self.min = Some(dp);
2655        self
2656    }
2657
2658    pub fn max(mut self, dp: f32) -> Self {
2659        self.max = Some(dp);
2660        self
2661    }
2662
2663    pub fn collapsible(mut self, collapsible: bool) -> Self {
2664        self.collapsible = collapsible;
2665        self
2666    }
2667
2668    /// Which pane shows when only one fits: 0 the side pane, 1 the body.
2669    pub fn selected_index(mut self, index: usize) -> Self {
2670        self.selected_index = index;
2671        self
2672    }
2673
2674    /// What a screen reader calls the divider.
2675    pub fn label(mut self, label: impl Into<String>) -> Self {
2676        self.label = Some(label.into());
2677        self
2678    }
2679
2680    pub fn on_change(mut self, handler: impl FnMut(f32) + 'static) -> Self {
2681        self.on_change = Some(std::boxed::Box::new(handler));
2682        self
2683    }
2684
2685    pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
2686        self.on_dismiss = Some(std::boxed::Box::new(handler));
2687        self
2688    }
2689
2690    fn compose(&mut self, content: impl FnOnce()) {
2691        let modifier = self.modifier;
2692        let label = self.label.take();
2693        let (value, min, max, collapsible, selected_index) = (
2694            self.value,
2695            self.min,
2696            self.max,
2697            self.collapsible,
2698            self.selected_index,
2699        );
2700        let change = match self.on_change.take() {
2701            Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
2702                handler(value as f32)
2703            }))),
2704            None => value_handler(None),
2705        };
2706        let as_float = |value: Option<f32>| {
2707            value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
2708        };
2709        emit(
2710            widget_key("compose_rust::ui::SplitPane"),
2711            WidgetKind::SplitPane,
2712            |attrs| {
2713                push_modifiers(&modifier, Mods::Standard, attrs);
2714                attrs.push(("value", as_float(value)));
2715                attrs.push(("min", as_float(min)));
2716                attrs.push(("max", as_float(max)));
2717                attrs.push(("collapsible", OwnedAttr::Bool(collapsible)));
2718                attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
2719                attrs.push(("text", optional_text(label)));
2720            },
2721            vec![
2722                ("onchange", change),
2723                ("ondismiss", unit(self.on_dismiss.take())),
2724            ],
2725            content,
2726        );
2727    }
2728}
2729
2730container!(SplitPaneScope);
2731
2732/// The worked extension widget: Material 3's determinate linear progress indicator.
2733pub struct LinearProgressIndicatorScope {
2734    modifier: Modifier,
2735    progress: f32,
2736    done: bool,
2737}
2738
2739/// The extension widget the schema carries as its example.
2740pub fn LinearProgressIndicator(progress: f32) -> LinearProgressIndicatorScope {
2741    LinearProgressIndicatorScope {
2742        modifier: Modifier::new(),
2743        progress,
2744        done: false,
2745    }
2746}
2747
2748impl LinearProgressIndicatorScope {
2749    fn compose(&mut self, content: impl FnOnce()) {
2750        let modifier = self.modifier;
2751        let progress = self.progress;
2752        emit(
2753            widget_key("compose_rust::ui::LinearProgressIndicator"),
2754            WidgetKind::LinearProgressIndicator,
2755            |attrs| {
2756                push_modifiers(&modifier, Mods::None, attrs);
2757                attrs.push(("progress", OwnedAttr::Float(f64::from(progress))));
2758            },
2759            Vec::new(),
2760            content,
2761        );
2762    }
2763}
2764
2765composed_on_drop!(LinearProgressIndicatorScope);
2766
2767// ----- windows --------------------------------------------------------------------
2768
2769/// A `Window` and its builder.
2770pub struct WindowScope {
2771    on_close_request: Option<Box<dyn FnMut()>>,
2772    title: Option<String>,
2773    size: Option<(f32, f32)>,
2774    min_size: Option<(f32, f32)>,
2775    resizable: Option<bool>,
2776    chrome: Option<crate::schema::Chrome>,
2777    title_bar: Option<crate::schema::TitleBar>,
2778    icon: Option<u32>,
2779    position: Option<(f32, f32)>,
2780    restore_key: Option<String>,
2781    zoom_level: Option<i8>,
2782    drag_from_pointer: Option<crate::windows::DragFromPointer>,
2783    theme: Option<crate::schema::Theme>,
2784    on_focus_changed: Option<Box<dyn FnMut(bool)>>,
2785    on_moved: Option<Box<dyn FnMut(crate::windows::WindowFrame)>>,
2786    on_drag: Option<Box<dyn FnMut(crate::windows::WindowDragEvent)>>,
2787    on_unavailable: Option<Box<dyn FnMut()>>,
2788    on_closed: Option<Box<dyn FnMut()>>,
2789    done: bool,
2790}
2791
2792/// A platform window, a child of the root, whose children are the window's content. Compose
2793/// Desktop's `Window`. The application has to be launched with
2794/// [`LaunchBuilder::windows`](crate::LaunchBuilder::windows).
2795///
2796/// `on_close_request` is called when the user asks to close the window. It does not close
2797/// it: the window closes when the application stops composing this `Window`, which is
2798/// where a dialog that asks about unsaved work goes.
2799///
2800/// ```ignore
2801/// Window(move || open.set(false)).title("Notes").size(900.0, 600.0).content(|| {
2802///     Text("Hello");
2803/// });
2804/// ```
2805pub fn Window(on_close_request: impl FnMut() + 'static) -> WindowScope {
2806    WindowScope {
2807        on_close_request: Some(std::boxed::Box::new(on_close_request)),
2808        title: None,
2809        size: None,
2810        min_size: None,
2811        resizable: None,
2812        chrome: None,
2813        title_bar: None,
2814        icon: None,
2815        position: None,
2816        restore_key: None,
2817        zoom_level: None,
2818        drag_from_pointer: None,
2819        theme: None,
2820        on_focus_changed: None,
2821        on_moved: None,
2822        on_drag: None,
2823        on_unavailable: None,
2824        on_closed: None,
2825        done: false,
2826    }
2827}
2828
2829impl Drop for WindowScope {
2830    fn drop(&mut self) {
2831        // A panic is already unwinding the composition, and composing on the way out would
2832        // only risk a second one.
2833        if !self.done && !std::thread::panicking() {
2834            self.done = true;
2835            self.compose(|| {});
2836        }
2837    }
2838}
2839
2840/// A window handler as the callback the runtime delivers events to.
2841fn window_callback(handler: impl FnMut(crate::windows::WindowEvent) + 'static) -> EventCallback {
2842    EventCallback::Window(Rc::new(RefCell::new(handler)))
2843}
2844
2845impl WindowScope {
2846    /// What the window calls itself.
2847    pub fn title(mut self, title: impl Into<String>) -> Self {
2848        self.title = Some(title.into());
2849        self
2850    }
2851
2852    /// The size the window opens at, in dp. A later change resizes it.
2853    pub fn size(mut self, width: f32, height: f32) -> Self {
2854        self.size = Some((width, height));
2855        self
2856    }
2857
2858    /// The smallest size the user can make it, in dp.
2859    pub fn min_size(mut self, width: f32, height: f32) -> Self {
2860        self.min_size = Some((width, height));
2861        self
2862    }
2863
2864    pub fn resizable(mut self, resizable: bool) -> Self {
2865        self.resizable = Some(resizable);
2866        self
2867    }
2868
2869    /// Who draws the caption.
2870    pub fn chrome(mut self, chrome: crate::schema::Chrome) -> Self {
2871        self.chrome = Some(chrome);
2872        self
2873    }
2874
2875    /// Which of the two title bars the window has.
2876    pub fn title_bar(mut self, title_bar: crate::schema::TitleBar) -> Self {
2877        self.title_bar = Some(title_bar);
2878        self
2879    }
2880
2881    /// The window's icon, as the id of a registered asset. Without one it wears the
2882    /// application's.
2883    pub fn icon(mut self, asset_id: u32) -> Self {
2884        self.icon = Some(asset_id);
2885        self
2886    }
2887
2888    /// Where the window sits, in screen dp from the top left of the main screen. Changing
2889    /// it moves the window; the position the user chose is not given back. Without it the
2890    /// platform places the window.
2891    pub fn position(mut self, x: f32, y: f32) -> Self {
2892        self.position = Some((x, y));
2893        self
2894    }
2895
2896    /// The application's stable name for the window, which matches a window the system
2897    /// brings back to the node that describes it.
2898    pub fn restore_key(mut self, key: impl Into<String>) -> Self {
2899        self.restore_key = Some(key.into());
2900        self
2901    }
2902
2903    /// The window's own application zoom level, kept inside its range.
2904    pub fn zoom_level(mut self, level: i8) -> Self {
2905        self.zoom_level = Some(crate::zoom::clamp_level(level));
2906        self
2907    }
2908
2909    /// Opens the window under a pointer that is held down and follows it until it is let
2910    /// go. Read when the window opens and not again.
2911    pub fn drag_from_pointer(mut self, drag: crate::windows::DragFromPointer) -> Self {
2912        self.drag_from_pointer = Some(drag);
2913        self
2914    }
2915
2916    /// A theme for this window over the application's.
2917    pub fn theme(mut self, theme: crate::schema::Theme) -> Self {
2918        self.theme = Some(theme);
2919        self
2920    }
2921
2922    /// The window became the key window or stopped being it.
2923    pub fn on_focus_changed(mut self, handler: impl FnMut(bool) + 'static) -> Self {
2924        self.on_focus_changed = Some(std::boxed::Box::new(handler));
2925        self
2926    }
2927
2928    /// The window's outer frame moved or changed size.
2929    pub fn on_moved(mut self, handler: impl FnMut(crate::windows::WindowFrame) + 'static) -> Self {
2930        self.on_moved = Some(std::boxed::Box::new(handler));
2931        self
2932    }
2933
2934    /// The window is being dragged out of a tab, and how that ended.
2935    pub fn on_drag(
2936        mut self,
2937        handler: impl FnMut(crate::windows::WindowDragEvent) + 'static,
2938    ) -> Self {
2939        self.on_drag = Some(std::boxed::Box::new(handler));
2940        self
2941    }
2942
2943    /// The platform could not open this window. Tell the user, or put the tab back.
2944    pub fn on_unavailable(mut self, handler: impl FnMut() + 'static) -> Self {
2945        self.on_unavailable = Some(std::boxed::Box::new(handler));
2946        self
2947    }
2948
2949    /// The system closed the window without asking. Stop composing it.
2950    pub fn on_closed(mut self, handler: impl FnMut() + 'static) -> Self {
2951        self.on_closed = Some(std::boxed::Box::new(handler));
2952        self
2953    }
2954
2955    /// Composes this window with `content` as its children.
2956    pub fn content(mut self, content: impl FnOnce()) {
2957        self.done = true;
2958        self.compose(content);
2959    }
2960
2961    fn compose(&mut self, content: impl FnOnce()) {
2962        use crate::windows::WindowEvent;
2963        let pair = |value: (f32, f32)| {
2964            let mut bytes = Vec::with_capacity(8);
2965            bytes.extend_from_slice(&value.0.to_bits().to_le_bytes());
2966            bytes.extend_from_slice(&value.1.to_bits().to_le_bytes());
2967            OwnedAttr::Bytes(bytes)
2968        };
2969        // Only what was said is written. A property left out is not a property sent as
2970        // nothing, and one that was said before and is not now is taken away.
2971        let mut properties: Attrs = Vec::new();
2972        if let Some(title) = self.title.take() {
2973            properties.push(("window_title", OwnedAttr::Text(title)));
2974        }
2975        if let Some(size) = self.size {
2976            properties.push(("window_size", pair(size)));
2977        }
2978        if let Some(size) = self.min_size {
2979            properties.push(("window_min_size", pair(size)));
2980        }
2981        if let Some(resizable) = self.resizable {
2982            properties.push(("window_resizable", OwnedAttr::Bool(resizable)));
2983        }
2984        if let Some(chrome) = self.chrome {
2985            properties.push(("window_chrome", OwnedAttr::Int(i64::from(chrome as u16))));
2986        }
2987        if let Some(title_bar) = self.title_bar {
2988            properties.push((
2989                "window_title_bar",
2990                OwnedAttr::Int(i64::from(title_bar as u16)),
2991            ));
2992        }
2993        if let Some(icon) = self.icon {
2994            properties.push(("window_icon", OwnedAttr::Int(i64::from(icon))));
2995        }
2996        if let Some(position) = self.position {
2997            properties.push(("window_position", pair(position)));
2998        }
2999        if let Some(key) = self.restore_key.take() {
3000            properties.push(("window_restore_key", OwnedAttr::Text(key)));
3001        }
3002        if let Some(level) = self.zoom_level {
3003            properties.push(("window_zoom_level", OwnedAttr::Int(i64::from(level))));
3004        }
3005        if let Some(drag) = self.drag_from_pointer {
3006            properties.push(("drag_from_pointer", OwnedAttr::Bytes(drag.to_bytes())));
3007        }
3008
3009        let mut listeners: Vec<(&'static str, EventCallback)> = Vec::new();
3010        let mut close = self.on_close_request.take();
3011        listeners.push((
3012            "onwindowclose",
3013            window_callback(move |event| {
3014                if let (WindowEvent::CloseRequested, Some(handler)) = (event, close.as_mut()) {
3015                    handler();
3016                }
3017            }),
3018        ));
3019        if let Some(mut handler) = self.on_focus_changed.take() {
3020            listeners.push((
3021                "onwindowfocus",
3022                window_callback(move |event| {
3023                    if let WindowEvent::FocusChanged { focused } = event {
3024                        handler(focused);
3025                    }
3026                }),
3027            ));
3028        }
3029        if let Some(mut handler) = self.on_moved.take() {
3030            listeners.push((
3031                "onwindowmoved",
3032                window_callback(move |event| {
3033                    if let WindowEvent::Moved {
3034                        x,
3035                        y,
3036                        width,
3037                        height,
3038                    } = event
3039                    {
3040                        handler(crate::windows::WindowFrame {
3041                            x,
3042                            y,
3043                            width,
3044                            height,
3045                        });
3046                    }
3047                }),
3048            ));
3049        }
3050        if let Some(mut handler) = self.on_drag.take() {
3051            listeners.push((
3052                "onwindowdrag",
3053                window_callback(move |event| {
3054                    if let WindowEvent::Drag {
3055                        phase,
3056                        screen_x,
3057                        screen_y,
3058                        over_window,
3059                    } = event
3060                    {
3061                        handler(crate::windows::WindowDragEvent {
3062                            phase,
3063                            screen_x,
3064                            screen_y,
3065                            over_window,
3066                        });
3067                    }
3068                }),
3069            ));
3070        }
3071        if let Some(mut handler) = self.on_unavailable.take() {
3072            listeners.push((
3073                "onwindowunavailable",
3074                window_callback(move |event| {
3075                    if event == WindowEvent::Unavailable {
3076                        handler();
3077                    }
3078                }),
3079            ));
3080        }
3081        if let Some(mut handler) = self.on_closed.take() {
3082            listeners.push((
3083                "onwindowclosed",
3084                window_callback(move |event| {
3085                    if event == WindowEvent::Closed {
3086                        handler();
3087                    }
3088                }),
3089            ));
3090        }
3091
3092        let theme = self.theme.take();
3093        emit(
3094            widget_key("compose_rust::ui::Window"),
3095            WidgetKind::Window,
3096            |attrs| attrs.append(&mut properties),
3097            listeners,
3098            move || {
3099                // The group has its node by now, so the window is known.
3100                if let (Some(theme), Some(window)) = (theme, crate::windows::current_window()) {
3101                    window.set_theme(theme);
3102                }
3103                content();
3104            },
3105        );
3106    }
3107}