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::font_features::FontFeature;
19use crate::fonts::FontRefs;
20use crate::runtime::composer::{EventCallback, OwnedAttr};
21use crate::schema::{
22 Alignment, Arrangement, ButtonKind, ButtonVariant, ColorRole, CompactNavigation, ContentScale,
23 HorizontalAlignment, IconRole, OverflowWrap, Paint, PopoverAlign, PopoverPlacement, SlotRole,
24 Snap, SpaceRole, TextAlign, TextOverflow, TypeRole, VerticalAlignment, WidgetKind, WordBreak,
25};
26use crate::spans::TextSpans;
27use crate::{FileDrop, KeyEvent, MotionFinished, PointerEvent, PointerKinds};
28use std::cell::RefCell;
29use std::rc::Rc;
30
31fn unit(handler: Option<Box<dyn FnMut()>>) -> EventCallback {
34 match handler {
35 Some(handler) => EventCallback::Unit(Rc::new(RefCell::new(handler))),
36 None => EventCallback::Unit(Rc::new(RefCell::new(|| {}))),
37 }
38}
39
40fn text_handler(handler: Option<Box<dyn FnMut(String)>>) -> EventCallback {
41 match handler {
42 Some(handler) => EventCallback::Text(Rc::new(RefCell::new(handler))),
43 None => EventCallback::Text(Rc::new(RefCell::new(|_: String| {}))),
44 }
45}
46
47fn value_handler(handler: Option<Box<dyn FnMut(f64)>>) -> EventCallback {
48 match handler {
49 Some(handler) => EventCallback::Value(Rc::new(RefCell::new(handler))),
50 None => EventCallback::Value(Rc::new(RefCell::new(|_: f64| {}))),
51 }
52}
53
54struct PointerSubscription {
57 kinds: PointerKinds,
58 handler: Box<dyn FnMut(PointerEvent)>,
59}
60
61fn pointer_input(
64 subscription: Option<PointerSubscription>,
65 attrs: &mut Attrs,
66 listeners: &mut Vec<(&'static str, EventCallback)>,
67) {
68 let Some(subscription) = subscription else {
69 return;
70 };
71 if subscription.kinds == PointerKinds::NONE {
72 return;
73 }
74 attrs.push((
75 "pointer_kinds",
76 OwnedAttr::Int(i64::from(subscription.kinds.bits())),
77 ));
78 listeners.push((
79 "onpointer",
80 EventCallback::Pointer(Rc::new(RefCell::new(subscription.handler))),
81 ));
82}
83
84struct TearOff {
87 key: Option<String>,
88 drag_over: Option<Box<dyn FnMut(crate::windows::WindowDragOver)>>,
89}
90
91fn tear_off_input(
94 tear_off: Option<TearOff>,
95 attrs: &mut Attrs,
96 listeners: &mut Vec<(&'static str, EventCallback)>,
97) {
98 let Some(tear_off) = tear_off else {
99 return;
100 };
101 if let Some(key) = tear_off.key {
102 attrs.push(("tear_off_key", OwnedAttr::Text(key)));
103 }
104 if let Some(mut handler) = tear_off.drag_over {
105 listeners.push((
106 "onwindowdragover",
107 EventCallback::Window(Rc::new(RefCell::new(
108 move |event: crate::windows::WindowEvent| {
109 if let crate::windows::WindowEvent::DragOver { kind, window, x, y } = event {
110 handler(crate::windows::WindowDragOver { kind, window, x, y });
111 }
112 },
113 ))),
114 ));
115 }
116}
117
118macro_rules! tear_off {
120 ($name:ident) => {
121 impl $name {
122 pub fn tear_off_key(mut self, key: impl Into<String>) -> Self {
125 self.tear_off
126 .get_or_insert_with(|| TearOff {
127 key: None,
128 drag_over: None,
129 })
130 .key = Some(key.into());
131 self
132 }
133
134 pub fn on_window_drag_over(
137 mut self,
138 handler: impl FnMut(crate::windows::WindowDragOver) + 'static,
139 ) -> Self {
140 self.tear_off
141 .get_or_insert_with(|| TearOff {
142 key: None,
143 drag_over: None,
144 })
145 .drag_over = Some(std::boxed::Box::new(handler));
146 self
147 }
148 }
149 };
150}
151
152macro_rules! pointer_subscription {
154 ($name:ident) => {
155 impl $name {
156 pub fn on_pointer(
162 mut self,
163 kinds: PointerKinds,
164 handler: impl FnMut(PointerEvent) + 'static,
165 ) -> Self {
166 self.pointer = Some(PointerSubscription {
167 kinds,
168 handler: std::boxed::Box::new(handler),
169 });
170 self
171 }
172 }
173 };
174}
175
176fn click_input(
180 on_click: Option<Box<dyn FnMut()>>,
181 listeners: &mut Vec<(&'static str, EventCallback)>,
182) {
183 if let Some(handler) = on_click {
184 listeners.push(("onclick", unit(Some(handler))));
185 }
186}
187
188macro_rules! click_handler {
190 ($name:ident) => {
191 impl $name {
192 pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
198 self.on_click = Some(std::boxed::Box::new(handler));
199 self
200 }
201 }
202 };
203}
204
205#[derive(Default)]
208struct FocusAndKeyHandlers {
209 on_focus_gained: Option<Box<dyn FnMut()>>,
210 on_focus_lost: Option<Box<dyn FnMut()>>,
211 on_key_down: Option<Box<dyn FnMut(KeyEvent)>>,
212 on_key_up: Option<Box<dyn FnMut(KeyEvent)>>,
213 on_motion_finished: Option<Box<dyn FnMut(MotionFinished)>>,
214 focus_handle: Option<super::FocusHandle>,
215}
216
217fn motion_input(
220 handler: Option<Box<dyn FnMut(MotionFinished)>>,
221 listeners: &mut Vec<(&'static str, EventCallback)>,
222) {
223 if let Some(handler) = handler {
224 listeners.push((
225 "onmotionfinished",
226 EventCallback::Motion(Rc::new(RefCell::new(handler))),
227 ));
228 }
229}
230
231fn focus_and_key_input(
234 mut handlers: FocusAndKeyHandlers,
235 listeners: &mut Vec<(&'static str, EventCallback)>,
236) {
237 super::focus::bind_focus_handle(handlers.focus_handle.take());
238 if let Some(handler) = handlers.on_focus_gained.take() {
239 listeners.push(("onfocusgained", unit(Some(handler))));
240 }
241 if let Some(handler) = handlers.on_focus_lost.take() {
242 listeners.push(("onfocuslost", unit(Some(handler))));
243 }
244 if let Some(handler) = handlers.on_key_down.take() {
245 listeners.push((
246 "onkeydown",
247 EventCallback::Key(Rc::new(RefCell::new(handler))),
248 ));
249 }
250 if let Some(handler) = handlers.on_key_up.take() {
251 listeners.push((
252 "onkeyup",
253 EventCallback::Key(Rc::new(RefCell::new(handler))),
254 ));
255 }
256 motion_input(handlers.on_motion_finished.take(), listeners);
257}
258
259macro_rules! focus_and_key_handlers {
260 ($name:ident) => {
261 impl $name {
262 pub fn on_focus_gained(mut self, handler: impl FnMut() + 'static) -> Self {
264 self.focus_and_keys.on_focus_gained = Some(std::boxed::Box::new(handler));
265 self
266 }
267
268 pub fn on_focus_lost(mut self, handler: impl FnMut() + 'static) -> Self {
270 self.focus_and_keys.on_focus_lost = Some(std::boxed::Box::new(handler));
271 self
272 }
273
274 pub fn on_key_down(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
277 self.focus_and_keys.on_key_down = Some(std::boxed::Box::new(handler));
278 self
279 }
280
281 pub fn on_key_up(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
283 self.focus_and_keys.on_key_up = Some(std::boxed::Box::new(handler));
284 self
285 }
286
287 pub fn focus_handle(mut self, handle: &super::FocusHandle) -> Self {
289 self.focus_and_keys.focus_handle = Some(handle.clone());
290 self
291 }
292
293 pub fn on_motion_finished(
299 mut self,
300 handler: impl FnMut(MotionFinished) + 'static,
301 ) -> Self {
302 self.focus_and_keys.on_motion_finished = Some(std::boxed::Box::new(handler));
303 self
304 }
305 }
306 };
307}
308
309macro_rules! composed_on_drop {
311 ($name:ident $(<$lifetime:lifetime>)?) => {
312 impl$(<$lifetime>)? Drop for $name$(<$lifetime>)? {
313 fn drop(&mut self) {
314 if !self.done && !std::thread::panicking() {
317 self.done = true;
318 self.compose(|| {});
319 }
320 }
321 }
322
323 impl$(<$lifetime>)? $name$(<$lifetime>)? {
324 pub fn modifier(mut self, modifier: Modifier) -> Self {
326 self.modifier = modifier;
327 self
328 }
329 }
330 };
331}
332
333macro_rules! container {
335 ($name:ident $(<$lifetime:lifetime>)?) => {
336 composed_on_drop!($name $(<$lifetime>)?);
337
338 impl$(<$lifetime>)? $name$(<$lifetime>)? {
339 pub fn content(mut self, content: impl FnOnce()) {
341 self.done = true;
342 self.compose(content);
343 }
344 }
345 };
346}
347
348#[derive(Default)]
352struct Linear {
353 arrangement: Option<Arrangement>,
354 spacing: Option<f32>,
355 space_role: Option<SpaceRole>,
356 alignment: Option<Alignment>,
357 intrinsic_size: Option<bool>,
358 radio_group: bool,
359}
360
361impl Linear {
362 fn push(&self, attrs: &mut Attrs) {
363 attrs.push(("arrangement", role_or_zero(self.arrangement)));
364 attrs.push(("spacing", dp_or_zero(self.spacing)));
365 attrs.push(("space_role", role_or_zero(self.space_role)));
366 attrs.push(("alignment", role_or_zero(self.alignment)));
367 if let Some(intrinsic) = self.intrinsic_size {
368 attrs.push(("collapsible", OwnedAttr::Bool(intrinsic)));
369 }
370 if self.radio_group {
372 attrs.push(("radio_group", OwnedAttr::Bool(true)));
373 }
374 }
375}
376
377macro_rules! linear {
378 ($name:ident, $widget:ident, $key:literal) => {
379 #[doc = concat!("A `", stringify!($widget), "` and its builder.")]
380 pub struct $name {
381 modifier: Modifier,
382 linear: Linear,
383 pointer: Option<PointerSubscription>,
384 on_click: Option<Box<dyn FnMut()>>,
385 focus_and_keys: FocusAndKeyHandlers,
386 tear_off: Option<TearOff>,
387 done: bool,
388 }
389
390 #[doc = concat!("Lays its children out one after another. Compose's `", stringify!($widget), "`.")]
391 pub fn $widget() -> $name {
392 $name {
393 modifier: Modifier::new(),
394 linear: Linear::default(),
395 pointer: None,
396 on_click: None,
397 focus_and_keys: FocusAndKeyHandlers::default(),
398 tear_off: None,
399 done: false,
400 }
401 }
402
403 impl $name {
404 pub fn arrangement(mut self, arrangement: Arrangement) -> Self {
406 self.linear.arrangement = Some(arrangement);
407 self
408 }
409
410 pub fn spacing(mut self, dp: f32) -> Self {
412 self.linear.spacing = Some(dp);
413 self
414 }
415
416 pub fn space_role(mut self, role: SpaceRole) -> Self {
418 self.linear.space_role = Some(role);
419 self
420 }
421
422 pub fn alignment(mut self, alignment: Alignment) -> Self {
424 self.linear.alignment = Some(alignment);
425 self
426 }
427
428 pub fn intrinsic_size(mut self, enabled: bool) -> Self {
432 self.linear.intrinsic_size = Some(enabled);
433 self
434 }
435
436 pub fn radio_group(mut self, enabled: bool) -> Self {
445 self.linear.radio_group = enabled;
446 self
447 }
448
449 fn compose(&mut self, content: impl FnOnce()) {
450 let modifier = self.modifier;
451 let linear = std::mem::take(&mut self.linear);
452 let pointer = self.pointer.take();
453 let tear_off = self.tear_off.take();
454 let mut listeners = Vec::new();
455 let mut subscription = Attrs::new();
456 pointer_input(pointer, &mut subscription, &mut listeners);
457 click_input(self.on_click.take(), &mut listeners);
458 focus_and_key_input(std::mem::take(&mut self.focus_and_keys), &mut listeners);
459 tear_off_input(tear_off, &mut subscription, &mut listeners);
460 emit(
461 widget_key($key),
462 WidgetKind::$widget,
463 |attrs| {
464 push_modifiers(&modifier, Mods::Container, attrs);
465 linear.push(attrs);
466 attrs.append(&mut subscription);
467 },
468 listeners,
469 content,
470 );
471 }
472 }
473
474 container!($name);
475 pointer_subscription!($name);
476 click_handler!($name);
477 focus_and_key_handlers!($name);
478 tear_off!($name);
479 };
480}
481
482linear!(ColumnScope, Column, "compose_rust::ui::Column");
483linear!(RowScope, Row, "compose_rust::ui::Row");
484
485impl RowScope {
486 pub fn equal_height(self, enabled: bool) -> Self {
489 self.intrinsic_size(enabled)
490 }
491}
492
493impl ColumnScope {
494 pub fn equal_width(self, enabled: bool) -> Self {
497 self.intrinsic_size(enabled)
498 }
499}
500
501pub struct FlowRowScope {
503 modifier: Modifier,
504 horizontal_spacing: Option<f32>,
505 vertical_spacing: Option<f32>,
506 horizontal_alignment: Option<HorizontalAlignment>,
507 vertical_alignment: Option<VerticalAlignment>,
508 max_items_in_each_row: Option<u32>,
509 done: bool,
510}
511
512pub fn FlowRow() -> FlowRowScope {
517 FlowRowScope {
518 modifier: Modifier::new(),
519 horizontal_spacing: None,
520 vertical_spacing: None,
521 horizontal_alignment: None,
522 vertical_alignment: None,
523 max_items_in_each_row: None,
524 done: false,
525 }
526}
527
528impl FlowRowScope {
529 pub fn horizontal_spacing(mut self, dp: f32) -> Self {
531 self.horizontal_spacing = Some(dp);
532 self
533 }
534
535 pub fn vertical_spacing(mut self, dp: f32) -> Self {
537 self.vertical_spacing = Some(dp);
538 self
539 }
540
541 pub fn horizontal_alignment(mut self, alignment: HorizontalAlignment) -> Self {
544 self.horizontal_alignment = Some(alignment);
545 self
546 }
547
548 pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
550 self.vertical_alignment = Some(alignment);
551 self
552 }
553
554 pub fn max_items_in_each_row(mut self, count: u32) -> Self {
556 self.max_items_in_each_row = Some(count);
557 self
558 }
559
560 fn compose(&mut self, content: impl FnOnce()) {
561 let modifier = self.modifier;
562 let (horizontal_spacing, vertical_spacing) =
563 (self.horizontal_spacing, self.vertical_spacing);
564 let (horizontal_alignment, vertical_alignment) =
565 (self.horizontal_alignment, self.vertical_alignment);
566 let max_items = self.max_items_in_each_row;
567 emit(
568 widget_key("compose_rust::ui::FlowRow"),
569 WidgetKind::FlowRow,
570 |attrs| {
571 push_modifiers(&modifier, Mods::Container, attrs);
572 if let Some(dp) = horizontal_spacing {
575 attrs.push(("horizontal_spacing", dp_or_zero(Some(dp))));
576 }
577 if let Some(dp) = vertical_spacing {
578 attrs.push(("vertical_spacing", dp_or_zero(Some(dp))));
579 }
580 if let Some(alignment) = horizontal_alignment {
581 attrs.push(("horizontal_alignment", role_or_zero(Some(alignment))));
582 }
583 if let Some(alignment) = vertical_alignment {
584 attrs.push(("vertical_alignment", role_or_zero(Some(alignment))));
585 }
586 if let Some(count) = max_items {
587 attrs.push(("max_items_in_each_row", OwnedAttr::Int(i64::from(count))));
588 }
589 },
590 Vec::new(),
591 content,
592 );
593 }
594}
595
596container!(FlowRowScope);
597
598pub struct ComposeBox {
601 modifier: Modifier,
602 alignment: Option<Alignment>,
603 pointer: Option<PointerSubscription>,
604 on_click: Option<Box<dyn FnMut()>>,
605 focus_and_keys: FocusAndKeyHandlers,
606 tear_off: Option<TearOff>,
607 done: bool,
608}
609
610pub fn Box() -> ComposeBox {
612 ComposeBox {
613 modifier: Modifier::new(),
614 alignment: None,
615 pointer: None,
616 on_click: None,
617 focus_and_keys: FocusAndKeyHandlers::default(),
618 tear_off: None,
619 done: false,
620 }
621}
622
623impl ComposeBox {
624 pub fn alignment(mut self, alignment: Alignment) -> Self {
626 self.alignment = Some(alignment);
627 self
628 }
629
630 fn compose(&mut self, content: impl FnOnce()) {
631 let (modifier, alignment) = (self.modifier, self.alignment);
632 let pointer = self.pointer.take();
633 let tear_off = self.tear_off.take();
634 let mut listeners = Vec::new();
635 let mut subscription = Attrs::new();
636 pointer_input(pointer, &mut subscription, &mut listeners);
637 click_input(self.on_click.take(), &mut listeners);
638 focus_and_key_input(std::mem::take(&mut self.focus_and_keys), &mut listeners);
639 tear_off_input(tear_off, &mut subscription, &mut listeners);
640 emit(
641 widget_key("compose_rust::ui::Box"),
642 WidgetKind::Box,
643 |attrs| {
644 push_modifiers(&modifier, Mods::Container, attrs);
645 attrs.push(("alignment", role_or_zero(alignment)));
646 attrs.append(&mut subscription);
647 },
648 listeners,
649 content,
650 );
651 }
652}
653
654container!(ComposeBox);
655pointer_subscription!(ComposeBox);
656click_handler!(ComposeBox);
657focus_and_key_handlers!(ComposeBox);
658tear_off!(ComposeBox);
659
660macro_rules! plain_container {
662 ($name:ident, $widget:ident, $mods:expr, $key:literal, $doc:literal) => {
663 #[doc = concat!("A `", stringify!($widget), "` and its builder.")]
664 pub struct $name {
665 modifier: Modifier,
666 pointer: Option<PointerSubscription>,
667 on_click: Option<Box<dyn FnMut()>>,
668 focus_and_keys: FocusAndKeyHandlers,
669 done: bool,
670 }
671
672 #[doc = $doc]
673 pub fn $widget() -> $name {
674 $name {
675 modifier: Modifier::new(),
676 pointer: None,
677 on_click: None,
678 focus_and_keys: FocusAndKeyHandlers::default(),
679 done: false,
680 }
681 }
682
683 impl $name {
684 fn compose(&mut self, content: impl FnOnce()) {
685 let modifier = self.modifier;
686 let pointer = self.pointer.take();
687 let mut listeners = Vec::new();
688 let mut subscription = Attrs::new();
689 pointer_input(pointer, &mut subscription, &mut listeners);
690 click_input(self.on_click.take(), &mut listeners);
691 focus_and_key_input(std::mem::take(&mut self.focus_and_keys), &mut listeners);
692 emit(
693 widget_key($key),
694 WidgetKind::$widget,
695 |attrs| {
696 push_modifiers(&modifier, $mods, attrs);
697 attrs.append(&mut subscription);
698 },
699 listeners,
700 content,
701 );
702 }
703 }
704
705 container!($name);
706 pointer_subscription!($name);
707 click_handler!($name);
708 focus_and_key_handlers!($name);
709 };
710}
711
712#[derive(Clone, Debug, PartialEq, Eq)]
717pub struct ScrollState {
718 offset: crate::runtime::MutableState<f32>,
719 max_offset: crate::runtime::MutableState<f32>,
720 command: crate::runtime::MutableState<(i64, f32, bool)>,
721}
722
723impl Default for ScrollState {
724 fn default() -> Self {
725 Self::new()
726 }
727}
728
729impl ScrollState {
730 pub fn new() -> Self {
732 Self {
733 offset: crate::runtime::mutable_state_of(0.0),
734 max_offset: crate::runtime::mutable_state_of(0.0),
735 command: crate::runtime::mutable_state_of((0, 0.0, false)),
736 }
737 }
738
739 pub fn offset(&self) -> f32 {
742 self.offset.get()
743 }
744
745 pub fn max_offset(&self) -> f32 {
749 self.max_offset.get()
750 }
751
752 pub fn scroll_to(&self, dp: f32) {
754 let (revision, _, _) = self.command.get_untracked();
755 self.command
756 .set((revision.wrapping_add(1).max(1), dp.max(0.0), false));
757 }
758
759 pub fn animate_scroll_to(&self, dp: f32) {
761 let (revision, _, _) = self.command.get_untracked();
762 self.command
763 .set((revision.wrapping_add(1).max(1), dp.max(0.0), true));
764 }
765}
766
767pub fn remember_scroll_state() -> ScrollState {
769 crate::runtime::remember(ScrollState::new)
770}
771
772fn scroll_state_input(
773 state: Option<ScrollState>,
774 attrs: &mut Attrs,
775 listeners: &mut Vec<(&'static str, EventCallback)>,
776) {
777 let Some(state) = state else {
778 return;
779 };
780 let (revision, target, animate) = state.command.get();
781 if revision > 0 {
782 attrs.push(("value", OwnedAttr::Float(f64::from(target))));
783 attrs.push(("determinate", OwnedAttr::Bool(animate)));
784 attrs.push(("count", OwnedAttr::Int(revision)));
785 }
786 let offset = state.offset;
787 let max_offset = state.max_offset;
788 listeners.push((
789 "onrangerequest",
790 EventCallback::Range(Rc::new(RefCell::new(move |range: crate::RangeRequest| {
791 let next_offset = f32::from_bits(range.start() as u32);
792 let next_max = f32::from_bits(range.count() as u32);
793 if next_offset.is_finite() {
794 offset.set(next_offset);
795 }
796 if next_max.is_finite() {
797 max_offset.set(next_max);
798 }
799 }))),
800 ));
801}
802
803macro_rules! scroll_container {
804 ($name:ident, $widget:ident, $key:literal, $doc:literal) => {
805 #[doc = concat!("A `", stringify!($widget), "` and its builder.")]
806 pub struct $name {
807 modifier: Modifier,
808 scroll_state: Option<ScrollState>,
809 snap: Snap,
810 pointer: Option<PointerSubscription>,
811 on_click: Option<Box<dyn FnMut()>>,
812 focus_and_keys: FocusAndKeyHandlers,
813 done: bool,
814 }
815
816 #[doc = $doc]
817 pub fn $widget() -> $name {
818 $name {
819 modifier: Modifier::new(),
820 scroll_state: None,
821 snap: Snap::None,
822 pointer: None,
823 on_click: None,
824 focus_and_keys: FocusAndKeyHandlers::default(),
825 done: false,
826 }
827 }
828
829 impl $name {
830 pub fn scroll_state(mut self, state: &ScrollState) -> Self {
833 self.scroll_state = Some(state.clone());
834 self
835 }
836
837 pub fn snap(mut self, snap: Snap) -> Self {
844 self.snap = snap;
845 self
846 }
847
848 fn compose(&mut self, content: impl FnOnce()) {
849 let modifier = self.modifier;
850 let snap = self.snap;
851 let scroll_state = self.scroll_state.take();
852 let pointer = self.pointer.take();
853 let mut listeners = Vec::new();
854 let mut subscription = Attrs::new();
855 scroll_state_input(scroll_state, &mut subscription, &mut listeners);
856 pointer_input(pointer, &mut subscription, &mut listeners);
857 click_input(self.on_click.take(), &mut listeners);
858 focus_and_key_input(std::mem::take(&mut self.focus_and_keys), &mut listeners);
859 emit(
860 widget_key($key),
861 WidgetKind::$widget,
862 |attrs| {
863 push_modifiers(&modifier, Mods::Standard, attrs);
864 if snap != Snap::None {
867 attrs.push(("snap", OwnedAttr::Int(i64::from(u16::from(snap)))));
868 }
869 attrs.append(&mut subscription);
870 },
871 listeners,
872 content,
873 );
874 }
875 }
876
877 container!($name);
878 pointer_subscription!($name);
879 click_handler!($name);
880 focus_and_key_handlers!($name);
881 };
882}
883
884scroll_container!(
885 ScrollColumnScope,
886 ScrollColumn,
887 "compose_rust::ui::ScrollColumn",
888 "All of its content, with a vertical scroll the Renderer owns."
889);
890scroll_container!(
891 ScrollRowScope,
892 ScrollRow,
893 "compose_rust::ui::ScrollRow",
894 "All of its content, with a horizontal scroll the Renderer owns."
895);
896plain_container!(
897 CardScope,
898 Card,
899 Mods::Container,
900 "compose_rust::ui::Card",
901 "A grouped container. What it looks like is the design system's."
902);
903plain_container!(
904 SurfaceScope,
905 Surface,
906 Mods::Container,
907 "compose_rust::ui::Surface",
908 "A plain background and elevation, without a card's meaning."
909);
910plain_container!(
911 SelectionContainerScope,
912 SelectionContainer,
913 Mods::Standard,
914 "compose_rust::ui::SelectionContainer",
915 "Every `Text` inside can be selected and copied as one selection."
916);
917plain_container!(
918 SpacerScope,
919 Spacer,
920 Mods::Standard,
921 "compose_rust::ui::Spacer",
922 "Empty space, sized by its Modifier."
923);
924
925fn font_bytes(font: &FontRefs) -> OwnedAttr {
929 if font.is_empty() {
930 OwnedAttr::None
931 } else {
932 OwnedAttr::Bytes(font.as_bytes().to_vec())
933 }
934}
935
936pub struct TextScope {
938 modifier: Modifier,
939 text: String,
940 spans: TextSpans,
941 type_role: Option<TypeRole>,
942 font_size: Option<f32>,
943 font_weight: Option<u16>,
944 line_height: Option<f32>,
945 letter_spacing: Option<f32>,
946 color: Option<Paint>,
947 text_align: Option<TextAlign>,
948 max_lines: Option<u32>,
949 overflow: Option<TextOverflow>,
950 font: FontRefs,
951 font_features: OwnedAttr,
952 word_break: Option<WordBreak>,
953 overflow_wrap: Option<OverflowWrap>,
954 pointer: Option<PointerSubscription>,
955 done: bool,
956}
957
958pub fn Text(text: impl Into<String>) -> TextScope {
963 TextScope {
964 modifier: Modifier::new(),
965 text: text.into(),
966 spans: TextSpans::default(),
967 type_role: None,
968 font_size: None,
969 font_weight: None,
970 line_height: None,
971 letter_spacing: None,
972 color: None,
973 text_align: None,
974 max_lines: None,
975 overflow: None,
976 font: FontRefs::default(),
977 font_features: OwnedAttr::None,
978 word_break: None,
979 overflow_wrap: None,
980 pointer: None,
981 done: false,
982 }
983}
984
985impl TextScope {
986 pub fn spans(mut self, spans: TextSpans) -> Self {
988 self.spans = spans;
989 self
990 }
991
992 pub fn type_role(mut self, role: TypeRole) -> Self {
993 self.type_role = Some(role);
994 self
995 }
996
997 pub fn font_size(mut self, sp: f32) -> Self {
998 self.font_size = Some(sp);
999 self
1000 }
1001
1002 pub fn font_weight(mut self, weight: u16) -> Self {
1003 self.font_weight = Some(weight);
1004 self
1005 }
1006
1007 pub fn line_height(mut self, sp: f32) -> Self {
1008 self.line_height = Some(sp);
1009 self
1010 }
1011
1012 pub fn letter_spacing(mut self, sp: f32) -> Self {
1015 self.letter_spacing = Some(sp);
1016 self
1017 }
1018
1019 pub fn color(mut self, paint: Paint) -> Self {
1020 self.color = Some(paint);
1021 self
1022 }
1023
1024 pub fn text_align(mut self, align: TextAlign) -> Self {
1025 self.text_align = Some(align);
1026 self
1027 }
1028
1029 pub fn max_lines(mut self, lines: u32) -> Self {
1030 self.max_lines = Some(lines);
1031 self
1032 }
1033
1034 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
1035 self.overflow = Some(overflow);
1036 self
1037 }
1038
1039 pub fn font(mut self, font: FontRefs) -> Self {
1042 self.font = font;
1043 self
1044 }
1045
1046 pub fn font_feature_settings(mut self, features: &[FontFeature]) -> Self {
1055 self.font_features = crate::font_features::font_feature_attr(features);
1056 self
1057 }
1058
1059 pub fn word_break(mut self, word_break: WordBreak) -> Self {
1061 self.word_break = Some(word_break);
1062 self
1063 }
1064
1065 pub fn overflow_wrap(mut self, overflow_wrap: OverflowWrap) -> Self {
1069 self.overflow_wrap = Some(overflow_wrap);
1070 self
1071 }
1072
1073 fn compose(&mut self, content: impl FnOnce()) {
1074 let modifier = self.modifier;
1075 let text = std::mem::take(&mut self.text);
1076 let spans = std::mem::take(&mut self.spans);
1077 let font = std::mem::take(&mut self.font);
1078 let font_features = std::mem::replace(&mut self.font_features, OwnedAttr::None);
1079 let (word_break, overflow_wrap) = (self.word_break, self.overflow_wrap);
1080 let (type_role, font_size, font_weight, line_height, letter_spacing) = (
1081 self.type_role,
1082 self.font_size,
1083 self.font_weight,
1084 self.line_height,
1085 self.letter_spacing,
1086 );
1087 let (color, text_align, max_lines, overflow) =
1088 (self.color, self.text_align, self.max_lines, self.overflow);
1089 let pointer = self.pointer.take();
1090 let mut listeners = Vec::new();
1091 let mut subscription = Attrs::new();
1092 pointer_input(pointer, &mut subscription, &mut listeners);
1093 emit(
1094 widget_key("compose_rust::ui::Text"),
1095 WidgetKind::Text,
1096 |attrs| {
1097 push_modifiers(&modifier, Mods::Standard, attrs);
1098 let span_fonts = spans.font_table().to_vec();
1100 attrs.push((
1101 "spans",
1102 if spans.is_empty() {
1103 OwnedAttr::None
1104 } else {
1105 OwnedAttr::Spans(spans)
1106 },
1107 ));
1108 attrs.push((
1111 "span_fonts",
1112 if span_fonts.is_empty() {
1113 OwnedAttr::None
1114 } else {
1115 OwnedAttr::Bytes(span_fonts)
1116 },
1117 ));
1118 attrs.push(("font", font_bytes(&font)));
1119 if font_features != OwnedAttr::None {
1123 attrs.push(("font_feature_settings", font_features));
1124 }
1125 attrs.push(("word_break", super::role(word_break)));
1126 attrs.push(("overflow_wrap", super::role(overflow_wrap)));
1127 attrs.push(("text", OwnedAttr::Text(text)));
1128 attrs.push(("type_role", super::role(type_role)));
1133 attrs.push(("font_size", dp_or_zero(font_size)));
1134 attrs.push((
1135 "font_weight",
1136 OwnedAttr::Int(i64::from(font_weight.unwrap_or(0))),
1137 ));
1138 attrs.push(("line_height", dp_or_zero(line_height)));
1139 attrs.push((
1141 "letter_spacing",
1142 OwnedAttr::Float(letter_spacing.map_or(f64::NAN, f64::from)),
1143 ));
1144 attrs.push(("color", paint_or_zero(color)));
1145 attrs.push(("text_align", role_or_zero(text_align)));
1146 attrs.push((
1147 "max_lines",
1148 OwnedAttr::Int(i64::from(max_lines.unwrap_or(0))),
1149 ));
1150 attrs.push(("overflow", role_or_zero(overflow)));
1151 attrs.append(&mut subscription);
1152 },
1153 listeners,
1154 content,
1155 );
1156 }
1157}
1158
1159composed_on_drop!(TextScope);
1160pointer_subscription!(TextScope);
1161
1162pub struct TextFieldScope {
1164 modifier: Modifier,
1165 text: String,
1166 placeholder: String,
1167 enabled: bool,
1168 multiline: bool,
1169 type_role: Option<TypeRole>,
1170 font_size: Option<f32>,
1171 font_weight: Option<u16>,
1172 line_height: Option<f32>,
1173 letter_spacing: Option<f32>,
1174 color: Option<Paint>,
1175 font: FontRefs,
1176 font_features: OwnedAttr,
1177 word_break: Option<WordBreak>,
1178 overflow_wrap: Option<OverflowWrap>,
1179 on_value_change: Option<Box<dyn FnMut(String)>>,
1180 on_submit: Option<Box<dyn FnMut(String)>>,
1181 on_focus_gained: Option<Box<dyn FnMut()>>,
1182 on_focus_lost: Option<Box<dyn FnMut()>>,
1183 on_key_down: Option<Box<dyn FnMut(KeyEvent)>>,
1184 on_key_up: Option<Box<dyn FnMut(KeyEvent)>>,
1185 on_composition_change: Option<Box<dyn FnMut(bool)>>,
1186 on_motion_finished: Option<Box<dyn FnMut(MotionFinished)>>,
1187 focus_handle: Option<super::FocusHandle>,
1188 bare: bool,
1189 done: bool,
1190}
1191
1192pub fn TextField() -> TextFieldScope {
1196 TextFieldScope {
1197 modifier: Modifier::new(),
1198 text: String::new(),
1199 placeholder: String::new(),
1200 enabled: true,
1201 multiline: false,
1202 type_role: None,
1203 font_size: None,
1204 font_weight: None,
1205 line_height: None,
1206 letter_spacing: None,
1207 color: None,
1208 font: FontRefs::default(),
1209 font_features: OwnedAttr::None,
1210 word_break: None,
1211 overflow_wrap: None,
1212 on_value_change: None,
1213 on_submit: None,
1214 on_focus_gained: None,
1215 on_focus_lost: None,
1216 on_key_down: None,
1217 on_key_up: None,
1218 on_composition_change: None,
1219 on_motion_finished: None,
1220 focus_handle: None,
1221 bare: false,
1222 done: false,
1223 }
1224}
1225
1226impl TextFieldScope {
1227 pub fn text(mut self, text: impl Into<String>) -> Self {
1232 self.text = text.into();
1233 self
1234 }
1235
1236 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
1237 self.placeholder = placeholder.into();
1238 self
1239 }
1240
1241 pub fn enabled(mut self, enabled: bool) -> Self {
1242 self.enabled = enabled;
1243 self
1244 }
1245
1246 pub fn multiline(mut self, multiline: bool) -> Self {
1247 self.multiline = multiline;
1248 self
1249 }
1250
1251 pub fn type_role(mut self, role: TypeRole) -> Self {
1253 self.type_role = Some(role);
1254 self
1255 }
1256
1257 pub fn font_size(mut self, sp: f32) -> Self {
1259 self.font_size = Some(sp);
1260 self
1261 }
1262
1263 pub fn font_weight(mut self, weight: u16) -> Self {
1265 self.font_weight = Some(weight);
1266 self
1267 }
1268
1269 pub fn line_height(mut self, sp: f32) -> Self {
1271 self.line_height = Some(sp);
1272 self
1273 }
1274
1275 pub fn letter_spacing(mut self, sp: f32) -> Self {
1277 self.letter_spacing = Some(sp);
1278 self
1279 }
1280
1281 pub fn color(mut self, paint: Paint) -> Self {
1283 self.color = Some(paint);
1284 self
1285 }
1286
1287 pub fn font(mut self, font: FontRefs) -> Self {
1289 self.font = font;
1290 self
1291 }
1292
1293 pub fn font_feature_settings(mut self, features: &[FontFeature]) -> Self {
1296 self.font_features = crate::font_features::font_feature_attr(features);
1297 self
1298 }
1299
1300 pub fn word_break(mut self, word_break: WordBreak) -> Self {
1303 self.word_break = Some(word_break);
1304 self
1305 }
1306
1307 pub fn overflow_wrap(mut self, overflow_wrap: OverflowWrap) -> Self {
1310 self.overflow_wrap = Some(overflow_wrap);
1311 self
1312 }
1313
1314 pub fn on_value_change(mut self, handler: impl FnMut(String) + 'static) -> Self {
1315 self.on_value_change = Some(std::boxed::Box::new(handler));
1316 self
1317 }
1318
1319 pub fn on_submit(mut self, handler: impl FnMut(String) + 'static) -> Self {
1320 self.on_submit = Some(std::boxed::Box::new(handler));
1321 self
1322 }
1323
1324 pub fn on_focus_gained(mut self, handler: impl FnMut() + 'static) -> Self {
1325 self.on_focus_gained = Some(std::boxed::Box::new(handler));
1326 self
1327 }
1328
1329 pub fn on_focus_lost(mut self, handler: impl FnMut() + 'static) -> Self {
1330 self.on_focus_lost = Some(std::boxed::Box::new(handler));
1331 self
1332 }
1333
1334 pub fn on_key_down(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
1337 self.on_key_down = Some(std::boxed::Box::new(handler));
1338 self
1339 }
1340
1341 pub fn on_key_up(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
1343 self.on_key_up = Some(std::boxed::Box::new(handler));
1344 self
1345 }
1346
1347 pub fn on_composition_change(mut self, handler: impl FnMut(bool) + 'static) -> Self {
1349 self.on_composition_change = Some(std::boxed::Box::new(handler));
1350 self
1351 }
1352
1353 pub fn focus_handle(mut self, handle: &super::FocusHandle) -> Self {
1355 self.focus_handle = Some(handle.clone());
1356 self
1357 }
1358
1359 pub fn on_motion_finished(mut self, handler: impl FnMut(MotionFinished) + 'static) -> Self {
1364 self.on_motion_finished = Some(std::boxed::Box::new(handler));
1365 self
1366 }
1367
1368 pub fn bare(mut self) -> Self {
1374 self.bare = true;
1375 self
1376 }
1377
1378 fn compose(&mut self, content: impl FnOnce()) {
1379 let modifier = self.modifier;
1380 let text = std::mem::take(&mut self.text);
1381 let placeholder = std::mem::take(&mut self.placeholder);
1382 let (enabled, multiline, type_role) = (self.enabled, self.multiline, self.type_role);
1383 let bare = self.bare;
1384 let (font_size, font_weight, line_height) =
1385 (self.font_size, self.font_weight, self.line_height);
1386 let (letter_spacing, color) = (self.letter_spacing, self.color);
1387 let font = std::mem::take(&mut self.font);
1388 let font_features = std::mem::replace(&mut self.font_features, OwnedAttr::None);
1389 let (word_break, overflow_wrap) = (self.word_break, self.overflow_wrap);
1390 let key_down: EventCallback = match self.on_key_down.take() {
1391 Some(handler) => EventCallback::Key(Rc::new(RefCell::new(handler))),
1392 None => EventCallback::Key(Rc::new(RefCell::new(|_: KeyEvent| {}))),
1393 };
1394 let mut listeners = vec![
1395 ("onvaluechange", text_handler(self.on_value_change.take())),
1396 ("onsubmit", text_handler(self.on_submit.take())),
1397 ("onfocuslost", unit(self.on_focus_lost.take())),
1398 ("onkeydown", key_down),
1399 ];
1400 if let Some(handler) = self.on_focus_gained.take() {
1401 listeners.push(("onfocusgained", unit(Some(handler))));
1402 }
1403 if let Some(handler) = self.on_key_up.take() {
1404 listeners.push((
1405 "onkeyup",
1406 EventCallback::Key(Rc::new(RefCell::new(handler))),
1407 ));
1408 }
1409 if let Some(mut handler) = self.on_composition_change.take() {
1410 listeners.push((
1411 "oncompositionchange",
1412 EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
1413 handler(value != 0.0);
1414 }))),
1415 ));
1416 }
1417 motion_input(self.on_motion_finished.take(), &mut listeners);
1418 super::focus::bind_focus_handle(self.focus_handle.take());
1419 emit(
1420 widget_key("compose_rust::ui::TextField"),
1421 WidgetKind::TextField,
1422 |attrs| {
1423 push_modifiers(&modifier, Mods::Standard, attrs);
1424 if !text.is_empty() {
1427 attrs.push(("text", OwnedAttr::Text(text)));
1428 }
1429 attrs.push(("placeholder", OwnedAttr::Text(placeholder)));
1430 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1431 attrs.push(("multiline", OwnedAttr::Bool(multiline)));
1432 attrs.push(("type_role", super::role(type_role)));
1435 if let Some(font_size) = font_size {
1436 attrs.push(("font_size", OwnedAttr::Float(font_size as f64)));
1437 }
1438 if let Some(font_weight) = font_weight {
1439 attrs.push(("font_weight", OwnedAttr::Int(font_weight as i64)));
1440 }
1441 if let Some(line_height) = line_height {
1442 attrs.push(("line_height", OwnedAttr::Float(line_height as f64)));
1443 }
1444 if let Some(letter_spacing) = letter_spacing {
1445 attrs.push(("letter_spacing", OwnedAttr::Float(letter_spacing as f64)));
1446 }
1447 if let Some(color) = color {
1448 attrs.push(("color", super::paint(Some(color))));
1449 }
1450 attrs.push(("font", font_bytes(&font)));
1451 if font_features != OwnedAttr::None {
1453 attrs.push(("font_feature_settings", font_features));
1454 }
1455 attrs.push(("word_break", super::role(word_break)));
1456 attrs.push(("overflow_wrap", super::role(overflow_wrap)));
1457 if bare {
1459 attrs.push(("bare", OwnedAttr::Bool(true)));
1460 }
1461 },
1462 listeners,
1463 content,
1464 );
1465 }
1466}
1467
1468composed_on_drop!(TextFieldScope);
1469
1470fn lines_and_overflow(max_lines: Option<u32>, overflow: Option<TextOverflow>, attrs: &mut Attrs) {
1475 if let Some(lines) = max_lines {
1476 attrs.push(("max_lines", OwnedAttr::Int(i64::from(lines))));
1477 }
1478 if overflow.is_some() {
1479 attrs.push(("overflow", role_or_zero(overflow)));
1480 }
1481}
1482
1483#[derive(Clone, Copy, Default)]
1487struct ControlLook {
1488 track: Option<Paint>,
1489 thumb: Option<Paint>,
1490 active_track: Option<Paint>,
1491 container: Option<Paint>,
1492 check: Option<Paint>,
1493 size: Option<(f32, f32)>,
1494}
1495
1496impl ControlLook {
1497 fn push(&self, attrs: &mut Attrs) {
1498 let paints = [
1499 ("track_color", self.track),
1500 ("thumb_color", self.thumb),
1501 ("active_track_color", self.active_track),
1502 ("container_color", self.container),
1503 ("check_color", self.check),
1504 ];
1505 for (name, paint) in paints {
1506 if let Some(paint) = paint {
1507 attrs.push((name, super::paint(Some(paint))));
1508 }
1509 }
1510 if let Some((width, height)) = self.size {
1511 let mut bytes = Vec::with_capacity(8);
1512 bytes.extend_from_slice(&width.to_bits().to_le_bytes());
1513 bytes.extend_from_slice(&height.to_bits().to_le_bytes());
1514 attrs.push(("control_size", OwnedAttr::Bytes(bytes)));
1515 }
1516 }
1517}
1518
1519macro_rules! control_colour {
1521 ($name:ident, $slot:ident, $setter:ident, $doc:literal) => {
1522 impl $name {
1523 #[doc = $doc]
1524 pub fn $setter(mut self, paint: Paint) -> Self {
1525 self.look.$slot = Some(paint);
1526 self
1527 }
1528 }
1529 };
1530}
1531
1532fn push_accessible_name(name: Option<String>, attrs: &mut Attrs) {
1535 if let Some(name) = name.filter(|name| !name.is_empty()) {
1536 attrs.retain(|(key, _)| *key != "accessible_name");
1537 attrs.push(("accessible_name", OwnedAttr::Text(name)));
1538 }
1539}
1540
1541macro_rules! accessible_name {
1543 ($name:ident) => {
1544 impl $name {
1545 pub fn accessible_name(mut self, name: impl Into<String>) -> Self {
1549 self.accessible_name = Some(name.into());
1550 self
1551 }
1552 }
1553 };
1554}
1555
1556pub struct ButtonScope {
1558 modifier: Modifier,
1559 text: String,
1560 icon: Option<IconRole>,
1561 enabled: bool,
1562 variant: Option<ButtonVariant>,
1563 kind: Option<ButtonKind>,
1564 color: Option<Paint>,
1565 max_lines: Option<u32>,
1566 overflow: Option<TextOverflow>,
1567 accessible_name: Option<String>,
1568 text_align: Option<TextAlign>,
1569 font_size: Option<f32>,
1570 font_weight: Option<u16>,
1571 type_role: Option<TypeRole>,
1572 on_click: Option<Box<dyn FnMut()>>,
1573 focus_handle: Option<super::FocusHandle>,
1574 done: bool,
1575}
1576
1577pub fn Button(text: impl Into<String>) -> ButtonScope {
1583 ButtonScope {
1584 modifier: Modifier::new(),
1585 text: text.into(),
1586 icon: None,
1587 enabled: true,
1588 variant: None,
1589 kind: None,
1590 color: None,
1591 max_lines: None,
1592 overflow: None,
1593 accessible_name: None,
1594 text_align: None,
1595 font_size: None,
1596 font_weight: None,
1597 type_role: None,
1598 on_click: None,
1599 focus_handle: None,
1600 done: false,
1601 }
1602}
1603
1604accessible_name!(ButtonScope);
1605
1606impl ButtonScope {
1607 pub fn text_align(mut self, align: TextAlign) -> Self {
1610 self.text_align = Some(align);
1611 self
1612 }
1613
1614 pub fn font_size(mut self, sp: f32) -> Self {
1617 self.font_size = Some(sp);
1618 self
1619 }
1620
1621 pub fn font_weight(mut self, weight: u16) -> Self {
1624 self.font_weight = Some(weight);
1625 self
1626 }
1627
1628 pub fn type_role(mut self, role: TypeRole) -> Self {
1632 self.type_role = Some(role);
1633 self
1634 }
1635
1636 pub fn icon(mut self, icon: IconRole) -> Self {
1638 self.icon = Some(icon);
1639 self
1640 }
1641
1642 pub fn enabled(mut self, enabled: bool) -> Self {
1643 self.enabled = enabled;
1644 self
1645 }
1646
1647 pub fn variant(mut self, variant: ButtonVariant) -> Self {
1648 self.variant = Some(variant);
1649 self
1650 }
1651
1652 pub fn kind(mut self, kind: ButtonKind) -> Self {
1655 self.kind = Some(kind);
1656 self
1657 }
1658
1659 pub fn max_lines(mut self, lines: u32) -> Self {
1662 self.max_lines = Some(lines);
1663 self
1664 }
1665
1666 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
1668 self.overflow = Some(overflow);
1669 self
1670 }
1671
1672 pub fn color(mut self, paint: Paint) -> Self {
1674 self.color = Some(paint);
1675 self
1676 }
1677
1678 pub fn color_if(mut self, paint: Option<Paint>) -> Self {
1680 self.color = paint;
1681 self
1682 }
1683
1684 pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
1685 self.on_click = Some(std::boxed::Box::new(handler));
1686 self
1687 }
1688
1689 pub fn focus_handle(mut self, handle: &super::FocusHandle) -> Self {
1691 self.focus_handle = Some(handle.clone());
1692 self
1693 }
1694
1695 fn compose(&mut self, content: impl FnOnce()) {
1696 let modifier = self.modifier;
1697 let text = std::mem::take(&mut self.text);
1698 let (icon, enabled, variant, kind, color) =
1699 (self.icon, self.enabled, self.variant, self.kind, self.color);
1700 let (max_lines, overflow) = (self.max_lines, self.overflow);
1701 let accessible_name = self.accessible_name.take();
1702 let (text_align, font_size) = (self.text_align, self.font_size);
1703 let (font_weight, type_role) = (self.font_weight, self.type_role);
1704 let listeners = vec![("onclick", unit(self.on_click.take()))];
1705 super::focus::bind_focus_handle(self.focus_handle.take());
1706 emit(
1707 widget_key("compose_rust::ui::Button"),
1708 WidgetKind::Button,
1709 |attrs| {
1710 push_modifiers(&modifier, Mods::Standard, attrs);
1711 attrs.push(("text", OwnedAttr::Text(text)));
1712 attrs.push(("icon", super::role(icon)));
1713 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1714 attrs.push(("variant", role_or_zero(variant)));
1715 attrs.push(("button_kind", role_or_zero(kind)));
1716 attrs.push(("color", super::paint(color)));
1717 lines_and_overflow(max_lines, overflow, attrs);
1718 push_accessible_name(accessible_name, attrs);
1719 if text_align.is_some() {
1720 attrs.push(("text_align", role_or_zero(text_align)));
1721 }
1722 if font_size.is_some() {
1723 attrs.push(("font_size", dp_or_zero(font_size)));
1724 }
1725 if let Some(font_weight) = font_weight {
1726 attrs.push(("font_weight", OwnedAttr::Int(i64::from(font_weight))));
1727 }
1728 if type_role.is_some() {
1729 attrs.push(("type_role", super::role(type_role)));
1730 }
1731 },
1732 listeners,
1733 content,
1734 );
1735 }
1736}
1737
1738container!(ButtonScope);
1739
1740macro_rules! toggle {
1743 ($name:ident, $widget:ident, $setter:ident, $key:literal, $doc:literal) => {
1744 #[doc = concat!("A `", stringify!($widget), "` and its builder.")]
1745 pub struct $name {
1746 modifier: Modifier,
1747 checked: bool,
1748 enabled: bool,
1749 accessible_name: Option<String>,
1750 look: ControlLook,
1751 on_change: Option<Box<dyn FnMut(bool)>>,
1752 done: bool,
1753 }
1754
1755 #[doc = $doc]
1756 pub fn $widget($setter: bool) -> $name {
1757 $name {
1758 modifier: Modifier::new(),
1759 checked: $setter,
1760 enabled: true,
1761 accessible_name: None,
1762 look: ControlLook::default(),
1763 on_change: None,
1764 done: false,
1765 }
1766 }
1767
1768 accessible_name!($name);
1769
1770 impl $name {
1771 pub fn enabled(mut self, enabled: bool) -> Self {
1772 self.enabled = enabled;
1773 self
1774 }
1775
1776 pub fn on_change(mut self, handler: impl FnMut(bool) + 'static) -> Self {
1779 self.on_change = Some(std::boxed::Box::new(handler));
1780 self
1781 }
1782
1783 fn compose(&mut self, content: impl FnOnce()) {
1784 let modifier = self.modifier;
1785 let (checked, enabled) = (self.checked, self.enabled);
1786 let accessible_name = self.accessible_name.take();
1787 let look = self.look;
1788 let handler: EventCallback = match self.on_change.take() {
1789 Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(
1790 move |value: f64| handler(value != 0.0),
1793 ))),
1794 None => value_handler(None),
1795 };
1796 emit(
1797 widget_key($key),
1798 WidgetKind::$widget,
1799 |attrs| {
1800 push_modifiers(&modifier, Mods::Standard, attrs);
1801 attrs.push(("checked", OwnedAttr::Bool(checked)));
1802 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
1803 push_accessible_name(accessible_name, attrs);
1804 look.push(attrs);
1805 },
1806 vec![("onchange", handler)],
1807 content,
1808 );
1809 }
1810 }
1811
1812 composed_on_drop!($name);
1813 };
1814}
1815
1816control_colour!(
1817 SwitchScope,
1818 track,
1819 track_color,
1820 "The track, whichever state it is in. The Host owns the state, so it passes the colour \
1821 that goes with it."
1822);
1823control_colour!(
1824 SwitchScope,
1825 thumb,
1826 thumb_color,
1827 "The thumb that travels along the track."
1828);
1829
1830impl SwitchScope {
1831 pub fn size(mut self, width: f32, height: f32) -> Self {
1833 self.look.size = Some((width, height));
1834 self
1835 }
1836}
1837
1838control_colour!(
1839 CheckboxScope,
1840 container,
1841 container_color,
1842 "The box when it is checked."
1843);
1844control_colour!(CheckboxScope, check, check_color, "The check mark.");
1845
1846impl CheckboxScope {
1847 pub fn size(mut self, side: f32) -> Self {
1849 self.look.size = Some((side, side));
1850 self
1851 }
1852}
1853
1854control_colour!(
1855 RadioButtonScope,
1856 container,
1857 ring_color,
1858 "The ring around the button, selected or not. Where the design system fills a selected \
1859 button, the fill takes this colour as well."
1860);
1861control_colour!(
1862 RadioButtonScope,
1863 check,
1864 dot_color,
1865 "The dot inside a selected button."
1866);
1867
1868impl RadioButtonScope {
1869 pub fn size(mut self, side: f32) -> Self {
1871 self.look.size = Some((side, side));
1872 self
1873 }
1874}
1875
1876toggle!(
1877 CheckboxScope,
1878 Checkbox,
1879 checked,
1880 "compose_rust::ui::Checkbox",
1881 "A box that is ticked or not. Compose's `Checkbox(checked, onCheckedChange)`."
1882);
1883toggle!(
1884 RadioButtonScope,
1885 RadioButton,
1886 selected,
1887 "compose_rust::ui::RadioButton",
1888 "One choice out of several. Compose's `RadioButton(selected, onClick)`."
1889);
1890toggle!(
1891 SwitchScope,
1892 Switch,
1893 checked,
1894 "compose_rust::ui::Switch",
1895 "An on and off control. Compose's `Switch(checked, onCheckedChange)`."
1896);
1897
1898pub struct SliderScope {
1900 modifier: Modifier,
1901 color: Option<Paint>,
1902 value: f32,
1903 min: f32,
1904 max: f32,
1905 steps: u32,
1906 enabled: bool,
1907 accessible_name: Option<String>,
1908 look: ControlLook,
1909 thumb_size: Option<f32>,
1910 on_change: Option<Box<dyn FnMut(f32)>>,
1911 done: bool,
1912}
1913
1914pub fn Slider(value: f32) -> SliderScope {
1917 SliderScope {
1918 modifier: Modifier::new(),
1919 color: None,
1920 value,
1921 min: 0.0,
1922 max: 1.0,
1923 steps: 0,
1924 enabled: true,
1925 accessible_name: None,
1926 look: ControlLook::default(),
1927 thumb_size: None,
1928 on_change: None,
1929 done: false,
1930 }
1931}
1932
1933accessible_name!(SliderScope);
1934control_colour!(
1935 SliderScope,
1936 track,
1937 track_color,
1938 "The part of the track that is not filled."
1939);
1940control_colour!(SliderScope, thumb, thumb_color, "The thumb.");
1941control_colour!(
1942 SliderScope,
1943 active_track,
1944 active_track_color,
1945 "The part of the track up to the thumb."
1946);
1947
1948impl SliderScope {
1949 pub fn range(mut self, min: f32, max: f32) -> Self {
1951 self.min = min;
1952 self.max = max;
1953 self
1954 }
1955
1956 pub fn steps(mut self, steps: u32) -> Self {
1958 self.steps = steps;
1959 self
1960 }
1961
1962 pub fn enabled(mut self, enabled: bool) -> Self {
1963 self.enabled = enabled;
1964 self
1965 }
1966
1967 pub fn color(mut self, paint: Paint) -> Self {
1969 self.color = Some(paint);
1970 self
1971 }
1972
1973 pub fn thumb_size(mut self, dp: f32) -> Self {
1977 self.thumb_size = Some(dp);
1978 self
1979 }
1980
1981 pub fn on_change(mut self, handler: impl FnMut(f32) + 'static) -> Self {
1982 self.on_change = Some(std::boxed::Box::new(handler));
1983 self
1984 }
1985
1986 fn compose(&mut self, content: impl FnOnce()) {
1987 let modifier = self.modifier;
1988 let thumb_size = self.thumb_size;
1989 let (color, value, min, max, steps, enabled) = (
1990 self.color,
1991 self.value,
1992 self.min,
1993 self.max,
1994 self.steps,
1995 self.enabled,
1996 );
1997 let accessible_name = self.accessible_name.take();
1998 let look = self.look;
1999 let handler = match self.on_change.take() {
2000 Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
2001 handler(value as f32)
2002 }))),
2003 None => value_handler(None),
2004 };
2005 emit(
2006 widget_key("compose_rust::ui::Slider"),
2007 WidgetKind::Slider,
2008 |attrs| {
2009 push_modifiers(&modifier, Mods::Standard, attrs);
2010 attrs.push(("color", super::paint(color)));
2011 attrs.push(("value", OwnedAttr::Float(f64::from(value))));
2012 attrs.push(("min", OwnedAttr::Float(f64::from(min))));
2013 attrs.push(("max", OwnedAttr::Float(f64::from(max))));
2014 attrs.push(("steps", OwnedAttr::Int(i64::from(steps))));
2015 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
2016 push_accessible_name(accessible_name, attrs);
2017 look.push(attrs);
2018 if let Some(dp) = thumb_size {
2019 attrs.push(("thumb_size", OwnedAttr::Float(f64::from(dp))));
2020 }
2021 },
2022 vec![("onchange", handler)],
2023 content,
2024 );
2025 }
2026}
2027
2028composed_on_drop!(SliderScope);
2029
2030pub struct ProgressIndicatorScope {
2032 modifier: Modifier,
2033 value: f32,
2034 determinate: bool,
2035 circular: bool,
2036 done: bool,
2037}
2038
2039pub fn ProgressIndicator(value: f32) -> ProgressIndicatorScope {
2041 ProgressIndicatorScope {
2042 modifier: Modifier::new(),
2043 value,
2044 determinate: true,
2045 circular: false,
2046 done: false,
2047 }
2048}
2049
2050pub fn IndeterminateProgressIndicator() -> ProgressIndicatorScope {
2052 ProgressIndicator(0.0).determinate(false)
2053}
2054
2055impl ProgressIndicatorScope {
2056 pub fn determinate(mut self, determinate: bool) -> Self {
2057 self.determinate = determinate;
2058 self
2059 }
2060
2061 pub fn circular(mut self, circular: bool) -> Self {
2063 self.circular = circular;
2064 self
2065 }
2066
2067 fn compose(&mut self, content: impl FnOnce()) {
2068 let modifier = self.modifier;
2069 let (value, determinate, circular) = (self.value, self.determinate, self.circular);
2070 emit(
2071 widget_key("compose_rust::ui::ProgressIndicator"),
2072 WidgetKind::ProgressIndicator,
2073 |attrs| {
2074 push_modifiers(&modifier, Mods::Standard, attrs);
2075 attrs.push(("value", OwnedAttr::Float(f64::from(value))));
2076 attrs.push(("determinate", OwnedAttr::Bool(determinate)));
2077 attrs.push(("circular", OwnedAttr::Bool(circular)));
2078 },
2079 Vec::new(),
2080 content,
2081 );
2082 }
2083}
2084
2085composed_on_drop!(ProgressIndicatorScope);
2086
2087pub struct DividerScope {
2089 modifier: Modifier,
2090 vertical: bool,
2091 done: bool,
2092}
2093
2094pub fn Divider() -> DividerScope {
2096 DividerScope {
2097 modifier: Modifier::new(),
2098 vertical: false,
2099 done: false,
2100 }
2101}
2102
2103pub fn Separator(color: Option<Paint>) -> DividerScope {
2106 Divider().modifier(Modifier::new().background_if(color))
2107}
2108
2109impl DividerScope {
2110 pub fn vertical(mut self, vertical: bool) -> Self {
2111 self.vertical = vertical;
2112 self
2113 }
2114
2115 fn compose(&mut self, content: impl FnOnce()) {
2116 let modifier = self.modifier;
2117 let vertical = self.vertical;
2118 emit(
2119 widget_key("compose_rust::ui::Divider"),
2120 WidgetKind::Divider,
2121 |attrs| {
2122 push_modifiers(&modifier, Mods::Standard, attrs);
2123 attrs.push(("vertical", OwnedAttr::Bool(vertical)));
2124 },
2125 Vec::new(),
2126 content,
2127 );
2128 }
2129}
2130
2131composed_on_drop!(DividerScope);
2132
2133pub struct AssetScope {
2137 modifier: Modifier,
2138 widget: WidgetKind,
2139 asset_id: u32,
2140 color: Option<Paint>,
2141 content_scale: Option<ContentScale>,
2142 done: bool,
2143}
2144
2145pub fn Image(asset_id: u32) -> AssetScope {
2147 AssetScope {
2148 modifier: Modifier::new(),
2149 widget: WidgetKind::Image,
2150 asset_id,
2151 color: None,
2152 content_scale: None,
2153 done: false,
2154 }
2155}
2156
2157pub fn Icon(asset_id: u32) -> AssetScope {
2159 AssetScope {
2160 modifier: Modifier::new(),
2161 widget: WidgetKind::Icon,
2162 asset_id,
2163 color: None,
2164 content_scale: None,
2165 done: false,
2166 }
2167}
2168
2169impl AssetScope {
2170 pub fn color(mut self, paint: Paint) -> Self {
2172 self.color = Some(paint);
2173 self
2174 }
2175
2176 pub fn content_scale(mut self, scale: ContentScale) -> Self {
2182 self.content_scale = Some(scale);
2183 self
2184 }
2185
2186 fn compose(&mut self, content: impl FnOnce()) {
2187 let modifier = self.modifier;
2188 let (widget, asset_id, color) = (self.widget, self.asset_id, self.color);
2189 let content_scale = self.content_scale;
2190 let key = if widget == WidgetKind::Icon {
2191 widget_key("compose_rust::ui::Icon")
2192 } else {
2193 widget_key("compose_rust::ui::Image")
2194 };
2195 emit(
2196 key,
2197 widget,
2198 |attrs| {
2199 push_modifiers(&modifier, Mods::Standard, attrs);
2200 attrs.push(("asset", OwnedAttr::Int(i64::from(asset_id))));
2201 if widget == WidgetKind::Icon {
2202 attrs.push(("color", paint_or_zero(color)));
2203 }
2204 if let (WidgetKind::Image, Some(scale)) = (widget, content_scale) {
2207 attrs.push(("content_scale", OwnedAttr::Int(i64::from(u16::from(scale)))));
2208 }
2209 },
2210 Vec::new(),
2211 content,
2212 );
2213 }
2214}
2215
2216composed_on_drop!(AssetScope);
2217
2218pub struct CanvasScope {
2220 modifier: Modifier,
2221 commands: DrawList,
2222 view_box: Option<(f32, f32)>,
2223 done: bool,
2224}
2225
2226pub fn Canvas(commands: DrawList) -> CanvasScope {
2230 let view_box = commands.view_box();
2231 CanvasScope {
2232 modifier: Modifier::new(),
2233 commands,
2234 view_box,
2235 done: false,
2236 }
2237}
2238
2239impl CanvasScope {
2240 pub fn view_box(mut self, width: f32, height: f32) -> Self {
2243 self.view_box = Some((width, height));
2244 self
2245 }
2246
2247 pub fn fractional(self) -> Self {
2250 self.view_box(1.0, 1.0)
2251 }
2252
2253 fn compose(&mut self, content: impl FnOnce()) {
2254 let modifier = self.modifier;
2255 let commands = std::mem::take(&mut self.commands);
2256 let view_box = self.view_box;
2257 emit(
2258 widget_key("compose_rust::ui::Canvas"),
2259 WidgetKind::Canvas,
2260 |attrs| {
2261 push_modifiers(&modifier, Mods::Standard, attrs);
2262 attrs.push(("commands", OwnedAttr::Draw(commands)));
2263 if let Some((width, height)) = view_box {
2264 attrs.push(("min", OwnedAttr::Float(f64::from(width))));
2265 attrs.push(("max", OwnedAttr::Float(f64::from(height))));
2266 }
2267 },
2268 Vec::new(),
2269 content,
2270 );
2271 }
2272}
2273
2274composed_on_drop!(CanvasScope);
2275
2276pub struct DatePickerScope {
2280 modifier: Modifier,
2281 value: i64,
2282 min: Option<i64>,
2283 max: Option<i64>,
2284 enabled: bool,
2285 on_change: Option<Box<dyn FnMut(i64)>>,
2286 done: bool,
2287}
2288
2289pub fn DatePicker(value: i64) -> DatePickerScope {
2291 DatePickerScope {
2292 modifier: Modifier::new(),
2293 value,
2294 min: None,
2295 max: None,
2296 enabled: true,
2297 on_change: None,
2298 done: false,
2299 }
2300}
2301
2302impl DatePickerScope {
2303 pub fn min(mut self, days: i64) -> Self {
2304 self.min = Some(days);
2305 self
2306 }
2307
2308 pub fn max(mut self, days: i64) -> Self {
2309 self.max = Some(days);
2310 self
2311 }
2312
2313 pub fn enabled(mut self, enabled: bool) -> Self {
2314 self.enabled = enabled;
2315 self
2316 }
2317
2318 pub fn on_change(mut self, handler: impl FnMut(i64) + 'static) -> Self {
2319 self.on_change = Some(std::boxed::Box::new(handler));
2320 self
2321 }
2322
2323 fn compose(&mut self, content: impl FnOnce()) {
2324 let modifier = self.modifier;
2325 let (value, min, max, enabled) = (self.value, self.min, self.max, self.enabled);
2326 let handler = match self.on_change.take() {
2327 Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
2328 handler(value as i64)
2329 }))),
2330 None => value_handler(None),
2331 };
2332 emit(
2333 widget_key("compose_rust::ui::DatePicker"),
2334 WidgetKind::DatePicker,
2335 |attrs| {
2336 push_modifiers(&modifier, Mods::Standard, attrs);
2337 attrs.push(("value", OwnedAttr::Int(value)));
2338 attrs.push(("min", OwnedAttr::Int(min.unwrap_or(i64::MIN))));
2339 attrs.push(("max", OwnedAttr::Int(max.unwrap_or(i64::MAX))));
2340 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
2341 },
2342 vec![("onchange", handler)],
2343 content,
2344 );
2345 }
2346}
2347
2348composed_on_drop!(DatePickerScope);
2349
2350const MINUTES_IN_A_DAY: u32 = 24 * 60;
2351
2352pub struct TimePickerScope {
2354 modifier: Modifier,
2355 value: u32,
2356 min: Option<u32>,
2357 max: Option<u32>,
2358 enabled: bool,
2359 on_change: Option<Box<dyn FnMut(u32)>>,
2360 done: bool,
2361}
2362
2363pub fn TimePicker(value: u32) -> TimePickerScope {
2365 TimePickerScope {
2366 modifier: Modifier::new(),
2367 value,
2368 min: None,
2369 max: None,
2370 enabled: true,
2371 on_change: None,
2372 done: false,
2373 }
2374}
2375
2376impl TimePickerScope {
2377 pub fn min(mut self, minutes: u32) -> Self {
2378 self.min = Some(minutes);
2379 self
2380 }
2381
2382 pub fn max(mut self, minutes: u32) -> Self {
2383 self.max = Some(minutes);
2384 self
2385 }
2386
2387 pub fn enabled(mut self, enabled: bool) -> Self {
2388 self.enabled = enabled;
2389 self
2390 }
2391
2392 pub fn on_change(mut self, handler: impl FnMut(u32) + 'static) -> Self {
2393 self.on_change = Some(std::boxed::Box::new(handler));
2394 self
2395 }
2396
2397 fn compose(&mut self, content: impl FnOnce()) {
2398 let modifier = self.modifier;
2399 let (value, min, max, enabled) = (self.value, self.min, self.max, self.enabled);
2400 let handler = match self.on_change.take() {
2401 Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
2402 handler((value as i64).clamp(0, i64::from(MINUTES_IN_A_DAY - 1)) as u32)
2403 }))),
2404 None => value_handler(None),
2405 };
2406 emit(
2407 widget_key("compose_rust::ui::TimePicker"),
2408 WidgetKind::TimePicker,
2409 |attrs| {
2410 push_modifiers(&modifier, Mods::Standard, attrs);
2411 attrs.push(("value", OwnedAttr::Int(i64::from(value))));
2412 attrs.push(("min", OwnedAttr::Int(i64::from(min.unwrap_or(0)))));
2413 attrs.push((
2414 "max",
2415 OwnedAttr::Int(i64::from(max.unwrap_or(MINUTES_IN_A_DAY - 1))),
2416 ));
2417 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
2418 },
2419 vec![("onchange", handler)],
2420 content,
2421 );
2422 }
2423}
2424
2425composed_on_drop!(TimePickerScope);
2426
2427pub struct DropdownScope {
2429 modifier: Modifier,
2430 selected_index: usize,
2431 enabled: bool,
2432 closed: bool,
2433 bare: bool,
2434 on_change: Option<Box<dyn FnMut(usize)>>,
2435 done: bool,
2436}
2437
2438pub fn Dropdown(selected_index: usize) -> DropdownScope {
2440 DropdownScope {
2441 modifier: Modifier::new(),
2442 selected_index,
2443 enabled: true,
2444 closed: false,
2445 bare: false,
2446 on_change: None,
2447 done: false,
2448 }
2449}
2450
2451pub fn Select(selected_index: usize) -> DropdownScope {
2454 Dropdown(selected_index).closed(true)
2455}
2456
2457impl DropdownScope {
2458 pub fn enabled(mut self, enabled: bool) -> Self {
2459 self.enabled = enabled;
2460 self
2461 }
2462
2463 pub fn closed(mut self, closed: bool) -> Self {
2466 self.closed = closed;
2467 self
2468 }
2469
2470 pub fn on_change(mut self, handler: impl FnMut(usize) + 'static) -> Self {
2471 self.on_change = Some(std::boxed::Box::new(handler));
2472 self
2473 }
2474
2475 pub fn bare(mut self) -> Self {
2480 self.bare = true;
2481 self
2482 }
2483
2484 fn compose(&mut self, content: impl FnOnce()) {
2485 let modifier = self.modifier;
2486 let (selected_index, enabled, closed) = (self.selected_index, self.enabled, self.closed);
2487 let bare = self.bare;
2488 let handler = match self.on_change.take() {
2489 Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
2490 handler((value as i64).max(0) as usize)
2491 }))),
2492 None => value_handler(None),
2493 };
2494 emit(
2495 widget_key("compose_rust::ui::Dropdown"),
2496 WidgetKind::Dropdown,
2497 |attrs| {
2498 push_modifiers(&modifier, Mods::Standard, attrs);
2499 attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
2500 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
2501 if closed {
2502 attrs.push(("collapsible", OwnedAttr::Bool(true)));
2503 }
2504 if bare {
2505 attrs.push(("bare", OwnedAttr::Bool(true)));
2506 }
2507 },
2508 vec![("onchange", handler)],
2509 content,
2510 );
2511 }
2512}
2513
2514container!(DropdownScope);
2515
2516pub struct MenuItemScope {
2518 modifier: Modifier,
2519 text: String,
2520 icon: Option<IconRole>,
2521 selected: bool,
2522 enabled: bool,
2523 color: Option<Paint>,
2524 on_click: Option<Box<dyn FnMut()>>,
2525 done: bool,
2526}
2527
2528pub fn MenuItem(text: impl Into<String>) -> MenuItemScope {
2531 MenuItemScope {
2532 modifier: Modifier::new(),
2533 text: text.into(),
2534 icon: None,
2535 selected: false,
2536 enabled: true,
2537 color: None,
2538 on_click: None,
2539 done: false,
2540 }
2541}
2542
2543impl MenuItemScope {
2544 pub fn icon(mut self, icon: IconRole) -> Self {
2546 self.icon = Some(icon);
2547 self
2548 }
2549
2550 pub fn selected(mut self, selected: bool) -> Self {
2552 self.selected = selected;
2553 self
2554 }
2555
2556 pub fn enabled(mut self, enabled: bool) -> Self {
2557 self.enabled = enabled;
2558 self
2559 }
2560
2561 pub fn color(mut self, paint: Paint) -> Self {
2563 self.color = Some(paint);
2564 self
2565 }
2566
2567 pub fn color_if(mut self, paint: Option<Paint>) -> Self {
2569 self.color = paint;
2570 self
2571 }
2572
2573 pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
2574 self.on_click = Some(std::boxed::Box::new(handler));
2575 self
2576 }
2577
2578 fn compose(&mut self, content: impl FnOnce()) {
2579 let modifier = self.modifier;
2580 let text = std::mem::take(&mut self.text);
2581 let (icon, selected, enabled, color) = (self.icon, self.selected, self.enabled, self.color);
2582 let listeners = vec![("onclick", unit(self.on_click.take()))];
2583 emit(
2584 widget_key("compose_rust::ui::MenuItem"),
2585 WidgetKind::Button,
2586 |attrs| {
2587 push_modifiers(&modifier, Mods::Standard, attrs);
2588 attrs.push(("text", OwnedAttr::Text(text)));
2589 attrs.push(("icon", super::role(icon)));
2590 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
2591 attrs.push(("variant", role_or_zero(Some(ButtonVariant::Text))));
2592 attrs.push(("text_align", role_or_zero(Some(TextAlign::Start))));
2593 attrs.push(("checked", OwnedAttr::Bool(selected)));
2594 attrs.push(("color", super::paint(color)));
2595 },
2596 listeners,
2597 content,
2598 );
2599 }
2600}
2601
2602composed_on_drop!(MenuItemScope);
2603
2604pub struct ModalScope {
2609 modifier: Modifier,
2610 widget: WidgetKind,
2611 open: bool,
2612 scrim: Option<Paint>,
2613 max_height_fraction: Option<f32>,
2614 on_dismiss: Option<Box<dyn FnMut()>>,
2615 done: bool,
2616}
2617
2618pub fn Dialog(open: bool) -> ModalScope {
2621 ModalScope {
2622 modifier: Modifier::new(),
2623 widget: WidgetKind::Dialog,
2624 open,
2625 scrim: None,
2626 max_height_fraction: None,
2627 on_dismiss: None,
2628 done: false,
2629 }
2630}
2631
2632pub fn Sheet(open: bool) -> ModalScope {
2635 ModalScope {
2636 modifier: Modifier::new(),
2637 widget: WidgetKind::Sheet,
2638 open,
2639 scrim: None,
2640 max_height_fraction: None,
2641 on_dismiss: None,
2642 done: false,
2643 }
2644}
2645
2646impl ModalScope {
2647 pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
2648 self.on_dismiss = Some(std::boxed::Box::new(handler));
2649 self
2650 }
2651
2652 pub fn scrim(mut self, paint: impl Into<Paint>) -> Self {
2654 self.scrim = Some(paint.into());
2655 self
2656 }
2657
2658 pub fn max_height_fraction(mut self, fraction: f32) -> Self {
2667 self.max_height_fraction = (fraction > 0.0).then(|| fraction.min(1.0));
2668 self
2669 }
2670
2671 fn compose(&mut self, content: impl FnOnce()) {
2672 let modifier = self.modifier;
2673 let (widget, open) = (self.widget, self.open);
2674 let max_height_fraction = self
2675 .max_height_fraction
2676 .filter(|_| widget == WidgetKind::Sheet);
2677 let key = if widget == WidgetKind::Dialog {
2678 widget_key("compose_rust::ui::Dialog")
2679 } else {
2680 widget_key("compose_rust::ui::Sheet")
2681 };
2682 let scrim = self.scrim;
2683 emit(
2684 key,
2685 widget,
2686 |attrs| {
2687 push_modifiers(&modifier, Mods::Standard, attrs);
2688 attrs.push(("open", OwnedAttr::Bool(open)));
2689 if let Some(scrim) = scrim {
2690 attrs.push(("scrim", super::paint(Some(scrim))));
2691 }
2692 if let Some(fraction) = max_height_fraction {
2693 attrs.push(("max_height_fraction", OwnedAttr::Float(f64::from(fraction))));
2694 }
2695 },
2696 vec![("ondismiss", unit(self.on_dismiss.take()))],
2697 content,
2698 );
2699 }
2700}
2701
2702container!(ModalScope);
2703
2704pub struct MenuScope<'a> {
2706 modifier: Modifier,
2707 expanded: bool,
2708 scrim: Option<Paint>,
2709 on_dismiss: Option<Box<dyn FnMut()>>,
2710 anchor: Option<Box<dyn FnOnce() + 'a>>,
2711 done: bool,
2712}
2713
2714pub fn Menu<'a>(expanded: bool) -> MenuScope<'a> {
2717 MenuScope {
2718 modifier: Modifier::new(),
2719 expanded,
2720 scrim: None,
2721 on_dismiss: None,
2722 anchor: None,
2723 done: false,
2724 }
2725}
2726
2727impl<'a> MenuScope<'a> {
2728 pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
2729 self.on_dismiss = Some(std::boxed::Box::new(handler));
2730 self
2731 }
2732
2733 pub fn scrim(mut self, paint: impl Into<Paint>) -> Self {
2735 self.scrim = Some(paint.into());
2736 self
2737 }
2738
2739 pub fn anchor(mut self, anchor: impl FnOnce() + 'a) -> Self {
2741 self.anchor = Some(std::boxed::Box::new(anchor));
2742 self
2743 }
2744
2745 fn compose(&mut self, content: impl FnOnce()) {
2746 let modifier = self.modifier;
2747 let expanded = self.expanded;
2748 let anchor = self.anchor.take();
2749 let scrim = self.scrim;
2750 emit(
2751 widget_key("compose_rust::ui::Menu"),
2752 WidgetKind::Menu,
2753 |attrs| {
2754 push_modifiers(&modifier, Mods::Standard, attrs);
2755 attrs.push(("open", OwnedAttr::Bool(expanded)));
2756 if let Some(scrim) = scrim {
2757 attrs.push(("scrim", super::paint(Some(scrim))));
2758 }
2759 },
2760 vec![("ondismiss", unit(self.on_dismiss.take()))],
2761 || {
2762 if let Some(anchor) = anchor {
2763 let _anchor =
2764 crate::runtime::group_guard(widget_key("compose_rust::ui::Menu::anchor"));
2765 anchor();
2766 }
2767 let _entries =
2768 crate::runtime::group_guard(widget_key("compose_rust::ui::Menu::entries"));
2769 content();
2770 },
2771 );
2772 }
2773}
2774
2775container!(MenuScope<'a>);
2776
2777pub struct PopoverScope<'a> {
2779 modifier: Modifier,
2780 open: bool,
2781 placement: Option<PopoverPlacement>,
2782 align: Option<PopoverAlign>,
2783 offset: Option<f32>,
2784 scrim: bool,
2785 scrim_color: Option<Paint>,
2786 on_dismiss: Option<Box<dyn FnMut()>>,
2787 anchor: Option<Box<dyn FnOnce() + 'a>>,
2788 done: bool,
2789}
2790
2791pub fn Popover<'a>(open: bool) -> PopoverScope<'a> {
2805 PopoverScope {
2806 modifier: Modifier::new(),
2807 open,
2808 placement: None,
2809 align: None,
2810 offset: None,
2811 scrim: false,
2812 scrim_color: None,
2813 on_dismiss: None,
2814 anchor: None,
2815 done: false,
2816 }
2817}
2818
2819impl<'a> PopoverScope<'a> {
2820 pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
2822 self.on_dismiss = Some(std::boxed::Box::new(handler));
2823 self
2824 }
2825
2826 pub fn placement(mut self, placement: PopoverPlacement) -> Self {
2828 self.placement = Some(placement);
2829 self
2830 }
2831
2832 pub fn align(mut self, align: PopoverAlign) -> Self {
2834 self.align = Some(align);
2835 self
2836 }
2837
2838 pub fn offset(mut self, dp: f32) -> Self {
2841 self.offset = Some(dp);
2842 self
2843 }
2844
2845 pub fn scrim(mut self, scrim: bool) -> Self {
2848 self.scrim = scrim;
2849 self
2850 }
2851
2852 pub fn scrim_color(mut self, paint: impl Into<Paint>) -> Self {
2854 self.scrim_color = Some(paint.into());
2855 self
2856 }
2857
2858 pub fn anchor(mut self, anchor: impl FnOnce() + 'a) -> Self {
2860 self.anchor = Some(std::boxed::Box::new(anchor));
2861 self
2862 }
2863
2864 fn compose(&mut self, content: impl FnOnce()) {
2865 let modifier = self.modifier;
2866 let open = self.open;
2867 let (placement, align, offset) = (self.placement, self.align, self.offset);
2868 let (scrim, scrim_color) = (self.scrim, self.scrim_color);
2869 let anchor = self.anchor.take();
2870 emit(
2871 widget_key("compose_rust::ui::Popover"),
2872 WidgetKind::Popover,
2873 |attrs| {
2874 push_modifiers(&modifier, Mods::Standard, attrs);
2875 attrs.push(("open", OwnedAttr::Bool(open)));
2876 if let Some(placement) = placement {
2877 attrs.push(("placement", role_or_zero(Some(placement))));
2878 }
2879 if let Some(align) = align {
2880 attrs.push(("align", role_or_zero(Some(align))));
2881 }
2882 if let Some(offset) = offset {
2883 attrs.push(("offset_dp", OwnedAttr::Float(f64::from(offset))));
2884 }
2885 if let Some(color) = scrim_color {
2886 attrs.push(("scrim", super::paint(Some(color))));
2887 } else if scrim {
2888 attrs.push(("scrim", OwnedAttr::Bool(true)));
2889 }
2890 },
2891 vec![("ondismiss", unit(self.on_dismiss.take()))],
2892 || {
2893 if let Some(anchor) = anchor {
2894 let _anchor = crate::runtime::group_guard(widget_key(
2895 "compose_rust::ui::Popover::anchor",
2896 ));
2897 anchor();
2898 }
2899 let _content =
2900 crate::runtime::group_guard(widget_key("compose_rust::ui::Popover::content"));
2901 content();
2902 },
2903 );
2904 }
2905}
2906
2907container!(PopoverScope<'a>);
2908
2909pub struct TabsScope {
2911 modifier: Modifier,
2912 color: Option<Paint>,
2913 selected_index: usize,
2914 done: bool,
2915}
2916
2917pub fn Tabs(selected_index: usize) -> TabsScope {
2920 TabsScope {
2921 modifier: Modifier::new(),
2922 color: None,
2923 selected_index,
2924 done: false,
2925 }
2926}
2927
2928impl TabsScope {
2929 pub fn color(mut self, paint: Paint) -> Self {
2931 self.color = Some(paint);
2932 self
2933 }
2934
2935 fn compose(&mut self, content: impl FnOnce()) {
2936 let modifier = self.modifier;
2937 let (color, selected_index) = (self.color, self.selected_index);
2938 emit(
2939 widget_key("compose_rust::ui::Tabs"),
2940 WidgetKind::Tabs,
2941 |attrs| {
2942 push_modifiers(&modifier, Mods::Standard, attrs);
2943 attrs.push(("color", super::paint(color)));
2944 attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
2945 },
2946 Vec::new(),
2947 content,
2948 );
2949 }
2950}
2951
2952container!(TabsScope);
2953
2954pub struct TopAppBarScope {
2956 modifier: Modifier,
2957 title: String,
2958 done: bool,
2959}
2960
2961pub fn TopAppBar() -> TopAppBarScope {
2964 TopAppBarScope {
2965 modifier: Modifier::new(),
2966 title: String::new(),
2967 done: false,
2968 }
2969}
2970
2971impl TopAppBarScope {
2972 pub fn title(mut self, title: impl Into<String>) -> Self {
2973 self.title = title.into();
2974 self
2975 }
2976
2977 fn compose(&mut self, content: impl FnOnce()) {
2978 let modifier = self.modifier;
2979 let title = std::mem::take(&mut self.title);
2980 emit(
2981 widget_key("compose_rust::ui::TopAppBar"),
2982 WidgetKind::TopAppBar,
2983 |attrs| {
2984 push_modifiers(&modifier, Mods::Standard, attrs);
2985 attrs.push(("text", OwnedAttr::Text(title)));
2986 },
2987 Vec::new(),
2988 content,
2989 );
2990 }
2991}
2992
2993container!(TopAppBarScope);
2994
2995pub struct TooltipScope {
2997 modifier: Modifier,
2998 text: String,
2999 done: bool,
3000}
3001
3002pub fn Tooltip(text: impl Into<String>) -> TooltipScope {
3005 TooltipScope {
3006 modifier: Modifier::new(),
3007 text: text.into(),
3008 done: false,
3009 }
3010}
3011
3012impl TooltipScope {
3013 fn compose(&mut self, content: impl FnOnce()) {
3014 let modifier = self.modifier;
3015 let text = std::mem::take(&mut self.text);
3016 emit(
3017 widget_key("compose_rust::ui::Tooltip"),
3018 WidgetKind::Tooltip,
3019 |attrs| {
3020 push_modifiers(&modifier, Mods::Standard, attrs);
3021 attrs.push(("text", OwnedAttr::Text(text)));
3022 },
3023 Vec::new(),
3024 content,
3025 );
3026 }
3027}
3028
3029container!(TooltipScope);
3030
3031fn slot(role: SlotRole, key: u64, content: impl FnOnce()) {
3036 emit(
3037 key,
3038 WidgetKind::ScaffoldSlot,
3039 |attrs| attrs.push(("slot", OwnedAttr::Int(i64::from(u16::from(role))))),
3040 Vec::new(),
3041 content,
3042 );
3043}
3044
3045type Slot<'a> = Option<Box<dyn FnOnce() + 'a>>;
3046
3047pub struct ScaffoldScope<'a> {
3049 modifier: Modifier,
3050 top_bar: Slot<'a>,
3051 bottom_bar: Slot<'a>,
3052 floating_action: Slot<'a>,
3053 done: bool,
3054}
3055
3056pub fn Scaffold<'a>() -> ScaffoldScope<'a> {
3061 ScaffoldScope {
3062 modifier: Modifier::new(),
3063 top_bar: None,
3064 bottom_bar: None,
3065 floating_action: None,
3066 done: false,
3067 }
3068}
3069
3070impl<'a> ScaffoldScope<'a> {
3071 pub fn top_bar(mut self, bar: impl FnOnce() + 'a) -> Self {
3072 self.top_bar = Some(std::boxed::Box::new(bar));
3073 self
3074 }
3075
3076 pub fn bottom_bar(mut self, bar: impl FnOnce() + 'a) -> Self {
3077 self.bottom_bar = Some(std::boxed::Box::new(bar));
3078 self
3079 }
3080
3081 pub fn floating_action(mut self, action: impl FnOnce() + 'a) -> Self {
3082 self.floating_action = Some(std::boxed::Box::new(action));
3083 self
3084 }
3085
3086 fn compose(&mut self, content: impl FnOnce()) {
3087 let modifier = self.modifier;
3088 let (top_bar, bottom_bar, floating_action) = (
3089 self.top_bar.take(),
3090 self.bottom_bar.take(),
3091 self.floating_action.take(),
3092 );
3093 emit(
3094 widget_key("compose_rust::ui::Scaffold"),
3095 WidgetKind::Scaffold,
3096 |attrs| push_modifiers(&modifier, Mods::Scaffold, attrs),
3097 Vec::new(),
3098 || {
3099 if let Some(bar) = top_bar {
3100 slot(
3101 SlotRole::TopBar,
3102 widget_key("compose_rust::ui::Scaffold::top_bar"),
3103 bar,
3104 );
3105 }
3106 if let Some(bar) = bottom_bar {
3107 slot(
3108 SlotRole::BottomBar,
3109 widget_key("compose_rust::ui::Scaffold::bottom_bar"),
3110 bar,
3111 );
3112 }
3113 if let Some(action) = floating_action {
3114 slot(
3115 SlotRole::FloatingAction,
3116 widget_key("compose_rust::ui::Scaffold::floating_action"),
3117 action,
3118 );
3119 }
3120 slot(
3121 SlotRole::Content,
3122 widget_key("compose_rust::ui::Scaffold::content"),
3123 content,
3124 );
3125 },
3126 );
3127 }
3128}
3129
3130container!(ScaffoldScope<'a>);
3131
3132pub struct NavigationScope<'a> {
3134 modifier: Modifier,
3135 selected_index: usize,
3136 compact: CompactNavigation,
3137 head: Slot<'a>,
3138 foot: Slot<'a>,
3139 done: bool,
3140}
3141
3142pub fn Navigation<'a>(selected_index: usize) -> NavigationScope<'a> {
3146 NavigationScope {
3147 modifier: Modifier::new(),
3148 selected_index,
3149 compact: CompactNavigation::Default,
3150 head: None,
3151 foot: None,
3152 done: false,
3153 }
3154}
3155
3156impl<'a> NavigationScope<'a> {
3157 pub fn compact(mut self, compact: CompactNavigation) -> Self {
3166 self.compact = compact;
3167 self
3168 }
3169
3170 pub fn head(mut self, head: impl FnOnce() + 'a) -> Self {
3172 self.head = Some(std::boxed::Box::new(head));
3173 self
3174 }
3175
3176 pub fn foot(mut self, foot: impl FnOnce() + 'a) -> Self {
3178 self.foot = Some(std::boxed::Box::new(foot));
3179 self
3180 }
3181
3182 fn compose(&mut self, content: impl FnOnce()) {
3183 let modifier = self.modifier;
3184 let selected_index = self.selected_index;
3185 let compact = self.compact;
3186 let (head, foot) = (self.head.take(), self.foot.take());
3187 emit(
3188 widget_key("compose_rust::ui::Navigation"),
3189 WidgetKind::Navigation,
3190 |attrs| {
3191 push_modifiers(&modifier, Mods::Standard, attrs);
3192 attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
3193 if compact != CompactNavigation::Default {
3196 attrs.push((
3197 "compact_navigation",
3198 OwnedAttr::Int(i64::from(u16::from(compact))),
3199 ));
3200 }
3201 },
3202 Vec::new(),
3203 || {
3204 if let Some(head) = head {
3205 slot(
3206 SlotRole::TopBar,
3207 widget_key("compose_rust::ui::Navigation::head"),
3208 head,
3209 );
3210 }
3211 if let Some(foot) = foot {
3212 slot(
3213 SlotRole::BottomBar,
3214 widget_key("compose_rust::ui::Navigation::foot"),
3215 foot,
3216 );
3217 }
3218 let _destinations = crate::runtime::group_guard(widget_key(
3219 "compose_rust::ui::Navigation::content",
3220 ));
3221 content();
3222 },
3223 );
3224 }
3225}
3226
3227container!(NavigationScope<'a>);
3228
3229pub struct NavigationItemScope {
3231 modifier: Modifier,
3232 text: String,
3233 icon: Option<IconRole>,
3234 color: Option<Paint>,
3235 enabled: bool,
3236 section: Option<String>,
3237 on_click: Option<Box<dyn FnMut()>>,
3238 done: bool,
3239}
3240
3241pub fn NavigationItem(text: impl Into<String>) -> NavigationItemScope {
3243 NavigationItemScope {
3244 modifier: Modifier::new(),
3245 text: text.into(),
3246 icon: None,
3247 color: None,
3248 enabled: true,
3249 section: None,
3250 on_click: None,
3251 done: false,
3252 }
3253}
3254
3255impl NavigationItemScope {
3256 pub fn icon(mut self, icon: IconRole) -> Self {
3257 self.icon = Some(icon);
3258 self
3259 }
3260
3261 pub fn color(mut self, paint: Paint) -> Self {
3262 self.color = Some(paint);
3263 self
3264 }
3265
3266 pub fn enabled(mut self, enabled: bool) -> Self {
3267 self.enabled = enabled;
3268 self
3269 }
3270
3271 pub fn section(mut self, section: impl Into<String>) -> Self {
3273 self.section = Some(section.into());
3274 self
3275 }
3276
3277 pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
3278 self.on_click = Some(std::boxed::Box::new(handler));
3279 self
3280 }
3281
3282 fn compose(&mut self, content: impl FnOnce()) {
3283 let modifier = self.modifier;
3284 let text = std::mem::take(&mut self.text);
3285 let section = self.section.take();
3286 let (icon, color, enabled) = (self.icon, self.color, self.enabled);
3287 emit(
3288 widget_key("compose_rust::ui::NavigationItem"),
3289 WidgetKind::NavigationItem,
3290 |attrs| {
3291 push_modifiers(&modifier, Mods::Standard, attrs);
3292 attrs.push(("text", OwnedAttr::Text(text)));
3293 attrs.push(("icon", super::role(icon)));
3294 attrs.push(("color", super::paint(color)));
3295 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
3296 attrs.push(("section", optional_text(section)));
3297 },
3298 vec![("onclick", unit(self.on_click.take()))],
3299 content,
3300 );
3301 }
3302}
3303
3304composed_on_drop!(NavigationItemScope);
3305
3306pub struct FileDropTargetScope {
3310 modifier: Modifier,
3311 alignment: Option<Alignment>,
3312 on_files_entered: Option<Box<dyn FnMut()>>,
3313 on_files_dropped: Option<Box<dyn FnMut(FileDrop)>>,
3314 done: bool,
3315}
3316
3317pub fn FileDropTarget() -> FileDropTargetScope {
3319 FileDropTargetScope {
3320 modifier: Modifier::new(),
3321 alignment: None,
3322 on_files_entered: None,
3323 on_files_dropped: None,
3324 done: false,
3325 }
3326}
3327
3328impl FileDropTargetScope {
3329 pub fn alignment(mut self, alignment: Alignment) -> Self {
3330 self.alignment = Some(alignment);
3331 self
3332 }
3333
3334 pub fn on_files_entered(mut self, handler: impl FnMut() + 'static) -> Self {
3335 self.on_files_entered = Some(std::boxed::Box::new(handler));
3336 self
3337 }
3338
3339 pub fn on_files_dropped(mut self, handler: impl FnMut(FileDrop) + 'static) -> Self {
3340 self.on_files_dropped = Some(std::boxed::Box::new(handler));
3341 self
3342 }
3343
3344 fn compose(&mut self, content: impl FnOnce()) {
3345 let modifier = self.modifier;
3346 let alignment = self.alignment;
3347 let dropped: EventCallback = match self.on_files_dropped.take() {
3348 Some(handler) => EventCallback::Files(Rc::new(RefCell::new(handler))),
3349 None => EventCallback::Files(Rc::new(RefCell::new(|_: FileDrop| {}))),
3350 };
3351 emit(
3352 widget_key("compose_rust::ui::FileDropTarget"),
3353 WidgetKind::FileDropTarget,
3354 |attrs| {
3355 push_modifiers(&modifier, Mods::Standard, attrs);
3356 attrs.push(("alignment", super::role(alignment)));
3357 },
3358 vec![
3359 ("onfilesentered", unit(self.on_files_entered.take())),
3360 ("onfilesdropped", dropped),
3361 ],
3362 content,
3363 );
3364 }
3365}
3366
3367container!(FileDropTargetScope);
3368
3369pub struct ChipScope {
3371 modifier: Modifier,
3372 text: String,
3373 icon: Option<IconRole>,
3374 selected: bool,
3375 enabled: bool,
3376 max_lines: Option<u32>,
3377 overflow: Option<TextOverflow>,
3378 on_click: Option<Box<dyn FnMut()>>,
3379 done: bool,
3380}
3381
3382pub fn Chip(text: impl Into<String>) -> ChipScope {
3384 ChipScope {
3385 modifier: Modifier::new(),
3386 text: text.into(),
3387 icon: None,
3388 selected: false,
3389 enabled: true,
3390 max_lines: None,
3391 overflow: None,
3392 on_click: None,
3393 done: false,
3394 }
3395}
3396
3397impl ChipScope {
3398 pub fn icon(mut self, icon: IconRole) -> Self {
3399 self.icon = Some(icon);
3400 self
3401 }
3402
3403 pub fn selected(mut self, selected: bool) -> Self {
3404 self.selected = selected;
3405 self
3406 }
3407
3408 pub fn enabled(mut self, enabled: bool) -> Self {
3409 self.enabled = enabled;
3410 self
3411 }
3412
3413 pub fn max_lines(mut self, lines: u32) -> Self {
3416 self.max_lines = Some(lines);
3417 self
3418 }
3419
3420 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
3422 self.overflow = Some(overflow);
3423 self
3424 }
3425
3426 pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
3427 self.on_click = Some(std::boxed::Box::new(handler));
3428 self
3429 }
3430
3431 fn compose(&mut self, content: impl FnOnce()) {
3432 let modifier = self.modifier;
3433 let text = std::mem::take(&mut self.text);
3434 let (max_lines, overflow) = (self.max_lines, self.overflow);
3435 let (icon, selected, enabled) = (self.icon, self.selected, self.enabled);
3436 emit(
3437 widget_key("compose_rust::ui::Chip"),
3438 WidgetKind::Chip,
3439 |attrs| {
3440 push_modifiers(&modifier, Mods::Chip, attrs);
3441 attrs.push(("text", OwnedAttr::Text(text)));
3442 attrs.push(("icon", super::role(icon)));
3443 attrs.push(("checked", OwnedAttr::Bool(selected)));
3444 attrs.push(("enabled", OwnedAttr::Bool(enabled)));
3445 lines_and_overflow(max_lines, overflow, attrs);
3446 },
3447 vec![("onclick", unit(self.on_click.take()))],
3448 content,
3449 );
3450 }
3451}
3452
3453composed_on_drop!(ChipScope);
3454
3455pub struct FloatingActionScope {
3457 modifier: Modifier,
3458 text: String,
3459 icon: Option<IconRole>,
3460 on_click: Option<Box<dyn FnMut()>>,
3461 done: bool,
3462}
3463
3464pub fn FloatingAction(text: impl Into<String>) -> FloatingActionScope {
3467 FloatingActionScope {
3468 modifier: Modifier::new(),
3469 text: text.into(),
3470 icon: None,
3471 on_click: None,
3472 done: false,
3473 }
3474}
3475
3476impl FloatingActionScope {
3477 pub fn icon(mut self, icon: IconRole) -> Self {
3478 self.icon = Some(icon);
3479 self
3480 }
3481
3482 pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
3483 self.on_click = Some(std::boxed::Box::new(handler));
3484 self
3485 }
3486
3487 fn compose(&mut self, content: impl FnOnce()) {
3488 let modifier = self.modifier;
3489 let text = std::mem::take(&mut self.text);
3490 let icon = self.icon;
3491 emit(
3492 widget_key("compose_rust::ui::FloatingAction"),
3493 WidgetKind::FloatingAction,
3494 |attrs| {
3495 push_modifiers(&modifier, Mods::None, attrs);
3496 attrs.push(("text", OwnedAttr::Text(text)));
3497 attrs.push(("icon", super::role(icon)));
3498 },
3499 vec![("onclick", unit(self.on_click.take()))],
3500 content,
3501 );
3502 }
3503}
3504
3505composed_on_drop!(FloatingActionScope);
3506
3507pub struct BadgeScope {
3509 modifier: Modifier,
3510 value: Option<u32>,
3511 text: Option<String>,
3512 color: Option<ColorRole>,
3513 done: bool,
3514}
3515
3516pub fn Badge() -> BadgeScope {
3518 BadgeScope {
3519 modifier: Modifier::new(),
3520 value: None,
3521 text: None,
3522 color: None,
3523 done: false,
3524 }
3525}
3526
3527impl BadgeScope {
3528 pub fn value(mut self, value: u32) -> Self {
3530 self.value = Some(value);
3531 self
3532 }
3533
3534 pub fn text(mut self, text: impl Into<String>) -> Self {
3536 self.text = Some(text.into());
3537 self
3538 }
3539
3540 pub fn color(mut self, role: ColorRole) -> Self {
3542 self.color = Some(role);
3543 self
3544 }
3545
3546 fn compose(&mut self, content: impl FnOnce()) {
3547 let modifier = self.modifier;
3548 let text = self.text.take();
3549 let (value, color) = (self.value, self.color);
3550 emit(
3551 widget_key("compose_rust::ui::Badge"),
3552 WidgetKind::Badge,
3553 |attrs| {
3554 push_modifiers(&modifier, Mods::Standard, attrs);
3555 attrs.push((
3556 "count",
3557 value.map_or(OwnedAttr::None, |value| OwnedAttr::Int(i64::from(value))),
3558 ));
3559 attrs.push(("text", optional_text(text)));
3560 attrs.push(("color", super::paint(color.map(Paint::Role))));
3561 },
3562 Vec::new(),
3563 content,
3564 );
3565 }
3566}
3567
3568container!(BadgeScope);
3569
3570pub struct SplitPaneScope {
3572 modifier: Modifier,
3573 value: Option<f32>,
3574 min: Option<f32>,
3575 max: Option<f32>,
3576 collapsible: bool,
3577 collapse_below: Option<f32>,
3578 selected_index: usize,
3579 label: Option<String>,
3580 on_change: Option<Box<dyn FnMut(f32)>>,
3581 on_dismiss: Option<Box<dyn FnMut()>>,
3582 done: bool,
3583}
3584
3585pub fn SplitPane() -> SplitPaneScope {
3588 SplitPaneScope {
3589 modifier: Modifier::new(),
3590 value: None,
3591 min: None,
3592 max: None,
3593 collapsible: false,
3594 collapse_below: None,
3595 selected_index: 0,
3596 label: None,
3597 on_change: None,
3598 on_dismiss: None,
3599 done: false,
3600 }
3601}
3602
3603impl SplitPaneScope {
3604 pub fn value(mut self, dp: f32) -> Self {
3606 self.value = Some(dp);
3607 self
3608 }
3609
3610 pub fn min(mut self, dp: f32) -> Self {
3611 self.min = Some(dp);
3612 self
3613 }
3614
3615 pub fn max(mut self, dp: f32) -> Self {
3616 self.max = Some(dp);
3617 self
3618 }
3619
3620 pub fn collapsible(mut self, collapsible: bool) -> Self {
3621 self.collapsible = collapsible;
3622 self
3623 }
3624
3625 pub fn collapse_below(mut self, dp: f32) -> Self {
3634 self.collapse_below = Some(dp);
3635 self
3636 }
3637
3638 pub fn selected_index(mut self, index: usize) -> Self {
3640 self.selected_index = index;
3641 self
3642 }
3643
3644 pub fn label(mut self, label: impl Into<String>) -> Self {
3646 self.label = Some(label.into());
3647 self
3648 }
3649
3650 pub fn on_change(mut self, handler: impl FnMut(f32) + 'static) -> Self {
3651 self.on_change = Some(std::boxed::Box::new(handler));
3652 self
3653 }
3654
3655 pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
3656 self.on_dismiss = Some(std::boxed::Box::new(handler));
3657 self
3658 }
3659
3660 fn compose(&mut self, content: impl FnOnce()) {
3661 let modifier = self.modifier;
3662 let label = self.label.take();
3663 let (value, min, max, collapsible, collapse_below, selected_index) = (
3664 self.value,
3665 self.min,
3666 self.max,
3667 self.collapsible,
3668 self.collapse_below,
3669 self.selected_index,
3670 );
3671 let change = match self.on_change.take() {
3672 Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
3673 handler(value as f32)
3674 }))),
3675 None => value_handler(None),
3676 };
3677 let as_float = |value: Option<f32>| {
3678 value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
3679 };
3680 emit(
3681 widget_key("compose_rust::ui::SplitPane"),
3682 WidgetKind::SplitPane,
3683 |attrs| {
3684 push_modifiers(&modifier, Mods::Standard, attrs);
3685 attrs.push(("value", as_float(value)));
3686 attrs.push(("min", as_float(min)));
3687 attrs.push(("max", as_float(max)));
3688 attrs.push(("collapsible", OwnedAttr::Bool(collapsible)));
3689 attrs.push(("collapse_below", as_float(collapse_below)));
3690 attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
3691 attrs.push(("text", optional_text(label)));
3692 },
3693 vec![
3694 ("onchange", change),
3695 ("ondismiss", unit(self.on_dismiss.take())),
3696 ],
3697 content,
3698 );
3699 }
3700}
3701
3702container!(SplitPaneScope);
3703
3704pub struct LinearProgressIndicatorScope {
3706 modifier: Modifier,
3707 progress: f32,
3708 done: bool,
3709}
3710
3711pub fn LinearProgressIndicator(progress: f32) -> LinearProgressIndicatorScope {
3713 LinearProgressIndicatorScope {
3714 modifier: Modifier::new(),
3715 progress,
3716 done: false,
3717 }
3718}
3719
3720impl LinearProgressIndicatorScope {
3721 fn compose(&mut self, content: impl FnOnce()) {
3722 let modifier = self.modifier;
3723 let progress = self.progress;
3724 emit(
3725 widget_key("compose_rust::ui::LinearProgressIndicator"),
3726 WidgetKind::LinearProgressIndicator,
3727 |attrs| {
3728 push_modifiers(&modifier, Mods::None, attrs);
3729 attrs.push(("progress", OwnedAttr::Float(f64::from(progress))));
3730 },
3731 Vec::new(),
3732 content,
3733 );
3734 }
3735}
3736
3737composed_on_drop!(LinearProgressIndicatorScope);
3738
3739pub struct WindowScope {
3743 on_close_request: Option<Box<dyn FnMut()>>,
3744 title: Option<String>,
3745 size: Option<(f32, f32)>,
3746 min_size: Option<(f32, f32)>,
3747 resizable: Option<bool>,
3748 report_size: Option<bool>,
3749 chrome: Option<crate::schema::Chrome>,
3750 title_bar: Option<crate::schema::TitleBar>,
3751 icon: Option<u32>,
3752 position: Option<(f32, f32)>,
3753 restore_key: Option<String>,
3754 zoom_level: Option<i8>,
3755 drag_from_pointer: Option<crate::windows::DragFromPointer>,
3756 theme: Option<crate::schema::Theme>,
3757 on_focus_changed: Option<Box<dyn FnMut(bool)>>,
3758 on_moved: Option<Box<dyn FnMut(crate::windows::WindowFrame)>>,
3759 on_drag: Option<Box<dyn FnMut(crate::windows::WindowDragEvent)>>,
3760 on_unavailable: Option<Box<dyn FnMut()>>,
3761 on_closed: Option<Box<dyn FnMut()>>,
3762 done: bool,
3763}
3764
3765pub fn Window(on_close_request: impl FnMut() + 'static) -> WindowScope {
3779 WindowScope {
3780 on_close_request: Some(std::boxed::Box::new(on_close_request)),
3781 title: None,
3782 size: None,
3783 min_size: None,
3784 resizable: None,
3785 report_size: None,
3786 chrome: None,
3787 title_bar: None,
3788 icon: None,
3789 position: None,
3790 restore_key: None,
3791 zoom_level: None,
3792 drag_from_pointer: None,
3793 theme: None,
3794 on_focus_changed: None,
3795 on_moved: None,
3796 on_drag: None,
3797 on_unavailable: None,
3798 on_closed: None,
3799 done: false,
3800 }
3801}
3802
3803impl Drop for WindowScope {
3804 fn drop(&mut self) {
3805 if !self.done && !std::thread::panicking() {
3808 self.done = true;
3809 self.compose(|| {});
3810 }
3811 }
3812}
3813
3814fn window_callback(handler: impl FnMut(crate::windows::WindowEvent) + 'static) -> EventCallback {
3816 EventCallback::Window(Rc::new(RefCell::new(handler)))
3817}
3818
3819impl WindowScope {
3820 pub fn title(mut self, title: impl Into<String>) -> Self {
3822 self.title = Some(title.into());
3823 self
3824 }
3825
3826 pub fn size(mut self, width: f32, height: f32) -> Self {
3828 self.size = Some((width, height));
3829 self
3830 }
3831
3832 pub fn min_size(mut self, width: f32, height: f32) -> Self {
3834 self.min_size = Some((width, height));
3835 self
3836 }
3837
3838 pub fn resizable(mut self, resizable: bool) -> Self {
3839 self.resizable = Some(resizable);
3840 self
3841 }
3842
3843 pub fn report_size(mut self, report: bool) -> Self {
3848 self.report_size = Some(report);
3849 self
3850 }
3851
3852 pub fn chrome(mut self, chrome: crate::schema::Chrome) -> Self {
3854 self.chrome = Some(chrome);
3855 self
3856 }
3857
3858 pub fn title_bar(mut self, title_bar: crate::schema::TitleBar) -> Self {
3860 self.title_bar = Some(title_bar);
3861 self
3862 }
3863
3864 pub fn icon(mut self, asset_id: u32) -> Self {
3867 self.icon = Some(asset_id);
3868 self
3869 }
3870
3871 pub fn position(mut self, x: f32, y: f32) -> Self {
3875 self.position = Some((x, y));
3876 self
3877 }
3878
3879 pub fn restore_key(mut self, key: impl Into<String>) -> Self {
3882 self.restore_key = Some(key.into());
3883 self
3884 }
3885
3886 pub fn zoom_level(mut self, level: i8) -> Self {
3888 self.zoom_level = Some(crate::zoom::clamp_level(level));
3889 self
3890 }
3891
3892 pub fn drag_from_pointer(mut self, drag: crate::windows::DragFromPointer) -> Self {
3895 self.drag_from_pointer = Some(drag);
3896 self
3897 }
3898
3899 pub fn theme(mut self, theme: crate::schema::Theme) -> Self {
3901 self.theme = Some(theme);
3902 self
3903 }
3904
3905 pub fn on_focus_changed(mut self, handler: impl FnMut(bool) + 'static) -> Self {
3907 self.on_focus_changed = Some(std::boxed::Box::new(handler));
3908 self
3909 }
3910
3911 pub fn on_moved(mut self, handler: impl FnMut(crate::windows::WindowFrame) + 'static) -> Self {
3913 self.on_moved = Some(std::boxed::Box::new(handler));
3914 self
3915 }
3916
3917 pub fn on_drag(
3919 mut self,
3920 handler: impl FnMut(crate::windows::WindowDragEvent) + 'static,
3921 ) -> Self {
3922 self.on_drag = Some(std::boxed::Box::new(handler));
3923 self
3924 }
3925
3926 pub fn on_unavailable(mut self, handler: impl FnMut() + 'static) -> Self {
3928 self.on_unavailable = Some(std::boxed::Box::new(handler));
3929 self
3930 }
3931
3932 pub fn on_closed(mut self, handler: impl FnMut() + 'static) -> Self {
3934 self.on_closed = Some(std::boxed::Box::new(handler));
3935 self
3936 }
3937
3938 pub fn content(mut self, content: impl FnOnce()) {
3940 self.done = true;
3941 self.compose(content);
3942 }
3943
3944 fn compose(&mut self, content: impl FnOnce()) {
3945 use crate::windows::WindowEvent;
3946 let pair = |value: (f32, f32)| {
3947 let mut bytes = Vec::with_capacity(8);
3948 bytes.extend_from_slice(&value.0.to_bits().to_le_bytes());
3949 bytes.extend_from_slice(&value.1.to_bits().to_le_bytes());
3950 OwnedAttr::Bytes(bytes)
3951 };
3952 let mut properties: Attrs = Vec::new();
3955 if let Some(title) = self.title.take() {
3956 properties.push(("window_title", OwnedAttr::Text(title)));
3957 }
3958 if let Some(size) = self.size {
3959 properties.push(("window_size", pair(size)));
3960 }
3961 if let Some(size) = self.min_size {
3962 properties.push(("window_min_size", pair(size)));
3963 }
3964 if let Some(resizable) = self.resizable {
3965 properties.push(("window_resizable", OwnedAttr::Bool(resizable)));
3966 }
3967 if let Some(report) = self.report_size {
3968 properties.push(("report_size", OwnedAttr::Bool(report)));
3969 }
3970 if let Some(chrome) = self.chrome {
3971 properties.push(("window_chrome", OwnedAttr::Int(i64::from(chrome as u16))));
3972 }
3973 if let Some(title_bar) = self.title_bar {
3974 properties.push((
3975 "window_title_bar",
3976 OwnedAttr::Int(i64::from(title_bar as u16)),
3977 ));
3978 }
3979 if let Some(icon) = self.icon {
3980 properties.push(("window_icon", OwnedAttr::Int(i64::from(icon))));
3981 }
3982 if let Some(position) = self.position {
3983 properties.push(("window_position", pair(position)));
3984 }
3985 if let Some(key) = self.restore_key.take() {
3986 properties.push(("window_restore_key", OwnedAttr::Text(key)));
3987 }
3988 if let Some(level) = self.zoom_level {
3989 properties.push(("window_zoom_level", OwnedAttr::Int(i64::from(level))));
3990 }
3991 if let Some(drag) = self.drag_from_pointer {
3992 properties.push(("drag_from_pointer", OwnedAttr::Bytes(drag.to_bytes())));
3993 }
3994
3995 let mut listeners: Vec<(&'static str, EventCallback)> = Vec::new();
3996 let mut close = self.on_close_request.take();
3997 listeners.push((
3998 "onwindowclose",
3999 window_callback(move |event| {
4000 if let (WindowEvent::CloseRequested, Some(handler)) = (event, close.as_mut()) {
4001 handler();
4002 }
4003 }),
4004 ));
4005 if let Some(mut handler) = self.on_focus_changed.take() {
4006 listeners.push((
4007 "onwindowfocus",
4008 window_callback(move |event| {
4009 if let WindowEvent::FocusChanged { focused } = event {
4010 handler(focused);
4011 }
4012 }),
4013 ));
4014 }
4015 if let Some(mut handler) = self.on_moved.take() {
4016 listeners.push((
4017 "onwindowmoved",
4018 window_callback(move |event| {
4019 if let WindowEvent::Moved {
4020 x,
4021 y,
4022 width,
4023 height,
4024 } = event
4025 {
4026 handler(crate::windows::WindowFrame {
4027 x,
4028 y,
4029 width,
4030 height,
4031 });
4032 }
4033 }),
4034 ));
4035 }
4036 if let Some(mut handler) = self.on_drag.take() {
4037 listeners.push((
4038 "onwindowdrag",
4039 window_callback(move |event| {
4040 if let WindowEvent::Drag {
4041 phase,
4042 screen_x,
4043 screen_y,
4044 over_window,
4045 } = event
4046 {
4047 handler(crate::windows::WindowDragEvent {
4048 phase,
4049 screen_x,
4050 screen_y,
4051 over_window,
4052 });
4053 }
4054 }),
4055 ));
4056 }
4057 if let Some(mut handler) = self.on_unavailable.take() {
4058 listeners.push((
4059 "onwindowunavailable",
4060 window_callback(move |event| {
4061 if event == WindowEvent::Unavailable {
4062 handler();
4063 }
4064 }),
4065 ));
4066 }
4067 if let Some(mut handler) = self.on_closed.take() {
4068 listeners.push((
4069 "onwindowclosed",
4070 window_callback(move |event| {
4071 if event == WindowEvent::Closed {
4072 handler();
4073 }
4074 }),
4075 ));
4076 }
4077
4078 let theme = self.theme.take();
4079 emit(
4080 widget_key("compose_rust::ui::Window"),
4081 WidgetKind::Window,
4082 |attrs| attrs.append(&mut properties),
4083 listeners,
4084 move || {
4085 if let (Some(theme), Some(window)) = (theme, crate::windows::current_window()) {
4087 window.set_theme(theme);
4088 }
4089 content();
4090 },
4091 );
4092 }
4093}