1#![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
30fn 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
53struct PointerSubscription {
56 kinds: PointerKinds,
57 handler: Box<dyn FnMut(PointerEvent)>,
58}
59
60fn 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
83struct TearOff {
86 key: Option<String>,
87 drag_over: Option<Box<dyn FnMut(crate::windows::WindowDragOver)>>,
88}
89
90fn 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
117macro_rules! tear_off {
119 ($name:ident) => {
120 impl $name {
121 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 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
151macro_rules! pointer_subscription {
153 ($name:ident) => {
154 impl $name {
155 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
175fn 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
187macro_rules! click_handler {
189 ($name:ident) => {
190 impl $name {
191 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#[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
215fn 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 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 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 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 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 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
279macro_rules! composed_on_drop {
281 ($name:ident $(<$lifetime:lifetime>)?) => {
282 impl$(<$lifetime>)? Drop for $name$(<$lifetime>)? {
283 fn drop(&mut self) {
284 if !self.done && !std::thread::panicking() {
287 self.done = true;
288 self.compose(|| {});
289 }
290 }
291 }
292
293 impl$(<$lifetime>)? $name$(<$lifetime>)? {
294 pub fn modifier(mut self, modifier: Modifier) -> Self {
296 self.modifier = modifier;
297 self
298 }
299 }
300 };
301}
302
303macro_rules! container {
305 ($name:ident $(<$lifetime:lifetime>)?) => {
306 composed_on_drop!($name $(<$lifetime>)?);
307
308 impl$(<$lifetime>)? $name$(<$lifetime>)? {
309 pub fn content(mut self, content: impl FnOnce()) {
311 self.done = true;
312 self.compose(content);
313 }
314 }
315 };
316}
317
318#[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 pub fn arrangement(mut self, arrangement: Arrangement) -> Self {
371 self.linear.arrangement = Some(arrangement);
372 self
373 }
374
375 pub fn spacing(mut self, dp: f32) -> Self {
377 self.linear.spacing = Some(dp);
378 self
379 }
380
381 pub fn space_role(mut self, role: SpaceRole) -> Self {
383 self.linear.space_role = Some(role);
384 self
385 }
386
387 pub fn alignment(mut self, alignment: Alignment) -> Self {
389 self.linear.alignment = Some(alignment);
390 self
391 }
392
393 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 pub fn equal_height(self, enabled: bool) -> Self {
441 self.intrinsic_size(enabled)
442 }
443}
444
445impl ColumnScope {
446 pub fn equal_width(self, enabled: bool) -> Self {
449 self.intrinsic_size(enabled)
450 }
451}
452
453pub 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
464pub 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 pub fn horizontal_spacing(mut self, dp: f32) -> Self {
483 self.horizontal_spacing = Some(dp);
484 self
485 }
486
487 pub fn vertical_spacing(mut self, dp: f32) -> Self {
489 self.vertical_spacing = Some(dp);
490 self
491 }
492
493 pub fn horizontal_alignment(mut self, alignment: HorizontalAlignment) -> Self {
496 self.horizontal_alignment = Some(alignment);
497 self
498 }
499
500 pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
502 self.vertical_alignment = Some(alignment);
503 self
504 }
505
506 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 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
550pub 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
562pub 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 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
612macro_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#[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 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 pub fn offset(&self) -> f32 {
694 self.offset.get()
695 }
696
697 pub fn max_offset(&self) -> f32 {
701 self.max_offset.get()
702 }
703
704 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 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
719pub 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 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
858fn 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
869pub 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
889pub 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 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 pub fn font(mut self, font: FontRefs) -> Self {
969 self.font = font;
970 self
971 }
972
973 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 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 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
1052pub 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
1078pub 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 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 pub fn type_role(mut self, role: TypeRole) -> Self {
1135 self.type_role = Some(role);
1136 self
1137 }
1138
1139 pub fn font_size(mut self, sp: f32) -> Self {
1141 self.font_size = Some(sp);
1142 self
1143 }
1144
1145 pub fn font_weight(mut self, weight: u16) -> Self {
1147 self.font_weight = Some(weight);
1148 self
1149 }
1150
1151 pub fn line_height(mut self, sp: f32) -> Self {
1153 self.line_height = Some(sp);
1154 self
1155 }
1156
1157 pub fn letter_spacing(mut self, sp: f32) -> Self {
1159 self.letter_spacing = Some(sp);
1160 self
1161 }
1162
1163 pub fn color(mut self, paint: Paint) -> Self {
1165 self.color = Some(paint);
1166 self
1167 }
1168
1169 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 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 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 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 pub fn focus_handle(mut self, handle: &super::FocusHandle) -> Self {
1216 self.focus_handle = Some(handle.clone());
1217 self
1218 }
1219
1220 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 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 if bare {
1300 attrs.push(("bare", OwnedAttr::Bool(true)));
1301 }
1302 },
1303 listeners,
1304 content,
1305 );
1306 }
1307}
1308
1309composed_on_drop!(TextFieldScope);
1310
1311fn 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#[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
1360macro_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
1373fn 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
1382macro_rules! accessible_name {
1384 ($name:ident) => {
1385 impl $name {
1386 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
1397pub 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
1418pub 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 pub fn text_align(mut self, align: TextAlign) -> Self {
1451 self.text_align = Some(align);
1452 self
1453 }
1454
1455 pub fn font_size(mut self, sp: f32) -> Self {
1458 self.font_size = Some(sp);
1459 self
1460 }
1461
1462 pub fn font_weight(mut self, weight: u16) -> Self {
1465 self.font_weight = Some(weight);
1466 self
1467 }
1468
1469 pub fn type_role(mut self, role: TypeRole) -> Self {
1473 self.type_role = Some(role);
1474 self
1475 }
1476
1477 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 pub fn kind(mut self, kind: ButtonKind) -> Self {
1496 self.kind = Some(kind);
1497 self
1498 }
1499
1500 pub fn max_lines(mut self, lines: u32) -> Self {
1503 self.max_lines = Some(lines);
1504 self
1505 }
1506
1507 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
1509 self.overflow = Some(overflow);
1510 self
1511 }
1512
1513 pub fn color(mut self, paint: Paint) -> Self {
1515 self.color = Some(paint);
1516 self
1517 }
1518
1519 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 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
1581macro_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 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 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 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 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
1717pub 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
1732pub 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 pub fn range(mut self, min: f32, max: f32) -> Self {
1768 self.min = min;
1769 self.max = max;
1770 self
1771 }
1772
1773 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 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
1835pub struct ProgressIndicatorScope {
1837 modifier: Modifier,
1838 value: f32,
1839 determinate: bool,
1840 circular: bool,
1841 done: bool,
1842}
1843
1844pub 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
1855pub 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 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
1892pub struct DividerScope {
1894 modifier: Modifier,
1895 vertical: bool,
1896 done: bool,
1897}
1898
1899pub fn Divider() -> DividerScope {
1901 DividerScope {
1902 modifier: Modifier::new(),
1903 vertical: false,
1904 done: false,
1905 }
1906}
1907
1908pub 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
1938pub struct AssetScope {
1942 modifier: Modifier,
1943 widget: WidgetKind,
1944 asset_id: u32,
1945 color: Option<Paint>,
1946 done: bool,
1947}
1948
1949pub 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
1960pub 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 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
2004pub struct CanvasScope {
2006 modifier: Modifier,
2007 commands: DrawList,
2008 view_box: Option<(f32, f32)>,
2009 done: bool,
2010}
2011
2012pub 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 pub fn view_box(mut self, width: f32, height: f32) -> Self {
2029 self.view_box = Some((width, height));
2030 self
2031 }
2032
2033 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
2062pub 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
2075pub 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
2138pub 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
2149pub 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
2213pub 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
2224pub 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
2237pub 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 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 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
2302pub 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
2314pub 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 pub fn icon(mut self, icon: IconRole) -> Self {
2332 self.icon = Some(icon);
2333 self
2334 }
2335
2336 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 pub fn color(mut self, paint: Paint) -> Self {
2349 self.color = Some(paint);
2350 self
2351 }
2352
2353 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
2390pub 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
2403pub 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
2416pub 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 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
2468pub 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
2478pub 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 pub fn scrim(mut self, paint: impl Into<Paint>) -> Self {
2499 self.scrim = Some(paint.into());
2500 self
2501 }
2502
2503 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
2541pub 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
2555pub 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 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 pub fn placement(mut self, placement: PopoverPlacement) -> Self {
2592 self.placement = Some(placement);
2593 self
2594 }
2595
2596 pub fn align(mut self, align: PopoverAlign) -> Self {
2598 self.align = Some(align);
2599 self
2600 }
2601
2602 pub fn offset(mut self, dp: f32) -> Self {
2605 self.offset = Some(dp);
2606 self
2607 }
2608
2609 pub fn scrim(mut self, scrim: bool) -> Self {
2612 self.scrim = scrim;
2613 self
2614 }
2615
2616 pub fn scrim_color(mut self, paint: impl Into<Paint>) -> Self {
2618 self.scrim_color = Some(paint.into());
2619 self
2620 }
2621
2622 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
2673pub struct TabsScope {
2675 modifier: Modifier,
2676 color: Option<Paint>,
2677 selected_index: usize,
2678 done: bool,
2679}
2680
2681pub 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 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
2718pub struct TopAppBarScope {
2720 modifier: Modifier,
2721 title: String,
2722 done: bool,
2723}
2724
2725pub 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
2759pub struct TooltipScope {
2761 modifier: Modifier,
2762 text: String,
2763 done: bool,
2764}
2765
2766pub 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
2795fn 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
2811pub 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
2820pub 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
2896pub 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
2906pub 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 pub fn compact(mut self, compact: CompactNavigation) -> Self {
2930 self.compact = compact;
2931 self
2932 }
2933
2934 pub fn head(mut self, head: impl FnOnce() + 'a) -> Self {
2936 self.head = Some(std::boxed::Box::new(head));
2937 self
2938 }
2939
2940 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 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
2993pub 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
3005pub 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 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
3070pub 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
3081pub 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
3133pub 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
3146pub 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 pub fn max_lines(mut self, lines: u32) -> Self {
3180 self.max_lines = Some(lines);
3181 self
3182 }
3183
3184 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
3219pub struct FloatingActionScope {
3221 modifier: Modifier,
3222 text: String,
3223 icon: Option<IconRole>,
3224 on_click: Option<Box<dyn FnMut()>>,
3225 done: bool,
3226}
3227
3228pub 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
3271pub struct BadgeScope {
3273 modifier: Modifier,
3274 value: Option<u32>,
3275 text: Option<String>,
3276 color: Option<ColorRole>,
3277 done: bool,
3278}
3279
3280pub 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 pub fn value(mut self, value: u32) -> Self {
3294 self.value = Some(value);
3295 self
3296 }
3297
3298 pub fn text(mut self, text: impl Into<String>) -> Self {
3300 self.text = Some(text.into());
3301 self
3302 }
3303
3304 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
3334pub 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
3348pub 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 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 pub fn selected_index(mut self, index: usize) -> Self {
3389 self.selected_index = index;
3390 self
3391 }
3392
3393 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
3451pub struct LinearProgressIndicatorScope {
3453 modifier: Modifier,
3454 progress: f32,
3455 done: bool,
3456}
3457
3458pub 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
3486pub 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
3512pub 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 if !self.done && !std::thread::panicking() {
3555 self.done = true;
3556 self.compose(|| {});
3557 }
3558 }
3559}
3560
3561fn window_callback(handler: impl FnMut(crate::windows::WindowEvent) + 'static) -> EventCallback {
3563 EventCallback::Window(Rc::new(RefCell::new(handler)))
3564}
3565
3566impl WindowScope {
3567 pub fn title(mut self, title: impl Into<String>) -> Self {
3569 self.title = Some(title.into());
3570 self
3571 }
3572
3573 pub fn size(mut self, width: f32, height: f32) -> Self {
3575 self.size = Some((width, height));
3576 self
3577 }
3578
3579 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 pub fn report_size(mut self, report: bool) -> Self {
3595 self.report_size = Some(report);
3596 self
3597 }
3598
3599 pub fn chrome(mut self, chrome: crate::schema::Chrome) -> Self {
3601 self.chrome = Some(chrome);
3602 self
3603 }
3604
3605 pub fn title_bar(mut self, title_bar: crate::schema::TitleBar) -> Self {
3607 self.title_bar = Some(title_bar);
3608 self
3609 }
3610
3611 pub fn icon(mut self, asset_id: u32) -> Self {
3614 self.icon = Some(asset_id);
3615 self
3616 }
3617
3618 pub fn position(mut self, x: f32, y: f32) -> Self {
3622 self.position = Some((x, y));
3623 self
3624 }
3625
3626 pub fn restore_key(mut self, key: impl Into<String>) -> Self {
3629 self.restore_key = Some(key.into());
3630 self
3631 }
3632
3633 pub fn zoom_level(mut self, level: i8) -> Self {
3635 self.zoom_level = Some(crate::zoom::clamp_level(level));
3636 self
3637 }
3638
3639 pub fn drag_from_pointer(mut self, drag: crate::windows::DragFromPointer) -> Self {
3642 self.drag_from_pointer = Some(drag);
3643 self
3644 }
3645
3646 pub fn theme(mut self, theme: crate::schema::Theme) -> Self {
3648 self.theme = Some(theme);
3649 self
3650 }
3651
3652 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 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 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 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 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 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 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 if let (Some(theme), Some(window)) = (theme, crate::windows::current_window()) {
3834 window.set_theme(theme);
3835 }
3836 content();
3837 },
3838 );
3839 }
3840}