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