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