1use std::any::Any;
2use std::fmt;
3use std::rc::Rc;
4use std::sync::Arc;
5use std::time::Duration;
6
7use super::*;
8use crate::core::{ComponentToken, ComponentView, ContextProvision};
9
10pub(crate) fn validate_image_uri(value: &str) -> windows_core::Result<()> {
11 native::validate_native_image_uri(value)
12}
13
14pub(crate) fn validate_uri(value: &str) -> windows_core::Result<()> {
15 native::validate_native_uri(value)
16}
17
18#[derive(Clone)]
20pub struct EncodedImage(EncodedImageBytes);
21
22#[derive(Clone)]
23enum EncodedImageBytes {
24 Static(&'static [u8]),
25 Shared(Arc<[u8]>),
26}
27
28impl EncodedImage {
29 pub fn new(bytes: impl Into<Arc<[u8]>>) -> Self {
35 let bytes = bytes.into();
36 assert!(
37 bytes.len() <= u32::MAX as usize,
38 "encoded image data cannot exceed 4 GiB"
39 );
40 Self(EncodedImageBytes::Shared(bytes))
41 }
42
43 pub fn from_static(bytes: &'static [u8]) -> Self {
49 assert!(
50 bytes.len() <= u32::MAX as usize,
51 "encoded image data cannot exceed 4 GiB"
52 );
53 Self(EncodedImageBytes::Static(bytes))
54 }
55
56 pub fn as_bytes(&self) -> &[u8] {
58 match &self.0 {
59 EncodedImageBytes::Static(bytes) => bytes,
60 EncodedImageBytes::Shared(bytes) => bytes,
61 }
62 }
63}
64
65impl fmt::Debug for EncodedImage {
66 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67 formatter
68 .debug_struct("EncodedImage")
69 .field("len", &self.as_bytes().len())
70 .finish()
71 }
72}
73
74impl PartialEq for EncodedImage {
75 fn eq(&self, other: &Self) -> bool {
76 match (&self.0, &other.0) {
77 (EncodedImageBytes::Static(left), EncodedImageBytes::Static(right))
78 if std::ptr::eq(*left, *right) =>
79 {
80 true
81 }
82 (EncodedImageBytes::Shared(left), EncodedImageBytes::Shared(right))
83 if Arc::ptr_eq(left, right) =>
84 {
85 true
86 }
87 _ => self.as_bytes() == other.as_bytes(),
88 }
89 }
90}
91
92#[derive(Clone, Debug, PartialEq)]
93pub(crate) enum ImageValue {
94 Uri(String),
95 Encoded(EncodedImage),
96}
97
98pub(crate) fn file_uri(path: &std::path::Path) -> windows_core::Result<String> {
99 if !path.is_absolute() {
100 return Err(windows_core::Error::new(
101 windows_core::HRESULT(0x80070057_u32 as _),
102 "Image::source_file requires an absolute path",
103 ));
104 }
105 let path = path.to_string_lossy();
106 let path = if let Some(path) = path.strip_prefix(r"\\?\UNC\") {
107 format!(r"\\{path}")
108 } else {
109 path.strip_prefix(r"\\?\").unwrap_or(&path).to_string()
110 };
111 let path = path.replace('\\', "/");
112 let mut encoded = String::with_capacity(path.len());
113 const HEX: &[u8; 16] = b"0123456789ABCDEF";
114 for byte in path.bytes() {
115 if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'/' | b':') {
116 encoded.push(char::from(byte));
117 } else {
118 encoded.push('%');
119 encoded.push(char::from(HEX[usize::from(byte >> 4)]));
120 encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
121 }
122 }
123 if let Some(path) = encoded.strip_prefix("//") {
124 Ok(format!("file://{path}"))
125 } else if encoded.starts_with('/') {
126 Ok(format!("file://{encoded}"))
127 } else {
128 Ok(format!("file:///{encoded}"))
129 }
130}
131
132#[cfg(test)]
133mod file_uri_tests {
134 use super::*;
135
136 #[test]
137 fn encodes_drive_paths_and_reserved_characters() {
138 assert_eq!(
139 file_uri(std::path::Path::new(r"\\?\C:\work dir\a#b%20.png")).unwrap(),
140 "file:///C:/work%20dir/a%23b%2520.png"
141 );
142 }
143
144 #[test]
145 fn encodes_extended_unc_paths_with_an_authority() {
146 assert_eq!(
147 file_uri(std::path::Path::new(r"\\?\UNC\server\share dir\asset.png")).unwrap(),
148 "file://server/share%20dir/asset.png"
149 );
150 }
151
152 #[test]
153 fn rejects_relative_paths() {
154 assert!(file_uri(std::path::Path::new(r"images\asset.png")).is_err());
155 }
156}
157
158#[repr(u8)]
160#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
161pub enum ThemeBrush {
162 Accent,
163 AccentText,
164 PrimaryText,
165 SolidBackground,
166 CardBackground,
167 CardStroke,
168 SystemCritical,
169 SystemCriticalBackground,
170}
171
172#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
174pub struct FontWeight(u16);
175
176impl FontWeight {
177 pub const BLACK: Self = Self(900);
178 pub const BOLD: Self = Self(700);
179 pub const EXTRA_BLACK: Self = Self(950);
180 pub const EXTRA_BOLD: Self = Self(800);
181 pub const EXTRA_LIGHT: Self = Self(200);
182 pub const LIGHT: Self = Self(300);
183 pub const MEDIUM: Self = Self(500);
184 pub const NORMAL: Self = Self(400);
185 pub const SEMI_BOLD: Self = Self(600);
186 pub const SEMI_LIGHT: Self = Self(350);
187 pub const THIN: Self = Self(100);
188
189 pub const fn new(weight: u16) -> Option<Self> {
191 if weight >= 1 && weight <= 999 {
192 Some(Self(weight))
193 } else {
194 None
195 }
196 }
197
198 pub const fn get(self) -> u16 {
200 self.0
201 }
202}
203
204impl Default for FontWeight {
205 fn default() -> Self {
206 Self::NORMAL
207 }
208}
209
210#[cfg(test)]
211mod font_weight_tests {
212 use super::*;
213
214 #[test]
215 fn validates_open_type_weight_range() {
216 assert_eq!(FontWeight::new(0), None);
217 assert_eq!(FontWeight::new(1).unwrap().get(), 1);
218 assert_eq!(FontWeight::new(999).unwrap().get(), 999);
219 assert_eq!(FontWeight::new(1000), None);
220 assert_eq!(FontWeight::default(), FontWeight::NORMAL);
221 }
222}
223
224impl ThemeBrush {
225 pub(crate) fn resource_key(self) -> &'static str {
226 match self {
227 Self::Accent => "AccentFillColorDefaultBrush",
228 Self::AccentText => "AccentTextFillColorPrimaryBrush",
229 Self::PrimaryText => "TextFillColorPrimaryBrush",
230 Self::SolidBackground => "SolidBackgroundFillColorBaseBrush",
231 Self::CardBackground => "CardBackgroundFillColorDefaultBrush",
232 Self::CardStroke => "CardStrokeColorDefaultBrush",
233 Self::SystemCritical => "SystemFillColorCriticalBrush",
234 Self::SystemCriticalBackground => "SystemFillColorCriticalBackgroundBrush",
235 }
236 }
237}
238
239#[repr(C)]
241#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
242pub struct Color {
243 pub a: u8,
244 pub r: u8,
245 pub g: u8,
246 pub b: u8,
247}
248
249impl Color {
250 pub const fn argb(a: u8, r: u8, g: u8, b: u8) -> Self {
252 Self { a, r, g, b }
253 }
254
255 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
257 Self::argb(255, r, g, b)
258 }
259
260 pub const fn transparent() -> Self {
262 Self::argb(0, 0, 0, 0)
263 }
264}
265
266#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
268pub enum Brush {
269 Theme(ThemeBrush),
270 Solid(Color),
271}
272
273#[derive(Clone, Copy, Debug, Default, PartialEq)]
275pub struct PointerEventInfo {
276 pub x: f64,
277 pub y: f64,
278 pub window_x: f64,
279 pub window_y: f64,
280 pub capture_succeeded: bool,
281 pub is_left_button_pressed: bool,
282 pub is_right_button_pressed: bool,
283 pub is_middle_button_pressed: bool,
284}
285
286#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
287pub enum NavigationViewDisplayMode {
288 Minimal,
289 Compact,
290 Expanded,
291}
292
293impl Brush {
294 pub(crate) const fn theme(self) -> Option<ThemeBrush> {
295 match self {
296 Self::Theme(value) => Some(value),
297 Self::Solid(_) => None,
298 }
299 }
300}
301
302impl From<ThemeBrush> for Brush {
303 fn from(value: ThemeBrush) -> Self {
304 Self::Theme(value)
305 }
306}
307
308impl From<Color> for Brush {
309 fn from(value: Color) -> Self {
310 Self::Solid(value)
311 }
312}
313
314pub(crate) mod sealed {
315 pub trait Sealed {}
316
317 pub(crate) trait NativeControl: Sealed + Sized {
318 fn into_element(self) -> super::Element;
319 }
320
321 pub(crate) trait LayoutControl: NativeControl {
322 fn element_state_mut(&mut self) -> &mut Option<std::rc::Rc<super::ElementState>>;
323 }
324
325 pub(crate) trait ContentControl: NativeControl {
326 fn into_content_view(self, content: super::View) -> super::View {
327 super::View(super::ViewKind::Content {
328 control: self.into_element(),
329 content: Box::new(content.into_kind()),
330 })
331 }
332 }
333
334 pub(crate) trait SlotIndex<S> {
335 fn slot_index(slot: S) -> u8;
336 }
337
338 pub trait StaticViews {
339 fn into_positioned(self) -> Vec<super::KeyedView>;
340 }
341}
342
343#[derive(Clone, Debug, Eq, Hash, PartialEq)]
345pub struct Key(KeyKind);
346
347#[derive(Clone, Debug, Eq, Hash, PartialEq)]
348enum KeyKind {
349 Integer(u64),
350 String(Rc<str>),
351 Position(usize),
352}
353
354impl Key {
355 pub(crate) fn position(value: usize) -> Self {
356 Self(KeyKind::Position(value))
357 }
358}
359
360impl From<u64> for Key {
361 fn from(value: u64) -> Self {
362 Self(KeyKind::Integer(value))
363 }
364}
365
366impl From<u32> for Key {
367 fn from(value: u32) -> Self {
368 Self(KeyKind::Integer(value.into()))
369 }
370}
371
372impl From<usize> for Key {
373 fn from(value: usize) -> Self {
374 Self(KeyKind::Integer(u64::try_from(value).unwrap()))
375 }
376}
377
378impl From<String> for Key {
379 fn from(value: String) -> Self {
380 Self(KeyKind::String(value.into()))
381 }
382}
383
384impl From<&str> for Key {
385 fn from(value: &str) -> Self {
386 Self(KeyKind::String(value.into()))
387 }
388}
389
390#[derive(Clone, Debug, PartialEq)]
391pub(crate) struct KeyedElement {
392 key: Key,
393 element: Element,
394}
395
396impl KeyedElement {
397 #[cfg(test)]
398 pub(crate) fn new(key: impl Into<Key>, element: impl Into<Element>) -> Self {
399 Self {
400 key: key.into(),
401 element: element.into(),
402 }
403 }
404
405 pub(crate) fn key(&self) -> &Key {
406 &self.key
407 }
408
409 pub(crate) fn element(&self) -> &Element {
410 &self.element
411 }
412
413 pub(crate) fn into_parts(self) -> (Key, Element) {
414 (self.key, self.element)
415 }
416}
417
418#[cfg(test)]
419pub(crate) trait NativeContentTestExt: Sized {
420 fn native_content(self, content: impl Into<Element>) -> Self;
421}
422
423#[cfg(test)]
424pub(crate) trait NativeChildrenTestExt: Sized {
425 fn native_child(self, key: impl Into<Key>, child: impl Into<Element>) -> Self;
426 fn native_children(self, children: impl IntoIterator<Item = KeyedElement>) -> Self;
427}
428
429#[derive(Clone, Debug, PartialEq)]
434pub struct View(ViewKind);
435
436#[derive(Clone, Debug, PartialEq)]
437pub(crate) enum ViewKind {
438 Native(Element),
439 Component(ComponentView),
440 Fragment(Rc<Vec<KeyedView>>),
441 Provider {
442 provision: ContextProvision,
443 child: Box<Self>,
444 },
445 Content {
446 control: Element,
447 content: Box<Self>,
448 },
449 Children {
450 control: Element,
451 children: Rc<Vec<KeyedView>>,
452 },
453 Slots {
454 control: Element,
455 slots: Rc<Vec<SlottedView>>,
456 },
457 Tooltip {
458 target: Box<Self>,
459 tooltip: Tooltip,
460 },
461 Flyout {
462 target: Box<Self>,
463 flyout: Flyout,
464 },
465 Menu {
466 target: Box<Self>,
467 menu: Menu,
468 },
469 CommandBarFlyout {
470 target: Box<Self>,
471 flyout: CommandBarFlyout,
472 },
473 TreeNodes {
474 tree: Box<Self>,
475 nodes: Rc<Vec<TreeNode>>,
476 },
477 ContentDialog {
478 dialog: Box<Self>,
479 open: bool,
480 },
481}
482
483pub trait IntoViews: sealed::StaticViews {}
507
508#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
510pub enum TooltipPlacement {
511 #[default]
512 Top,
513 Bottom,
514 Left,
515 Right,
516 Mouse,
517}
518
519#[derive(Clone, Debug, PartialEq)]
521pub struct Tooltip {
522 pub(crate) content: Box<View>,
523 pub(crate) placement: TooltipPlacement,
524}
525
526impl Tooltip {
527 pub fn text(value: impl Into<String>) -> Self {
529 Self::rich(TextBlock::new().text(value))
530 }
531
532 pub fn rich(content: impl Into<View>) -> Self {
534 Self {
535 content: Box::new(content.into()),
536 placement: TooltipPlacement::Top,
537 }
538 }
539
540 pub fn placement(mut self, placement: TooltipPlacement) -> Self {
542 self.placement = placement;
543 self
544 }
545}
546
547pub trait TooltipExt: Into<View> + Sized {
549 fn tooltip(self, value: impl Into<String>) -> View {
550 self.tooltip_with(Tooltip::text(value))
551 }
552
553 fn tooltip_with(self, tooltip: Tooltip) -> View {
554 View(ViewKind::Tooltip {
555 target: Box::new(self.into().into_kind()),
556 tooltip,
557 })
558 }
559}
560
561impl<T> TooltipExt for T where T: Into<View> {}
562
563#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
565pub enum FlyoutPlacement {
566 #[default]
567 Top,
568 Bottom,
569 Left,
570 Right,
571 Full,
572 TopEdgeAlignedLeft,
573 TopEdgeAlignedRight,
574 BottomEdgeAlignedLeft,
575 BottomEdgeAlignedRight,
576 LeftEdgeAlignedTop,
577 LeftEdgeAlignedBottom,
578 RightEdgeAlignedTop,
579 RightEdgeAlignedBottom,
580 Auto,
581}
582
583#[derive(Clone, Debug, PartialEq)]
585pub struct Flyout {
586 pub(crate) content: Box<View>,
587 pub(crate) placement: FlyoutPlacement,
588}
589
590impl Flyout {
591 pub fn text(value: impl Into<String>) -> Self {
593 Self::rich(TextBlock::new().text(value))
594 }
595
596 pub fn rich(content: impl Into<View>) -> Self {
598 Self {
599 content: Box::new(content.into()),
600 placement: FlyoutPlacement::Top,
601 }
602 }
603
604 pub fn placement(mut self, placement: FlyoutPlacement) -> Self {
606 self.placement = placement;
607 self
608 }
609}
610
611pub trait FlyoutExt: Into<View> + Sized {
613 fn flyout(self, value: impl Into<String>) -> View {
614 self.flyout_with(Flyout::text(value))
615 }
616
617 fn flyout_with(self, flyout: Flyout) -> View {
618 View(ViewKind::Flyout {
619 target: Box::new(self.into().into_kind()),
620 flyout,
621 })
622 }
623}
624
625impl<T> FlyoutExt for T where T: Into<View> {}
626
627#[derive(Clone, Debug, PartialEq)]
629pub enum MenuItem {
630 Item {
631 key: Key,
632 label: String,
633 enabled: bool,
634 },
635 Separator {
636 key: Key,
637 },
638 Submenu {
639 key: Key,
640 label: String,
641 items: Vec<Self>,
642 },
643}
644
645impl MenuItem {
646 pub fn item(key: impl Into<Key>, label: impl Into<String>) -> Self {
648 Self::Item {
649 key: key.into(),
650 label: label.into(),
651 enabled: true,
652 }
653 }
654
655 pub fn disabled(key: impl Into<Key>, label: impl Into<String>) -> Self {
657 Self::Item {
658 key: key.into(),
659 label: label.into(),
660 enabled: false,
661 }
662 }
663
664 pub fn separator(key: impl Into<Key>) -> Self {
666 Self::Separator { key: key.into() }
667 }
668
669 pub fn submenu(
671 key: impl Into<Key>,
672 label: impl Into<String>,
673 items: impl IntoIterator<Item = Self>,
674 ) -> Self {
675 Self::Submenu {
676 key: key.into(),
677 label: label.into(),
678 items: items.into_iter().collect(),
679 }
680 }
681
682 pub(crate) fn key(&self) -> &Key {
683 match self {
684 Self::Item { key, .. } | Self::Separator { key } | Self::Submenu { key, .. } => key,
685 }
686 }
687}
688
689#[derive(Clone, Debug, PartialEq)]
691pub struct Menu {
692 pub(crate) items: Vec<MenuItem>,
693 pub(crate) on_click: Callback<String>,
694}
695
696impl Menu {
697 pub fn new(
699 items: impl IntoIterator<Item = MenuItem>,
700 on_click: impl IntoPayloadCallback<String>,
701 ) -> Self {
702 Self {
703 items: items.into_iter().collect(),
704 on_click: on_click.into_payload_callback(),
705 }
706 }
707}
708
709pub trait MenuExt: Into<View> + Sized {
711 fn menu(self, menu: Menu) -> View {
712 View(ViewKind::Menu {
713 target: Box::new(self.into().into_kind()),
714 menu,
715 })
716 }
717}
718
719impl<T> MenuExt for T where T: Into<View> {}
720
721#[derive(Clone, Debug, PartialEq)]
723pub enum CommandBarCommand {
724 Button {
725 key: Key,
726 label: String,
727 icon: Option<Symbol>,
728 enabled: bool,
729 },
730 Separator {
731 key: Key,
732 },
733}
734
735impl CommandBarCommand {
736 pub fn button(key: impl Into<Key>, label: impl Into<String>) -> Self {
737 Self::Button {
738 key: key.into(),
739 label: label.into(),
740 icon: None,
741 enabled: true,
742 }
743 }
744
745 pub fn button_with_icon(key: impl Into<Key>, label: impl Into<String>, icon: Symbol) -> Self {
746 Self::Button {
747 key: key.into(),
748 label: label.into(),
749 icon: Some(icon),
750 enabled: true,
751 }
752 }
753
754 pub fn separator(key: impl Into<Key>) -> Self {
755 Self::Separator { key: key.into() }
756 }
757
758 pub(crate) fn key(&self) -> &Key {
759 match self {
760 Self::Button { key, .. } | Self::Separator { key } => key,
761 }
762 }
763
764 fn into_keyed_view(self, on_click: &Callback<String>) -> KeyedView {
765 match self {
766 Self::Button {
767 key,
768 label,
769 icon,
770 enabled,
771 } => {
772 let callback = on_click.clone();
773 let clicked = label.clone();
774 let button = AppBarButton::new()
775 .label(label)
776 .is_enabled(enabled)
777 .on_click(move || {
778 let _ = callback.call(clicked.clone());
779 });
780 let view = match icon {
781 Some(icon) => button.slots([SlotView::new(
782 AppBarButtonSlot::Icon,
783 SymbolIcon::new().symbol(icon),
784 )]),
785 None => button.into(),
786 };
787 KeyedView { key, view }
788 }
789 Self::Separator { key } => KeyedView {
790 key,
791 view: AppBarSeparator::new().into(),
792 },
793 }
794 }
795}
796
797impl CommandBar {
798 pub fn owned_commands(
799 self,
800 primary: impl IntoIterator<Item = CommandBarCommand>,
801 secondary: impl IntoIterator<Item = CommandBarCommand>,
802 on_click: impl IntoPayloadCallback<String>,
803 ) -> View {
804 let on_click = on_click.into_payload_callback();
805 self.slots([
806 SlotView::collection(
807 CommandBarSlot::PrimaryCommands,
808 primary
809 .into_iter()
810 .map(|command| command.into_keyed_view(&on_click)),
811 ),
812 SlotView::collection(
813 CommandBarSlot::SecondaryCommands,
814 secondary
815 .into_iter()
816 .map(|command| command.into_keyed_view(&on_click)),
817 ),
818 ])
819 }
820}
821
822#[derive(Clone, Debug, PartialEq)]
824pub struct CommandBarFlyout {
825 pub(crate) primary: Vec<CommandBarCommand>,
826 pub(crate) secondary: Vec<CommandBarCommand>,
827 pub(crate) on_click: Callback<String>,
828}
829
830impl CommandBarFlyout {
831 pub fn new(
832 primary: impl IntoIterator<Item = CommandBarCommand>,
833 secondary: impl IntoIterator<Item = CommandBarCommand>,
834 on_click: impl IntoPayloadCallback<String>,
835 ) -> Self {
836 Self {
837 primary: primary.into_iter().collect(),
838 secondary: secondary.into_iter().collect(),
839 on_click: on_click.into_payload_callback(),
840 }
841 }
842}
843
844pub trait CommandBarFlyoutExt: Into<View> + Sized {
846 fn command_bar_flyout(self, flyout: CommandBarFlyout) -> View {
847 View(ViewKind::CommandBarFlyout {
848 target: Box::new(self.into().into_kind()),
849 flyout,
850 })
851 }
852}
853
854impl<T> CommandBarFlyoutExt for T where T: Into<View> {}
855
856#[derive(Clone, Debug, Default, Eq, PartialEq)]
858pub struct RichText {
859 pub(crate) paragraphs: Rc<Vec<RichTextParagraph>>,
860}
861
862impl RichText {
863 pub fn new(paragraphs: impl IntoIterator<Item = RichTextParagraph>) -> Self {
865 Self {
866 paragraphs: Rc::new(paragraphs.into_iter().collect()),
867 }
868 }
869
870 pub fn single_paragraph(inlines: impl IntoIterator<Item = RichTextInline>) -> Self {
872 Self::new([RichTextParagraph::new(inlines)])
873 }
874}
875
876#[derive(Clone, Debug, Default, Eq, PartialEq)]
878pub struct RichTextParagraph {
879 pub(crate) inlines: Vec<RichTextInline>,
880}
881
882impl RichTextParagraph {
883 pub fn new(inlines: impl IntoIterator<Item = RichTextInline>) -> Self {
885 Self {
886 inlines: inlines.into_iter().collect(),
887 }
888 }
889}
890
891#[derive(Clone, Debug, Eq, PartialEq)]
893pub enum RichTextInline {
894 Run(RichTextRun),
895 Hyperlink(RichTextHyperlink),
896 LineBreak,
897}
898
899#[derive(Clone, Debug, Default, Eq, PartialEq)]
901pub struct RichTextRun {
902 pub text: String,
903 pub is_bold: bool,
904 pub is_italic: bool,
905}
906
907impl RichTextRun {
908 pub fn plain(text: impl Into<String>) -> Self {
910 Self {
911 text: text.into(),
912 ..Self::default()
913 }
914 }
915}
916
917#[derive(Clone, Debug, Eq, PartialEq)]
919pub struct RichTextHyperlink {
920 pub text: String,
921 pub uri: String,
922}
923
924#[derive(Clone, Debug, Eq, PartialEq)]
926pub struct TreeNode {
927 pub(crate) key: Key,
928 pub(crate) text: String,
929 pub(crate) expanded: bool,
930 pub(crate) children: Vec<Self>,
931}
932
933impl TreeNode {
934 pub fn new(key: impl Into<Key>, text: impl Into<String>) -> Self {
936 Self {
937 key: key.into(),
938 text: text.into(),
939 expanded: false,
940 children: Vec::new(),
941 }
942 }
943
944 pub fn expanded(mut self, value: bool) -> Self {
946 self.expanded = value;
947 self
948 }
949
950 pub fn children(mut self, children: impl IntoIterator<Item = Self>) -> Self {
952 self.children = children.into_iter().collect();
953 self
954 }
955
956 pub fn child(mut self, child: Self) -> Self {
958 self.children.push(child);
959 self
960 }
961}
962
963pub trait TreeViewExt: Into<View> + Sized {
965 fn nodes(self, nodes: impl IntoIterator<Item = TreeNode>) -> View {
966 View(ViewKind::TreeNodes {
967 tree: Box::new(self.into().into_kind()),
968 nodes: Rc::new(nodes.into_iter().collect()),
969 })
970 }
971}
972
973impl<T> TreeViewExt for T where T: Into<View> {}
974
975impl View {
976 pub fn empty() -> Self {
978 Self::fragment(())
979 }
980
981 pub(crate) fn native(control: impl Into<Element>) -> Self {
982 Self(ViewKind::Native(control.into()))
983 }
984
985 pub fn component<C: Component>(input: C::Input) -> Self {
987 Self(ViewKind::Component(ComponentView::new::<C>(input)))
988 }
989
990 pub fn fragment(children: impl IntoViews) -> Self {
992 Self(ViewKind::Fragment(positioned(children)))
993 }
994
995 pub fn keyed_fragment<T>(children: impl IntoIterator<Item = T>) -> Self
997 where
998 T: Into<KeyedView>,
999 {
1000 Self(ViewKind::Fragment(Rc::new(
1001 children.into_iter().map(Into::into).collect(),
1002 )))
1003 }
1004
1005 pub fn provide<T>(context: &Context<T>, value: T, child: impl Into<Self>) -> Self
1007 where
1008 T: Clone + PartialEq + 'static,
1009 {
1010 Self(ViewKind::Provider {
1011 provision: ContextProvision::new(context, value),
1012 child: Box::new(child.into().into_kind()),
1013 })
1014 }
1015
1016 pub(crate) fn from_kind(kind: ViewKind) -> Self {
1017 Self(kind)
1018 }
1019
1020 pub(crate) fn content_dialog(dialog: Element, content: Option<Self>, open: bool) -> Self {
1021 let dialog = match content {
1022 Some(content) => Self(ViewKind::Content {
1023 control: dialog,
1024 content: Box::new(content.into_kind()),
1025 }),
1026 None => Self::native(dialog),
1027 };
1028 Self(ViewKind::ContentDialog {
1029 dialog: Box::new(dialog.into_kind()),
1030 open,
1031 })
1032 }
1033
1034 pub(crate) fn as_kind(&self) -> &ViewKind {
1035 &self.0
1036 }
1037
1038 pub(crate) fn into_kind(self) -> ViewKind {
1039 self.0
1040 }
1041}
1042
1043#[derive(Clone, Debug, PartialEq)]
1045pub struct SlotView<S> {
1046 slot: S,
1047 content: SlotContent,
1048}
1049
1050impl<S> SlotView<S> {
1051 pub fn new(slot: S, view: impl Into<View>) -> Self {
1053 Self {
1054 slot,
1055 content: SlotContent::Single(view.into()),
1056 }
1057 }
1058
1059 pub fn collection<T>(slot: S, children: impl IntoIterator<Item = T>) -> Self
1061 where
1062 T: Into<KeyedView>,
1063 {
1064 Self {
1065 slot,
1066 content: SlotContent::Collection(Rc::new(
1067 children.into_iter().map(Into::into).collect(),
1068 )),
1069 }
1070 }
1071
1072 fn into_parts(self) -> (S, SlotContent) {
1073 (self.slot, self.content)
1074 }
1075}
1076
1077#[derive(Clone, Debug, PartialEq)]
1078pub(crate) enum SlotContent {
1079 Single(View),
1080 Collection(Rc<Vec<KeyedView>>),
1081}
1082
1083#[derive(Clone, Debug, PartialEq)]
1084pub(crate) struct SlottedView {
1085 pub(crate) slot: SlotId,
1086 pub(crate) content: SlotContent,
1087}
1088
1089impl From<Element> for View {
1090 fn from(value: Element) -> Self {
1091 Self(ViewKind::Native(value))
1092 }
1093}
1094
1095impl From<String> for View {
1096 fn from(value: String) -> Self {
1097 TextBlock::new().text(value).into()
1098 }
1099}
1100
1101impl From<&str> for View {
1102 fn from(value: &str) -> Self {
1103 value.to_string().into()
1104 }
1105}
1106
1107#[derive(Clone, Debug, PartialEq)]
1109pub struct KeyedView {
1110 key: Key,
1111 view: View,
1112}
1113
1114impl KeyedView {
1115 pub fn new(key: impl Into<Key>, view: impl Into<View>) -> Self {
1117 Self {
1118 key: key.into(),
1119 view: view.into(),
1120 }
1121 }
1122
1123 pub fn key(&self) -> &Key {
1124 &self.key
1125 }
1126
1127 pub fn view(&self) -> &View {
1128 &self.view
1129 }
1130
1131 pub(crate) fn into_parts(self) -> (Key, View) {
1132 (self.key, self.view)
1133 }
1134
1135 fn position(position: usize, view: View) -> Self {
1136 Self {
1137 key: Key::position(position),
1138 view,
1139 }
1140 }
1141}
1142
1143impl<K, V> From<(K, V)> for KeyedView
1144where
1145 K: Into<Key>,
1146 V: Into<View>,
1147{
1148 fn from((key, view): (K, V)) -> Self {
1149 Self::new(key, view)
1150 }
1151}
1152
1153#[derive(Clone)]
1157pub struct VirtualSource {
1158 key_revision: u64,
1159 len: usize,
1160 key: Rc<dyn Fn(usize) -> Key>,
1161 view: Rc<dyn Fn(usize) -> View>,
1162}
1163
1164impl VirtualSource {
1165 pub fn new<K, V, KI, VI>(key_revision: u64, len: usize, key: K, view: V) -> Self
1171 where
1172 K: Fn(usize) -> KI + 'static,
1173 V: Fn(usize) -> VI + 'static,
1174 KI: Into<Key>,
1175 VI: Into<View>,
1176 {
1177 Self {
1178 key_revision,
1179 len,
1180 key: Rc::new(move |index| key(index).into()),
1181 view: Rc::new(move |index| view(index).into()),
1182 }
1183 }
1184
1185 pub fn len(&self) -> usize {
1186 self.len
1187 }
1188
1189 pub fn is_empty(&self) -> bool {
1190 self.len == 0
1191 }
1192
1193 pub fn key_revision(&self) -> u64 {
1194 self.key_revision
1195 }
1196
1197 fn key(&self, index: usize) -> Key {
1198 (self.key)(index)
1199 }
1200
1201 fn view(&self, index: usize) -> View {
1202 (self.view)(index)
1203 }
1204}
1205
1206impl fmt::Debug for VirtualSource {
1207 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1208 formatter
1209 .debug_struct("VirtualSource")
1210 .field("key_revision", &self.key_revision)
1211 .field("len", &self.len)
1212 .finish_non_exhaustive()
1213 }
1214}
1215
1216impl PartialEq for VirtualSource {
1217 fn eq(&self, other: &Self) -> bool {
1218 self.key_revision == other.key_revision
1219 && self.len == other.len
1220 && Rc::ptr_eq(&self.key, &other.key)
1221 && Rc::ptr_eq(&self.view, &other.view)
1222 }
1223}
1224
1225#[derive(Clone, Debug, PartialEq)]
1226pub(crate) enum VirtualItems {
1227 Eager(Rc<Vec<KeyedView>>),
1228 Lazy(VirtualSource),
1229}
1230
1231impl Default for VirtualItems {
1232 fn default() -> Self {
1233 Self::Eager(Rc::default())
1234 }
1235}
1236
1237impl VirtualItems {
1238 pub(crate) fn len(&self) -> usize {
1239 match self {
1240 Self::Eager(items) => items.len(),
1241 Self::Lazy(source) => source.len(),
1242 }
1243 }
1244
1245 pub(crate) fn key(&self, index: usize) -> Option<Key> {
1246 match self {
1247 Self::Eager(items) => items.get(index).map(|item| item.key().clone()),
1248 Self::Lazy(source) => (index < source.len()).then(|| source.key(index)),
1249 }
1250 }
1251
1252 pub(crate) fn view(&self, index: usize) -> Option<View> {
1253 match self {
1254 Self::Eager(items) => items.get(index).map(|item| item.view().clone()),
1255 Self::Lazy(source) => (index < source.len()).then(|| source.view(index)),
1256 }
1257 }
1258
1259 pub(crate) fn changed_keys(&self, previous: &Self, keys: &[Key]) -> Option<Vec<Key>> {
1260 if let (Self::Lazy(current), Self::Lazy(previous)) = (self, previous)
1261 && current.key_revision() == previous.key_revision()
1262 && current.len() == previous.len()
1263 && current.len() == keys.len()
1264 {
1265 return None;
1266 }
1267 if let Self::Eager(items) = self
1268 && items.len() == keys.len()
1269 && keys
1270 .iter()
1271 .zip(items.iter())
1272 .all(|(key, item)| key == item.key())
1273 {
1274 return None;
1275 }
1276 let next = (0..self.len())
1277 .map(|index| self.key(index).unwrap())
1278 .collect::<Vec<_>>();
1279 (next != keys).then_some(next)
1280 }
1281}
1282
1283fn positioned(children: impl IntoViews) -> Rc<Vec<KeyedView>> {
1284 Rc::new(sealed::StaticViews::into_positioned(children))
1285}
1286
1287impl sealed::StaticViews for () {
1288 fn into_positioned(self) -> Vec<KeyedView> {
1289 Vec::new()
1290 }
1291}
1292
1293impl IntoViews for () {}
1294
1295impl<T, const N: usize> sealed::StaticViews for [T; N]
1296where
1297 T: Into<View>,
1298{
1299 fn into_positioned(self) -> Vec<KeyedView> {
1300 self.into_iter()
1301 .enumerate()
1302 .map(|(position, view)| KeyedView::position(position, view.into()))
1303 .collect()
1304 }
1305}
1306
1307impl<T, const N: usize> IntoViews for [T; N] where T: Into<View> {}
1308
1309macro_rules! impl_into_views_tuple {
1310 ($($type:ident $index:tt),+ $(,)?) => {
1311 impl<$($type),+> sealed::StaticViews for ($($type,)+)
1312 where
1313 $($type: Into<View>,)+
1314 {
1315 fn into_positioned(self) -> Vec<KeyedView> {
1316 vec![$(KeyedView::position($index, self.$index.into())),+]
1317 }
1318 }
1319
1320 impl<$($type),+> IntoViews for ($($type,)+)
1321 where
1322 $($type: Into<View>,)+
1323 {
1324 }
1325 };
1326}
1327
1328impl_into_views_tuple!(A 0);
1329impl_into_views_tuple!(A 0, B 1);
1330impl_into_views_tuple!(A 0, B 1, C 2);
1331impl_into_views_tuple!(A 0, B 1, C 2, D 3);
1332impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4);
1333impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4, F 5);
1334impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
1335impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
1336impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8);
1337impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9);
1338impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10);
1339impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11);
1340impl_into_views_tuple!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12);
1341impl_into_views_tuple!(
1342 A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13
1343);
1344impl_into_views_tuple!(
1345 A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14
1346);
1347impl_into_views_tuple!(
1348 A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15
1349);
1350
1351#[derive(Clone, Debug)]
1352enum FourValues {
1353 Uniform(f64),
1354 Values(Rc<[f64; 4]>),
1355}
1356
1357impl FourValues {
1358 fn new(values: [f64; 4]) -> Self {
1359 if values.iter().all(|value| *value == values[0]) {
1360 Self::Uniform(values[0])
1361 } else {
1362 Self::Values(Rc::new(values))
1363 }
1364 }
1365
1366 fn values(&self) -> [f64; 4] {
1367 match self {
1368 Self::Uniform(value) => [*value; 4],
1369 Self::Values(values) => **values,
1370 }
1371 }
1372}
1373
1374#[derive(Clone, Debug)]
1376pub struct Thickness(FourValues);
1377
1378impl Thickness {
1379 pub fn uniform(value: f64) -> Self {
1381 Self(FourValues::Uniform(value))
1382 }
1383
1384 pub fn xy(horizontal: f64, vertical: f64) -> Self {
1386 Self::new(horizontal, vertical, horizontal, vertical)
1387 }
1388
1389 pub fn new(left: f64, top: f64, right: f64, bottom: f64) -> Self {
1391 Self(FourValues::new([left, top, right, bottom]))
1392 }
1393
1394 pub fn left(&self) -> f64 {
1395 self.values()[0]
1396 }
1397
1398 pub fn top(&self) -> f64 {
1399 self.values()[1]
1400 }
1401
1402 pub fn right(&self) -> f64 {
1403 self.values()[2]
1404 }
1405
1406 pub fn bottom(&self) -> f64 {
1407 self.values()[3]
1408 }
1409
1410 pub(crate) fn values(&self) -> [f64; 4] {
1411 self.0.values()
1412 }
1413
1414 pub(crate) fn is_finite_non_negative(&self) -> bool {
1415 self.values()
1416 .into_iter()
1417 .all(|value| value.is_finite() && value >= 0.0)
1418 }
1419
1420 pub(crate) fn is_finite(&self) -> bool {
1421 self.values().into_iter().all(f64::is_finite)
1422 }
1423}
1424
1425impl Default for Thickness {
1426 fn default() -> Self {
1427 Self::uniform(0.0)
1428 }
1429}
1430
1431impl From<f64> for Thickness {
1432 fn from(value: f64) -> Self {
1433 Self::uniform(value)
1434 }
1435}
1436
1437impl PartialEq for Thickness {
1438 fn eq(&self, other: &Self) -> bool {
1439 self.values() == other.values()
1440 }
1441}
1442
1443#[derive(Clone, Debug)]
1445pub struct CornerRadius(FourValues);
1446
1447impl CornerRadius {
1448 pub fn uniform(value: f64) -> Self {
1450 Self(FourValues::Uniform(value))
1451 }
1452
1453 pub fn new(top_left: f64, top_right: f64, bottom_right: f64, bottom_left: f64) -> Self {
1455 Self(FourValues::new([
1456 top_left,
1457 top_right,
1458 bottom_right,
1459 bottom_left,
1460 ]))
1461 }
1462
1463 pub fn top_left(&self) -> f64 {
1464 self.values()[0]
1465 }
1466
1467 pub fn top_right(&self) -> f64 {
1468 self.values()[1]
1469 }
1470
1471 pub fn bottom_right(&self) -> f64 {
1472 self.values()[2]
1473 }
1474
1475 pub fn bottom_left(&self) -> f64 {
1476 self.values()[3]
1477 }
1478
1479 pub(crate) fn values(&self) -> [f64; 4] {
1480 self.0.values()
1481 }
1482
1483 pub(crate) fn is_finite_non_negative(&self) -> bool {
1484 self.values()
1485 .into_iter()
1486 .all(|value| value.is_finite() && value >= 0.0)
1487 }
1488}
1489
1490impl Default for CornerRadius {
1491 fn default() -> Self {
1492 Self::uniform(0.0)
1493 }
1494}
1495
1496impl From<f64> for CornerRadius {
1497 fn from(value: f64) -> Self {
1498 Self::uniform(value)
1499 }
1500}
1501
1502impl PartialEq for CornerRadius {
1503 fn eq(&self, other: &Self) -> bool {
1504 self.values() == other.values()
1505 }
1506}
1507
1508#[derive(Clone, Debug, PartialEq)]
1510pub enum ResourceValue {
1511 Color(Color),
1512 Thickness(Thickness),
1513 CornerRadius(CornerRadius),
1514}
1515
1516impl From<Color> for ResourceValue {
1517 fn from(value: Color) -> Self {
1518 Self::Color(value)
1519 }
1520}
1521
1522impl From<Thickness> for ResourceValue {
1523 fn from(value: Thickness) -> Self {
1524 Self::Thickness(value)
1525 }
1526}
1527
1528impl From<CornerRadius> for ResourceValue {
1529 fn from(value: CornerRadius) -> Self {
1530 Self::CornerRadius(value)
1531 }
1532}
1533
1534#[derive(Clone, Debug, Default, PartialEq)]
1536pub struct ResourceOverrides {
1537 values: std::collections::BTreeMap<String, ResourceValue>,
1538}
1539
1540impl ResourceOverrides {
1541 pub fn new() -> Self {
1542 Self::default()
1543 }
1544
1545 pub fn set(mut self, key: impl Into<String>, value: impl Into<ResourceValue>) -> Self {
1551 let key = key.into();
1552 assert!(!key.is_empty(), "resource override key must not be empty");
1553 let value = value.into();
1554 match &value {
1555 ResourceValue::Color(_) => {}
1556 ResourceValue::Thickness(value) => {
1557 assert!(
1558 value.is_finite_non_negative(),
1559 "resource override thickness must be finite and non-negative"
1560 );
1561 }
1562 ResourceValue::CornerRadius(value) => {
1563 assert!(
1564 value.is_finite_non_negative(),
1565 "resource override corner radius must be finite and non-negative"
1566 );
1567 }
1568 }
1569 self.values.insert(key, value);
1570 self
1571 }
1572
1573 pub(crate) fn values(&self) -> impl Iterator<Item = (&str, &ResourceValue)> {
1574 self.values.iter().map(|(key, value)| (key.as_str(), value))
1575 }
1576}
1577
1578#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1580pub enum WindowTheme {
1581 #[default]
1582 System,
1583 Light,
1584 Dark,
1585}
1586
1587#[derive(Clone, Copy, Debug, Default, PartialEq)]
1589pub struct WindowSize {
1590 pub width: f64,
1591 pub height: f64,
1592}
1593
1594#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1596pub enum ColorScheme {
1597 #[default]
1598 Light,
1599 Dark,
1600}
1601
1602#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1603pub enum DragKind {
1604 StorageItems,
1605 Text,
1606 Unsupported,
1607}
1608
1609#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1610pub enum DragDropOperation {
1611 Copy,
1612 Move,
1613 Link,
1614}
1615
1616#[derive(Clone, Debug, Eq, PartialEq)]
1617pub struct DragDropAction {
1618 pub operation: DragDropOperation,
1619 pub caption: Option<String>,
1620}
1621
1622impl DragDropAction {
1623 pub fn new(operation: DragDropOperation) -> Self {
1624 Self {
1625 operation,
1626 caption: None,
1627 }
1628 }
1629
1630 pub fn caption(mut self, caption: impl Into<String>) -> Self {
1631 self.caption = Some(caption.into());
1632 self
1633 }
1634}
1635
1636#[derive(Clone, Debug, Default, Eq, PartialEq)]
1637pub struct DragDropPolicy {
1638 pub storage_items: Option<DragDropAction>,
1639 pub text: Option<DragDropAction>,
1640}
1641
1642impl DragDropPolicy {
1643 pub fn new() -> Self {
1644 Self::default()
1645 }
1646
1647 pub fn storage_items(mut self, action: impl Into<Option<DragDropAction>>) -> Self {
1648 self.storage_items = action.into();
1649 self
1650 }
1651
1652 pub fn text(mut self, action: impl Into<Option<DragDropAction>>) -> Self {
1653 self.text = action.into();
1654 self
1655 }
1656
1657 pub(crate) fn accepts(&self, kind: DragKind) -> bool {
1658 match kind {
1659 DragKind::StorageItems => self.storage_items.is_some(),
1660 DragKind::Text => self.text.is_some(),
1661 DragKind::Unsupported => false,
1662 }
1663 }
1664}
1665
1666#[derive(Clone, Debug, Eq, PartialEq)]
1667pub struct DroppedStorageItem {
1668 pub name: String,
1669 pub path: String,
1670}
1671
1672#[derive(Clone, Debug, Eq, PartialEq)]
1673pub enum DroppedData {
1674 StorageItems(Vec<DroppedStorageItem>),
1675 Text(String),
1676 Unsupported,
1677}
1678
1679#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1681pub enum WindowBackdrop {
1682 #[default]
1683 None,
1684 Mica,
1685 MicaAlt,
1686 Acrylic,
1687}
1688
1689#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1691pub enum WindowTitleBarHeight {
1692 #[default]
1693 Standard,
1694 Tall,
1695}
1696
1697#[derive(Clone, Copy, Debug, Default, PartialEq)]
1699pub struct WindowConstraints {
1700 pub min_width: Option<f64>,
1701 pub min_height: Option<f64>,
1702 pub max_width: Option<f64>,
1703 pub max_height: Option<f64>,
1704}
1705
1706impl WindowConstraints {
1707 fn validate(self) {
1708 for value in [
1709 self.min_width,
1710 self.min_height,
1711 self.max_width,
1712 self.max_height,
1713 ]
1714 .into_iter()
1715 .flatten()
1716 {
1717 assert!(
1718 value.is_finite() && value > 0.0,
1719 "window constraints must be finite and positive"
1720 );
1721 }
1722 assert!(
1723 self.min_width
1724 .zip(self.max_width)
1725 .is_none_or(|(min, max)| min <= max)
1726 && self
1727 .min_height
1728 .zip(self.max_height)
1729 .is_none_or(|(min, max)| min <= max),
1730 "window minimum constraints must not exceed maximum constraints"
1731 );
1732 }
1733}
1734
1735#[derive(Clone, Copy, Debug, Default, PartialEq)]
1737pub struct WindowVisuals {
1738 pub(crate) backdrop: WindowBackdrop,
1739 pub(crate) client_size: Option<(f64, f64)>,
1740 pub(crate) constraints: Option<WindowConstraints>,
1741 pub(crate) icon: Option<&'static str>,
1742 pub(crate) theme: WindowTheme,
1743}
1744
1745impl WindowVisuals {
1746 pub fn new() -> Self {
1747 Self::default()
1748 }
1749
1750 pub fn backdrop(mut self, backdrop: WindowBackdrop) -> Self {
1751 self.backdrop = backdrop;
1752 self
1753 }
1754
1755 pub fn client_size(mut self, width: f64, height: f64) -> Self {
1761 assert!(
1762 width.is_finite() && width > 0.0 && height.is_finite() && height > 0.0,
1763 "window client size must be finite and positive"
1764 );
1765 self.client_size = Some((width, height));
1766 self
1767 }
1768
1769 pub fn icon(mut self, path: &'static str) -> Self {
1775 assert!(!path.is_empty(), "window icon path must not be empty");
1776 self.icon = Some(path);
1777 self
1778 }
1779
1780 pub fn constraints(mut self, constraints: WindowConstraints) -> Self {
1786 constraints.validate();
1787 self.constraints = Some(constraints);
1788 self
1789 }
1790
1791 pub fn theme(mut self, theme: WindowTheme) -> Self {
1792 self.theme = theme;
1793 self
1794 }
1795}
1796
1797#[cfg(test)]
1798mod visual_value_tests {
1799 use super::*;
1800 use crate::core::ThemeStyle;
1801 use std::mem::size_of;
1802
1803 #[test]
1804 fn four_value_types_keep_compact_layout_and_semantic_equality() {
1805 assert_eq!(size_of::<Thickness>(), 16);
1806 assert_eq!(size_of::<CornerRadius>(), 16);
1807 assert_eq!(size_of::<ThemeStyle>(), 4);
1808
1809 assert_eq!(Thickness::uniform(3.0), Thickness::new(3.0, 3.0, 3.0, 3.0));
1810 assert_eq!(
1811 CornerRadius::uniform(4.0),
1812 CornerRadius::new(4.0, 4.0, 4.0, 4.0)
1813 );
1814 assert_eq!(Thickness::xy(2.0, 5.0).values(), [2.0, 5.0, 2.0, 5.0]);
1815 }
1816
1817 #[test]
1818 fn window_client_size_rejects_invalid_values() {
1819 for (width, height) in [
1820 (0.0, 1.0),
1821 (1.0, -1.0),
1822 (f64::NAN, 1.0),
1823 (1.0, f64::INFINITY),
1824 ] {
1825 assert!(
1826 std::panic::catch_unwind(|| {
1827 WindowVisuals::new().client_size(width, height);
1828 })
1829 .is_err()
1830 );
1831 }
1832 }
1833}
1834
1835#[derive(Clone, Copy, Debug)]
1837pub enum GridLength {
1838 Auto,
1840 Pixel(f64),
1842 Star(f64),
1844}
1845
1846impl GridLength {
1847 pub const STAR: Self = Self::Star(1.0);
1849
1850 pub(crate) fn is_valid(self) -> bool {
1851 match self {
1852 Self::Auto => true,
1853 Self::Pixel(value) | Self::Star(value) => value.is_finite() && value >= 0.0,
1854 }
1855 }
1856}
1857
1858impl PartialEq for GridLength {
1859 fn eq(&self, other: &Self) -> bool {
1860 match (self, other) {
1861 (Self::Auto, Self::Auto) => true,
1862 (Self::Pixel(left), Self::Pixel(right)) | (Self::Star(left), Self::Star(right)) => {
1863 f64_eq(*left, *right)
1864 }
1865 _ => false,
1866 }
1867 }
1868}
1869
1870#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1872pub enum HorizontalAlignment {
1873 Left,
1874 Center,
1875 Right,
1876 Stretch,
1877}
1878
1879#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1881pub enum VerticalAlignment {
1882 Top,
1883 Center,
1884 Bottom,
1885 Stretch,
1886}
1887
1888#[derive(Clone, Debug, Default, PartialEq)]
1889pub(crate) struct ElementState {
1890 width: Property<f64>,
1891 height: Property<f64>,
1892 min_width: Property<f64>,
1893 max_width: Property<f64>,
1894 min_height: Property<f64>,
1895 max_height: Property<f64>,
1896 opacity: Property<f64>,
1897 horizontal_alignment: Property<HorizontalAlignment>,
1898 vertical_alignment: Property<VerticalAlignment>,
1899 margin: Property<Thickness>,
1900 row: Option<i32>,
1901 column: Option<i32>,
1902 row_span: Option<i32>,
1903 column_span: Option<i32>,
1904 relative_align_left: bool,
1905 relative_align_top: bool,
1906 relative_align_right: bool,
1907 relative_align_bottom: bool,
1908 relative_align_horizontal_center: bool,
1909 relative_align_vertical_center: bool,
1910 canvas_left: Option<f64>,
1911 canvas_top: Option<f64>,
1912 automation_name: Option<String>,
1913 automation_id: Option<String>,
1914 automation_heading_level: Option<AutomationHeadingLevel>,
1915 exit_transition: Option<ExitTransition>,
1916}
1917
1918#[derive(Clone, Debug, Default, PartialEq)]
1919pub(crate) enum Property<T> {
1920 #[default]
1921 Inherited,
1922 Set(T),
1923}
1924
1925impl<T> Property<T> {
1926 pub(crate) fn as_set(&self) -> Option<&T> {
1927 match self {
1928 Self::Inherited => None,
1929 Self::Set(value) => Some(value),
1930 }
1931 }
1932}
1933
1934impl<T> From<Option<T>> for Property<T> {
1935 fn from(value: Option<T>) -> Self {
1936 match value {
1937 Some(value) => Self::Set(value),
1938 None => Self::Inherited,
1939 }
1940 }
1941}
1942
1943pub(crate) fn f64_eq(left: f64, right: f64) -> bool {
1944 left == right || left.is_nan() && right.is_nan()
1945}
1946
1947pub(crate) fn f64_property_eq(left: &Property<f64>, right: &Property<f64>) -> bool {
1948 match (left, right) {
1949 (Property::Inherited, Property::Inherited) => true,
1950 (Property::Set(left), Property::Set(right)) => f64_eq(*left, *right),
1951 _ => false,
1952 }
1953}
1954
1955#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1956pub(crate) struct CallbackSource {
1957 queue: usize,
1958 token: ComponentToken,
1959}
1960
1961impl CallbackSource {
1962 pub(crate) fn new(queue: usize, token: ComponentToken) -> Self {
1963 Self { queue, token }
1964 }
1965}
1966
1967trait ErasedCallbackIdentity {
1968 fn as_any(&self) -> &dyn Any;
1969 fn equals(&self, other: &dyn ErasedCallbackIdentity) -> bool;
1970}
1971
1972struct TypedCallbackIdentity<K> {
1973 key: K,
1974 source: CallbackSource,
1975}
1976
1977impl<K: PartialEq + 'static> ErasedCallbackIdentity for TypedCallbackIdentity<K> {
1978 fn as_any(&self) -> &dyn Any {
1979 self
1980 }
1981
1982 fn equals(&self, other: &dyn ErasedCallbackIdentity) -> bool {
1983 other
1984 .as_any()
1985 .downcast_ref::<Self>()
1986 .is_some_and(|other| self.source == other.source && self.key == other.key)
1987 }
1988}
1989
1990pub struct Callback<T> {
1995 callback: Rc<dyn Fn(T) -> bool>,
1996 identity: Option<Rc<dyn ErasedCallbackIdentity>>,
1997}
1998
1999impl<T> Callback<T> {
2000 pub fn new(callback: impl Fn(T) + 'static) -> Self {
2002 Self::new_with_acceptance(move |value| {
2003 callback(value);
2004 true
2005 })
2006 }
2007
2008 pub(crate) fn new_with_acceptance(callback: impl Fn(T) -> bool + 'static) -> Self {
2009 Self {
2010 callback: Rc::new(callback),
2011 identity: None,
2012 }
2013 }
2014
2015 pub(crate) fn new_identified<K>(
2016 source: CallbackSource,
2017 key: K,
2018 callback: impl Fn(T) -> bool + 'static,
2019 ) -> Self
2020 where
2021 K: PartialEq + 'static,
2022 {
2023 Self {
2024 callback: Rc::new(callback),
2025 identity: Some(Rc::new(TypedCallbackIdentity { key, source })),
2026 }
2027 }
2028
2029 #[must_use = "false means the adapted message was rejected"]
2030 pub fn call(&self, value: T) -> bool {
2034 (self.callback)(value)
2035 }
2036}
2037
2038impl<T> Clone for Callback<T> {
2039 fn clone(&self) -> Self {
2040 Self {
2041 callback: Rc::clone(&self.callback),
2042 identity: self.identity.clone(),
2043 }
2044 }
2045}
2046
2047impl<T> fmt::Debug for Callback<T> {
2048 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2049 formatter
2050 .debug_tuple("Callback")
2051 .field(&Rc::as_ptr(&self.callback))
2052 .finish()
2053 }
2054}
2055
2056impl<T> PartialEq for Callback<T> {
2057 fn eq(&self, other: &Self) -> bool {
2058 Rc::ptr_eq(&self.callback, &other.callback)
2059 || self
2060 .identity
2061 .as_deref()
2062 .zip(other.identity.as_deref())
2063 .is_some_and(|(left, right)| left.equals(right))
2064 }
2065}
2066
2067pub trait IntoPayloadCallback<T> {
2069 fn into_payload_callback(self) -> Callback<T>;
2070}
2071
2072impl<T, F> IntoPayloadCallback<T> for F
2073where
2074 F: Fn(T) + 'static,
2075{
2076 fn into_payload_callback(self) -> Callback<T> {
2077 Callback::new(self)
2078 }
2079}
2080
2081impl<T> IntoPayloadCallback<T> for Callback<T> {
2082 fn into_payload_callback(self) -> Self {
2083 self
2084 }
2085}
2086
2087pub trait IntoUnitCallback {
2089 fn into_unit_callback(self) -> Callback<()>;
2090}
2091
2092impl<F> IntoUnitCallback for F
2093where
2094 F: Fn() + 'static,
2095{
2096 fn into_unit_callback(self) -> Callback<()> {
2097 Callback::new(move |()| self())
2098 }
2099}
2100
2101impl IntoUnitCallback for Callback<()> {
2102 fn into_unit_callback(self) -> Self {
2103 self
2104 }
2105}
2106
2107#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2109pub enum AcceleratorKey {
2110 R,
2111 NumberPad0,
2112 NumberPad1,
2113 NumberPad2,
2114 NumberPad3,
2115 NumberPad4,
2116 NumberPad5,
2117 NumberPad6,
2118 NumberPad7,
2119 NumberPad8,
2120 NumberPad9,
2121 Divide,
2122 Multiply,
2123 Subtract,
2124 Add,
2125 Decimal,
2126 Enter,
2127}
2128
2129#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
2131pub enum AcceleratorModifiers {
2132 #[default]
2133 None,
2134 Control,
2135}
2136
2137#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2138pub enum AutomationHeadingLevel {
2139 Level1,
2140 Level2,
2141 Level3,
2142 Level4,
2143 Level5,
2144 Level6,
2145 Level7,
2146 Level8,
2147 Level9,
2148}
2149
2150#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2152pub struct ExitTransition {
2153 duration: Duration,
2154}
2155
2156impl ExitTransition {
2157 pub fn fade(duration: Duration) -> Self {
2163 assert!(
2164 !duration.is_zero(),
2165 "Exit transition duration must be positive"
2166 );
2167 Self { duration }
2168 }
2169
2170 pub fn duration(self) -> Duration {
2171 self.duration
2172 }
2173}
2174
2175#[derive(Clone, Debug, PartialEq)]
2177pub struct KeyAccelerator {
2178 pub(crate) key: AcceleratorKey,
2179 pub(crate) modifiers: AcceleratorModifiers,
2180 pub(crate) callback: Callback<()>,
2181}
2182
2183impl KeyAccelerator {
2184 pub fn new(
2186 key: AcceleratorKey,
2187 modifiers: AcceleratorModifiers,
2188 callback: impl IntoUnitCallback,
2189 ) -> Self {
2190 Self {
2191 key,
2192 modifiers,
2193 callback: callback.into_unit_callback(),
2194 }
2195 }
2196}
2197
2198#[derive(Clone, Debug, Default, PartialEq)]
2200pub struct KeyAccelerators {
2201 pub(crate) values: Vec<KeyAccelerator>,
2202}
2203
2204impl KeyAccelerators {
2205 pub fn new(values: impl IntoIterator<Item = KeyAccelerator>) -> Self {
2206 Self {
2207 values: values.into_iter().collect(),
2208 }
2209 }
2210}
2211
2212#[allow(private_bounds)]
2217pub trait LayoutControl: sealed::LayoutControl {
2218 fn width(mut self, value: impl Into<Option<f64>>) -> Self
2219 where
2220 Self: Sized,
2221 {
2222 let value = value.into();
2223 assert!(
2224 value.is_none_or(|value| value.is_finite() && value >= 0.0),
2225 "Width must be finite and non-negative",
2226 );
2227 Rc::make_mut(
2228 sealed::LayoutControl::element_state_mut(&mut self)
2229 .get_or_insert_with(|| Rc::new(ElementState::default())),
2230 )
2231 .width = Property::from(value);
2232 self
2233 }
2234
2235 fn height(mut self, value: impl Into<Option<f64>>) -> Self
2236 where
2237 Self: Sized,
2238 {
2239 let value = value.into();
2240 assert!(
2241 value.is_none_or(|value| value.is_finite() && value >= 0.0),
2242 "Height must be finite and non-negative",
2243 );
2244 Rc::make_mut(
2245 sealed::LayoutControl::element_state_mut(&mut self)
2246 .get_or_insert_with(|| Rc::new(ElementState::default())),
2247 )
2248 .height = Property::from(value);
2249 self
2250 }
2251
2252 fn min_width(mut self, value: impl Into<Option<f64>>) -> Self
2253 where
2254 Self: Sized,
2255 {
2256 let value = value.into();
2257 assert!(
2258 value.is_none_or(|value| value.is_finite() && value >= 0.0),
2259 "Minimum width must be finite and non-negative",
2260 );
2261 Rc::make_mut(
2262 sealed::LayoutControl::element_state_mut(&mut self)
2263 .get_or_insert_with(|| Rc::new(ElementState::default())),
2264 )
2265 .min_width = Property::from(value);
2266 self
2267 }
2268
2269 fn max_width(mut self, value: impl Into<Option<f64>>) -> Self
2270 where
2271 Self: Sized,
2272 {
2273 let value = value.into();
2274 assert!(
2275 value.is_none_or(|value| value.is_finite() && value >= 0.0),
2276 "Maximum width must be finite and non-negative",
2277 );
2278 Rc::make_mut(
2279 sealed::LayoutControl::element_state_mut(&mut self)
2280 .get_or_insert_with(|| Rc::new(ElementState::default())),
2281 )
2282 .max_width = Property::from(value);
2283 self
2284 }
2285
2286 fn min_height(mut self, value: impl Into<Option<f64>>) -> Self
2287 where
2288 Self: Sized,
2289 {
2290 let value = value.into();
2291 assert!(
2292 value.is_none_or(|value| value.is_finite() && value >= 0.0),
2293 "Minimum height must be finite and non-negative",
2294 );
2295 Rc::make_mut(
2296 sealed::LayoutControl::element_state_mut(&mut self)
2297 .get_or_insert_with(|| Rc::new(ElementState::default())),
2298 )
2299 .min_height = Property::from(value);
2300 self
2301 }
2302
2303 fn max_height(mut self, value: impl Into<Option<f64>>) -> Self
2304 where
2305 Self: Sized,
2306 {
2307 let value = value.into();
2308 assert!(
2309 value.is_none_or(|value| value.is_finite() && value >= 0.0),
2310 "Maximum height must be finite and non-negative",
2311 );
2312 Rc::make_mut(
2313 sealed::LayoutControl::element_state_mut(&mut self)
2314 .get_or_insert_with(|| Rc::new(ElementState::default())),
2315 )
2316 .max_height = Property::from(value);
2317 self
2318 }
2319
2320 fn opacity(mut self, value: impl Into<Option<f64>>) -> Self
2321 where
2322 Self: Sized,
2323 {
2324 let value = value.into();
2325 assert!(
2326 value.is_none_or(|value| value.is_finite() && value >= 0.0),
2327 "Opacity must be finite and non-negative",
2328 );
2329 Rc::make_mut(
2330 sealed::LayoutControl::element_state_mut(&mut self)
2331 .get_or_insert_with(|| Rc::new(ElementState::default())),
2332 )
2333 .opacity = Property::from(value);
2334 self
2335 }
2336
2337 fn horizontal_alignment(mut self, value: impl Into<Option<HorizontalAlignment>>) -> Self
2338 where
2339 Self: Sized,
2340 {
2341 Rc::make_mut(
2342 sealed::LayoutControl::element_state_mut(&mut self)
2343 .get_or_insert_with(|| Rc::new(ElementState::default())),
2344 )
2345 .horizontal_alignment = Property::from(value.into());
2346 self
2347 }
2348
2349 fn vertical_alignment(mut self, value: impl Into<Option<VerticalAlignment>>) -> Self
2350 where
2351 Self: Sized,
2352 {
2353 Rc::make_mut(
2354 sealed::LayoutControl::element_state_mut(&mut self)
2355 .get_or_insert_with(|| Rc::new(ElementState::default())),
2356 )
2357 .vertical_alignment = Property::from(value.into());
2358 self
2359 }
2360
2361 fn margin(mut self, value: impl Into<Thickness>) -> Self
2362 where
2363 Self: Sized,
2364 {
2365 let value = value.into();
2366 assert!(value.is_finite(), "Margin must be finite");
2367 Rc::make_mut(
2368 sealed::LayoutControl::element_state_mut(&mut self)
2369 .get_or_insert_with(|| Rc::new(ElementState::default())),
2370 )
2371 .margin = Property::Set(value);
2372 self
2373 }
2374
2375 fn margin_optional<T>(mut self, value: Option<T>) -> Self
2376 where
2377 Self: Sized,
2378 T: Into<Thickness>,
2379 {
2380 let value = value.map(Into::into);
2381 assert!(
2382 value.as_ref().is_none_or(Thickness::is_finite),
2383 "Margin must be finite",
2384 );
2385 Rc::make_mut(
2386 sealed::LayoutControl::element_state_mut(&mut self)
2387 .get_or_insert_with(|| Rc::new(ElementState::default())),
2388 )
2389 .margin = Property::from(value);
2390 self
2391 }
2392
2393 fn exit_transition(mut self, transition: ExitTransition) -> Self
2394 where
2395 Self: Sized,
2396 {
2397 Rc::make_mut(
2398 sealed::LayoutControl::element_state_mut(&mut self)
2399 .get_or_insert_with(|| Rc::new(ElementState::default())),
2400 )
2401 .exit_transition = Some(transition);
2402 self
2403 }
2404}
2405
2406impl ElementState {
2407 pub(crate) fn exit_transition(&self) -> Option<ExitTransition> {
2408 self.exit_transition
2409 }
2410}
2411
2412#[allow(private_bounds)]
2414pub trait ContentControl: sealed::ContentControl + Sized {
2415 fn content(self, content: impl Into<View>) -> View {
2416 sealed::ContentControl::into_content_view(self, content.into())
2417 }
2418}
2419
2420#[allow(private_bounds)]
2425pub trait ChildrenControl: sealed::NativeControl + Sized {
2426 fn children(self, children: impl IntoViews) -> View {
2427 View(ViewKind::Children {
2428 control: sealed::NativeControl::into_element(self),
2429 children: positioned(children),
2430 })
2431 }
2432
2433 fn keyed_children<T>(self, children: impl IntoIterator<Item = T>) -> View
2434 where
2435 T: Into<KeyedView>,
2436 {
2437 View(ViewKind::Children {
2438 control: sealed::NativeControl::into_element(self),
2439 children: Rc::new(children.into_iter().map(Into::into).collect()),
2440 })
2441 }
2442}
2443
2444#[allow(private_bounds)]
2446pub trait SlotsControl: sealed::NativeControl + sealed::SlotIndex<Self::Slot> + Sized {
2447 type Slot: Copy;
2448
2449 fn slot(self, slot: Self::Slot, view: impl Into<View>) -> View {
2450 self.slots([SlotView::new(slot, view)])
2451 }
2452
2453 fn collection_slot<T>(self, slot: Self::Slot, children: impl IntoIterator<Item = T>) -> View
2454 where
2455 T: Into<KeyedView>,
2456 {
2457 self.slots([SlotView::collection(slot, children)])
2458 }
2459
2460 fn slots(self, slots: impl IntoIterator<Item = SlotView<Self::Slot>>) -> View {
2461 let control = sealed::NativeControl::into_element(self);
2462 let kind = control.kind();
2463 let slots = slots
2464 .into_iter()
2465 .map(|slot| {
2466 let (slot, content) = slot.into_parts();
2467 SlottedView {
2468 slot: slot_id(
2469 kind,
2470 <Self as sealed::SlotIndex<Self::Slot>>::slot_index(slot),
2471 )
2472 .unwrap(),
2473 content,
2474 }
2475 })
2476 .collect();
2477 View(ViewKind::Slots {
2478 control,
2479 slots: Rc::new(slots),
2480 })
2481 }
2482}
2483
2484pub trait GridChildExt: LayoutControl + Sized {
2509 fn grid_row(mut self, row: i32) -> Self {
2510 assert!(row >= 0, "Grid row must be non-negative");
2511 Rc::make_mut(
2512 self.element_state_mut()
2513 .get_or_insert_with(|| Rc::new(ElementState::default())),
2514 )
2515 .row = Some(row);
2516 self
2517 }
2518
2519 fn grid_column(mut self, column: i32) -> Self {
2520 assert!(column >= 0, "Grid column must be non-negative");
2521 Rc::make_mut(
2522 self.element_state_mut()
2523 .get_or_insert_with(|| Rc::new(ElementState::default())),
2524 )
2525 .column = Some(column);
2526 self
2527 }
2528
2529 fn grid_row_span(mut self, span: i32) -> Self {
2530 assert!(span > 0, "Grid row span must be positive");
2531 Rc::make_mut(
2532 self.element_state_mut()
2533 .get_or_insert_with(|| Rc::new(ElementState::default())),
2534 )
2535 .row_span = Some(span);
2536 self
2537 }
2538
2539 fn grid_column_span(mut self, span: i32) -> Self {
2540 assert!(span > 0, "Grid column span must be positive");
2541 Rc::make_mut(
2542 self.element_state_mut()
2543 .get_or_insert_with(|| Rc::new(ElementState::default())),
2544 )
2545 .column_span = Some(span);
2546 self
2547 }
2548}
2549
2550impl<T: LayoutControl> GridChildExt for T {}
2551
2552pub trait RelativePanelChildExt: LayoutControl + Sized {
2554 fn relative_align_left(mut self) -> Self {
2555 Rc::make_mut(
2556 self.element_state_mut()
2557 .get_or_insert_with(|| Rc::new(ElementState::default())),
2558 )
2559 .relative_align_left = true;
2560 self
2561 }
2562
2563 fn relative_align_top(mut self) -> Self {
2564 Rc::make_mut(
2565 self.element_state_mut()
2566 .get_or_insert_with(|| Rc::new(ElementState::default())),
2567 )
2568 .relative_align_top = true;
2569 self
2570 }
2571
2572 fn relative_align_right(mut self) -> Self {
2573 Rc::make_mut(
2574 self.element_state_mut()
2575 .get_or_insert_with(|| Rc::new(ElementState::default())),
2576 )
2577 .relative_align_right = true;
2578 self
2579 }
2580
2581 fn relative_align_bottom(mut self) -> Self {
2582 Rc::make_mut(
2583 self.element_state_mut()
2584 .get_or_insert_with(|| Rc::new(ElementState::default())),
2585 )
2586 .relative_align_bottom = true;
2587 self
2588 }
2589
2590 fn relative_align_horizontal_center(mut self) -> Self {
2591 Rc::make_mut(
2592 self.element_state_mut()
2593 .get_or_insert_with(|| Rc::new(ElementState::default())),
2594 )
2595 .relative_align_horizontal_center = true;
2596 self
2597 }
2598
2599 fn relative_align_vertical_center(mut self) -> Self {
2600 Rc::make_mut(
2601 self.element_state_mut()
2602 .get_or_insert_with(|| Rc::new(ElementState::default())),
2603 )
2604 .relative_align_vertical_center = true;
2605 self
2606 }
2607}
2608
2609impl<T: LayoutControl> RelativePanelChildExt for T {}
2610
2611pub trait CanvasChildExt: LayoutControl + Sized {
2613 fn canvas_left(mut self, value: f64) -> Self {
2614 assert!(value.is_finite(), "Canvas left must be finite");
2615 Rc::make_mut(
2616 self.element_state_mut()
2617 .get_or_insert_with(|| Rc::new(ElementState::default())),
2618 )
2619 .canvas_left = Some(value);
2620 self
2621 }
2622
2623 fn canvas_top(mut self, value: f64) -> Self {
2624 assert!(value.is_finite(), "Canvas top must be finite");
2625 Rc::make_mut(
2626 self.element_state_mut()
2627 .get_or_insert_with(|| Rc::new(ElementState::default())),
2628 )
2629 .canvas_top = Some(value);
2630 self
2631 }
2632}
2633
2634impl<T: LayoutControl> CanvasChildExt for T {}
2635
2636pub trait AutomationExt: LayoutControl + Sized {
2638 fn automation_name(mut self, value: impl Into<String>) -> Self {
2639 Rc::make_mut(
2640 self.element_state_mut()
2641 .get_or_insert_with(|| Rc::new(ElementState::default())),
2642 )
2643 .automation_name = Some(value.into());
2644 self
2645 }
2646
2647 fn automation_id(mut self, value: impl Into<String>) -> Self {
2648 Rc::make_mut(
2649 self.element_state_mut()
2650 .get_or_insert_with(|| Rc::new(ElementState::default())),
2651 )
2652 .automation_id = Some(value.into());
2653 self
2654 }
2655
2656 fn automation_heading_level(mut self, value: AutomationHeadingLevel) -> Self {
2657 Rc::make_mut(
2658 self.element_state_mut()
2659 .get_or_insert_with(|| Rc::new(ElementState::default())),
2660 )
2661 .automation_heading_level = Some(value);
2662 self
2663 }
2664}
2665
2666impl<T: LayoutControl> AutomationExt for T {}
2667
2668pub(crate) fn visit_element_state(
2669 placement: Option<&ElementState>,
2670 visit: &mut dyn FnMut(PropertyId, Option<PropertyValueRef<'_>>),
2671) {
2672 visit(
2673 PropertyId::Width,
2674 placement
2675 .and_then(|value| value.width.as_set())
2676 .copied()
2677 .map(PropertyValueRef::F64),
2678 );
2679 visit(
2680 PropertyId::Height,
2681 placement
2682 .and_then(|value| value.height.as_set())
2683 .copied()
2684 .map(PropertyValueRef::F64),
2685 );
2686 visit(
2687 PropertyId::MinWidth,
2688 placement
2689 .and_then(|value| value.min_width.as_set())
2690 .copied()
2691 .map(PropertyValueRef::F64),
2692 );
2693 visit(
2694 PropertyId::MaxWidth,
2695 placement
2696 .and_then(|value| value.max_width.as_set())
2697 .copied()
2698 .map(PropertyValueRef::F64),
2699 );
2700 visit(
2701 PropertyId::MinHeight,
2702 placement
2703 .and_then(|value| value.min_height.as_set())
2704 .copied()
2705 .map(PropertyValueRef::F64),
2706 );
2707 visit(
2708 PropertyId::MaxHeight,
2709 placement
2710 .and_then(|value| value.max_height.as_set())
2711 .copied()
2712 .map(PropertyValueRef::F64),
2713 );
2714 visit(
2715 PropertyId::Opacity,
2716 placement
2717 .and_then(|value| value.opacity.as_set())
2718 .copied()
2719 .map(PropertyValueRef::F64),
2720 );
2721 visit(
2722 PropertyId::HorizontalAlignment,
2723 placement
2724 .and_then(|value| value.horizontal_alignment.as_set())
2725 .copied()
2726 .map(PropertyValueRef::HorizontalAlignment),
2727 );
2728 visit(
2729 PropertyId::VerticalAlignment,
2730 placement
2731 .and_then(|value| value.vertical_alignment.as_set())
2732 .copied()
2733 .map(PropertyValueRef::VerticalAlignment),
2734 );
2735 visit(
2736 PropertyId::Margin,
2737 placement
2738 .and_then(|value| value.margin.as_set())
2739 .map(PropertyValueRef::Thickness),
2740 );
2741 let value = |value: Option<i32>| value.map(PropertyValueRef::I32);
2742 visit(
2743 PropertyId::GridRow,
2744 value(placement.and_then(|value| value.row)),
2745 );
2746 visit(
2747 PropertyId::GridColumn,
2748 value(placement.and_then(|value| value.column)),
2749 );
2750 visit(
2751 PropertyId::GridRowSpan,
2752 value(placement.and_then(|value| value.row_span)),
2753 );
2754 visit(
2755 PropertyId::GridColumnSpan,
2756 value(placement.and_then(|value| value.column_span)),
2757 );
2758 let relative = |value: bool| value.then_some(PropertyValueRef::Bool(true));
2759 visit(
2760 PropertyId::RelativeAlignLeft,
2761 relative(placement.is_some_and(|value| value.relative_align_left)),
2762 );
2763 visit(
2764 PropertyId::RelativeAlignTop,
2765 relative(placement.is_some_and(|value| value.relative_align_top)),
2766 );
2767 visit(
2768 PropertyId::RelativeAlignRight,
2769 relative(placement.is_some_and(|value| value.relative_align_right)),
2770 );
2771 visit(
2772 PropertyId::RelativeAlignBottom,
2773 relative(placement.is_some_and(|value| value.relative_align_bottom)),
2774 );
2775 visit(
2776 PropertyId::RelativeAlignHorizontalCenter,
2777 relative(placement.is_some_and(|value| value.relative_align_horizontal_center)),
2778 );
2779 visit(
2780 PropertyId::RelativeAlignVerticalCenter,
2781 relative(placement.is_some_and(|value| value.relative_align_vertical_center)),
2782 );
2783 visit(
2784 PropertyId::CanvasLeft,
2785 placement
2786 .and_then(|value| value.canvas_left)
2787 .map(PropertyValueRef::F64),
2788 );
2789 visit(
2790 PropertyId::CanvasTop,
2791 placement
2792 .and_then(|value| value.canvas_top)
2793 .map(PropertyValueRef::F64),
2794 );
2795 visit(
2796 PropertyId::AutomationName,
2797 placement
2798 .and_then(|value| value.automation_name.as_deref())
2799 .map(PropertyValueRef::Str),
2800 );
2801 visit(
2802 PropertyId::AutomationId,
2803 placement
2804 .and_then(|value| value.automation_id.as_deref())
2805 .map(PropertyValueRef::Str),
2806 );
2807 visit(
2808 PropertyId::AutomationHeadingLevel,
2809 placement
2810 .and_then(|value| value.automation_heading_level)
2811 .map(|value| PropertyValueRef::I32(value as i32 + 1)),
2812 );
2813}