@effindomv2/fui-rs 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/Cargo.toml +1 -1
  2. package/package.json +1 -1
  3. package/src/bridge_callbacks.rs +1 -0
  4. package/src/controls/appearance.rs +200 -0
  5. package/src/controls/button.rs +48 -122
  6. package/src/controls/checkbox.rs +27 -3
  7. package/src/controls/combobox.rs +61 -23
  8. package/src/controls/context_menu.rs +156 -256
  9. package/src/controls/control_template_set.rs +2 -3
  10. package/src/controls/control_tokens.rs +76 -0
  11. package/src/controls/dialog.rs +80 -78
  12. package/src/controls/dropdown.rs +46 -5
  13. package/src/controls/form.rs +6 -0
  14. package/src/controls/internal/button_presenter.rs +21 -20
  15. package/src/controls/internal/pressable_labeled_control.rs +11 -11
  16. package/src/controls/internal/text_input_core.rs +40 -22
  17. package/src/controls/internal/text_input_presenter.rs +29 -23
  18. package/src/controls/mod.rs +7 -2
  19. package/src/controls/nav_link.rs +15 -11
  20. package/src/controls/popup.rs +24 -40
  21. package/src/controls/progress_bar.rs +80 -109
  22. package/src/controls/radio_button.rs +27 -3
  23. package/src/controls/radio_group.rs +6 -0
  24. package/src/controls/slider.rs +38 -14
  25. package/src/controls/switch.rs +27 -3
  26. package/src/controls/tests.rs +796 -56
  27. package/src/controls/text_area.rs +12 -2
  28. package/src/controls/text_input.rs +19 -3
  29. package/src/lib.rs +44 -38
  30. package/src/node/core.rs +2 -0
  31. package/src/node/flex_box.rs +245 -2
  32. package/src/node/grid.rs +5 -0
  33. package/src/node/mod.rs +48 -0
  34. package/src/node/presenter_host_style.rs +177 -0
  35. package/src/node/text_node.rs +35 -0
  36. package/src/popup_presenter.rs +32 -0
@@ -11,6 +11,7 @@ use crate::node::Node;
11
11
  use crate::node::{column, text, Child, FlexBoxSurface};
12
12
  use crate::theme::{current_theme, generate_theme, use_custom_theme, use_system_theme};
13
13
  use crate::Application;
14
+ use crate::Border;
14
15
  use crate::PointerType;
15
16
  use crate::ScrollBarVisibility;
16
17
  use crate::TextCore;
@@ -46,15 +47,14 @@ impl ButtonPresenter for TestButtonPresenter {
46
47
  self.label.clone()
47
48
  }
48
49
 
49
- fn apply(
50
+ fn present(
50
51
  &self,
51
- host: &FlexBox,
52
52
  _theme: crate::theme::Theme,
53
53
  _state: ButtonVisualState,
54
54
  _colors: Option<ButtonColors>,
55
- ) {
56
- host.border(3.0, self.border_color);
55
+ ) -> crate::PresenterHostStyle {
57
56
  self.label.text_color(0x112233FF);
57
+ crate::PresenterHostStyle::new().border(Border::solid(3.0, self.border_color))
58
58
  }
59
59
  }
60
60
 
@@ -76,15 +76,16 @@ struct TestTextInputPresenter {
76
76
  }
77
77
 
78
78
  impl TextInputPresenter for TestTextInputPresenter {
79
- fn bind(&self, _host: FlexBox, _editor_host: TextCore, _placeholder_host: FlexBox) {}
79
+ fn bind(&self, _editor_host: TextCore, _placeholder_host: FlexBox) {}
80
80
 
81
- fn apply(
81
+ fn present(
82
82
  &self,
83
83
  _theme: crate::theme::Theme,
84
84
  state: &TextInputVisualState,
85
85
  _colors: Option<TextInputColors>,
86
- ) {
86
+ ) -> crate::PresenterHostStyle {
87
87
  *self.last_state.borrow_mut() = Some(*state);
88
+ crate::PresenterHostStyle::new()
88
89
  }
89
90
  }
90
91
 
@@ -335,7 +336,7 @@ fn dropdown_presenter_templates_create_once_and_are_stored_in_template_set() {
335
336
  })),
336
337
  ..ControlTemplateSet::default()
337
338
  };
338
- use_control_templates(Some(templates));
339
+ use_control_templates(templates);
339
340
  let active = get_control_templates().expect("templates should be installed");
340
341
  assert!(active.dropdown_field.is_some());
341
342
  assert!(active.dropdown_chevron.is_some());
@@ -684,18 +685,757 @@ fn button_hover_and_pressed_visual_states_follow_fui_as_lifecycle() {
684
685
  )));
685
686
  }
686
687
 
688
+ #[test]
689
+ fn button_host_geometry_overrides_survive_hover_and_pressed_presenter_updates() {
690
+ ffi::test::reset();
691
+ let button = button("Stable geometry");
692
+ button
693
+ .padding(18.0, 10.0, 20.0, 12.0)
694
+ .corners(11.0, 12.0, 13.0, 14.0)
695
+ .border_config(Border::dashed(2.0, 0x123456FF, 5.0, 3.0))
696
+ .drop_shadow(0x10203040, 1.0, 2.0, 6.0, 3.0);
697
+ Application::mount(button.clone());
698
+ let _ = ffi::test::take_calls();
699
+ let node_ref = button.retained_node_ref();
700
+ let button_handle = node_ref.handle().raw();
701
+
702
+ for update in [
703
+ |node: &NodeRef| press_enter(node),
704
+ |node: &NodeRef| press_down(node, 1),
705
+ |node: &NodeRef| press_up(node),
706
+ |node: &NodeRef| press_leave(node),
707
+ ] {
708
+ update(&node_ref);
709
+ let calls = ffi::test::take_calls();
710
+ assert!(!calls.iter().any(|call| matches!(
711
+ call,
712
+ Call::SetPadding { handle, .. }
713
+ | Call::SetBoxStyle { handle, .. }
714
+ | Call::SetDropShadow { handle, .. }
715
+ if *handle == button_handle
716
+ )));
717
+ assert_button_host_override_style(&button, Border::dashed(2.0, 0x123456FF, 5.0, 3.0));
718
+ }
719
+ }
720
+
721
+ #[test]
722
+ fn button_host_geometry_overrides_survive_theme_and_template_replacement() {
723
+ ffi::test::reset();
724
+ clear_control_templates();
725
+ let created = Rc::new(Cell::new(0));
726
+ let button = button("Stable host style");
727
+ button
728
+ .padding(18.0, 10.0, 20.0, 12.0)
729
+ .corners(11.0, 12.0, 13.0, 14.0)
730
+ .border_config(Border::solid(2.0, 0x123456FF))
731
+ .drop_shadow(0x10203040, 1.0, 2.0, 6.0, 3.0);
732
+ Application::mount(button.clone());
733
+ let handle = button.handle().raw();
734
+ let _ = ffi::test::take_calls();
735
+
736
+ let mut theme = current_theme();
737
+ theme.colors.border = 0xFFEEDDFF;
738
+ use_custom_theme(theme);
739
+ assert_button_host_override_style(&button, Border::solid(2.0, 0x123456FF));
740
+ assert_no_button_host_geometry_calls(handle, &ffi::test::take_calls());
741
+
742
+ button.template(Rc::new(TestButtonTemplate {
743
+ created: created.clone(),
744
+ border_color: 0xAABBCCFF,
745
+ }));
746
+ assert_eq!(created.get(), 1);
747
+ assert_button_host_override_style(&button, Border::solid(2.0, 0x123456FF));
748
+ assert_no_button_host_geometry_calls(handle, &ffi::test::take_calls());
749
+
750
+ use_system_theme();
751
+ clear_control_templates();
752
+ }
753
+
754
+ fn assert_button_host_override_style(button: &Button, expected_border: Border) {
755
+ let style = button.flex_box_root().resolved_host_style();
756
+ assert_eq!(
757
+ style.padding,
758
+ Some(crate::EdgeInsets::new(18.0, 10.0, 20.0, 12.0))
759
+ );
760
+ assert_eq!(
761
+ style.corners,
762
+ Some(crate::Corners::new(11.0, 12.0, 13.0, 14.0))
763
+ );
764
+ assert_eq!(style.border, Some(expected_border));
765
+ assert_eq!(
766
+ style.shadow,
767
+ Some(crate::Shadow::new(0x10203040, 1.0, 2.0, 6.0, 3.0))
768
+ );
769
+ }
770
+
771
+ fn assert_no_button_host_geometry_calls(handle: u64, calls: &[Call]) {
772
+ assert!(!calls.iter().any(|call| matches!(
773
+ call,
774
+ Call::SetPadding { handle: call_handle, .. }
775
+ | Call::SetBoxStyle { handle: call_handle, .. }
776
+ | Call::SetDropShadow { handle: call_handle, .. }
777
+ if *call_handle == handle
778
+ )));
779
+ }
780
+
781
+ #[test]
782
+ fn text_input_local_host_style_survives_state_theme_and_template_changes() {
783
+ ffi::test::reset();
784
+ let input = text_input();
785
+ input
786
+ .bg_color(0x010203FF)
787
+ .padding(17.0, 9.0, 19.0, 11.0)
788
+ .corners(10.0, 11.0, 12.0, 13.0)
789
+ .border(3.0, 0xAABBCCFF);
790
+ Application::mount(input.clone());
791
+ let handle = input.handle().raw();
792
+ let _ = ffi::test::take_calls();
793
+
794
+ input.enabled(false);
795
+ assert_text_input_local_host_style(&input);
796
+ assert_no_text_input_local_host_style_calls("disabled", handle, &ffi::test::take_calls());
797
+
798
+ let mut theme = current_theme();
799
+ theme.colors.surface = 0xFFEEDDFF;
800
+ theme.colors.border = 0x112233FF;
801
+ use_custom_theme(theme);
802
+ assert_text_input_local_host_style(&input);
803
+ assert_no_text_input_local_host_style_calls("theme", handle, &ffi::test::take_calls());
804
+
805
+ input.template(Rc::new(TestTextInputTemplate {
806
+ created: Rc::new(Cell::new(0)),
807
+ last_state: Rc::new(RefCell::new(None)),
808
+ }));
809
+ assert_text_input_local_host_style(&input);
810
+ assert_no_text_input_local_host_style_calls("template", handle, &ffi::test::take_calls());
811
+ use_system_theme();
812
+ }
813
+
814
+ fn assert_text_input_local_host_style(input: &TextInput) {
815
+ let style = input.flex_box_root().resolved_host_style();
816
+ assert_eq!(style.background, Some(0x010203FF));
817
+ assert_eq!(
818
+ style.padding,
819
+ Some(crate::EdgeInsets::new(17.0, 9.0, 19.0, 11.0))
820
+ );
821
+ assert_eq!(
822
+ style.corners,
823
+ Some(crate::Corners::new(10.0, 11.0, 12.0, 13.0))
824
+ );
825
+ assert_eq!(style.border, Some(Border::solid(3.0, 0xAABBCCFF)));
826
+ }
827
+
828
+ fn assert_no_text_input_local_host_style_calls(stage: &str, handle: u64, calls: &[Call]) {
829
+ assert!(
830
+ !calls.iter().any(|call| matches!(
831
+ call,
832
+ Call::SetBgColor { handle: call_handle, .. }
833
+ | Call::SetPadding { handle: call_handle, .. }
834
+ | Call::SetBoxStyle { handle: call_handle, .. }
835
+ if *call_handle == handle
836
+ )),
837
+ "{stage}: {calls:?}"
838
+ );
839
+ }
840
+
841
+ #[test]
842
+ fn clearing_local_host_style_reveals_live_presenter_style() {
843
+ ffi::test::reset();
844
+ let host = flex_box();
845
+ host.apply_presenter_style(
846
+ crate::PresenterHostStyle::new()
847
+ .background(0x102030FF)
848
+ .padding(crate::EdgeInsets::new(1.0, 2.0, 3.0, 4.0))
849
+ .corners(crate::Corners::new(5.0, 6.0, 7.0, 8.0))
850
+ .border(Border::solid(2.0, 0x405060FF))
851
+ .shadow(crate::Shadow::new(0x11223344, 9.0, 10.0, 11.0, 12.0))
852
+ .opacity(0.7)
853
+ .flex_direction(crate::FlexDirection::Row)
854
+ .justify_content(crate::JustifyContent::Center)
855
+ .align_items(crate::AlignItems::End)
856
+ .cursor(crate::CursorStyle::Pointer),
857
+ );
858
+ host.bg_color(0xAABBCCFF)
859
+ .padding(20.0, 21.0, 22.0, 23.0)
860
+ .corners(24.0, 25.0, 26.0, 27.0)
861
+ .border_config(Border::dashed(3.0, 0xDDEEFF00, 4.0, 5.0))
862
+ .drop_shadow(0x55667788, 28.0, 29.0, 30.0, 31.0)
863
+ .opacity(0.4)
864
+ .flex_direction(crate::FlexDirection::Column)
865
+ .justify_content(crate::JustifyContent::End)
866
+ .align_items(crate::AlignItems::Center)
867
+ .cursor(crate::CursorStyle::Text);
868
+ Application::mount(host.clone());
869
+ let handle = host.handle().raw();
870
+ let _ = ffi::test::take_calls();
871
+
872
+ host.clear_bg_color()
873
+ .clear_padding()
874
+ .clear_corners()
875
+ .clear_border()
876
+ .clear_drop_shadow()
877
+ .clear_opacity()
878
+ .clear_flex_direction()
879
+ .clear_justify_content()
880
+ .clear_align_items()
881
+ .clear_cursor();
882
+
883
+ assert_eq!(
884
+ host.resolved_host_style(),
885
+ crate::PresenterHostStyle::new()
886
+ .background(0x102030FF)
887
+ .padding(crate::EdgeInsets::new(1.0, 2.0, 3.0, 4.0))
888
+ .corners(crate::Corners::new(5.0, 6.0, 7.0, 8.0))
889
+ .border(Border::solid(2.0, 0x405060FF))
890
+ .shadow(crate::Shadow::new(0x11223344, 9.0, 10.0, 11.0, 12.0))
891
+ .opacity(0.7)
892
+ .flex_direction(crate::FlexDirection::Row)
893
+ .justify_content(crate::JustifyContent::Center)
894
+ .align_items(crate::AlignItems::End)
895
+ .cursor(crate::CursorStyle::Pointer)
896
+ );
897
+ let calls = ffi::test::take_calls();
898
+ assert!(calls.iter().any(|call| matches!(
899
+ call,
900
+ Call::SetBoxStyle {
901
+ handle: call_handle,
902
+ bg_color,
903
+ radius_tl,
904
+ border_width,
905
+ border_color,
906
+ ..
907
+ } if *call_handle == handle && *bg_color == 0x102030FF
908
+ && *radius_tl == 5.0 && *border_width == 2.0 && *border_color == 0x405060FF
909
+ )));
910
+ assert!(calls.iter().any(|call| matches!(
911
+ call,
912
+ Call::SetPadding { handle: call_handle, left, top, right, bottom }
913
+ if *call_handle == handle && *left == 1.0 && *top == 2.0
914
+ && *right == 3.0 && *bottom == 4.0
915
+ )));
916
+ }
917
+
918
+ #[test]
919
+ fn local_host_style_precedence_is_independent_of_setter_order() {
920
+ fn configured(presenter_first: bool) -> FlexBox {
921
+ let host = column();
922
+ let presenter = crate::PresenterHostStyle::new()
923
+ .background(0x102030FF)
924
+ .padding(crate::EdgeInsets::all(4.0))
925
+ .opacity(0.8);
926
+ if presenter_first {
927
+ host.apply_presenter_style(presenter);
928
+ }
929
+ host.bg_color(0xAABBCCFF)
930
+ .padding(5.0, 6.0, 7.0, 8.0)
931
+ .opacity(0.5);
932
+ if !presenter_first {
933
+ host.apply_presenter_style(presenter);
934
+ }
935
+ host
936
+ }
937
+
938
+ let presenter_first = configured(true);
939
+ let local_first = configured(false);
940
+ assert_eq!(
941
+ presenter_first.resolved_host_style(),
942
+ local_first.resolved_host_style()
943
+ );
944
+ assert_eq!(
945
+ presenter_first.resolved_host_style().background,
946
+ Some(0xAABBCCFF)
947
+ );
948
+ assert_eq!(
949
+ presenter_first.resolved_host_style().padding,
950
+ Some(crate::EdgeInsets::new(5.0, 6.0, 7.0, 8.0))
951
+ );
952
+ assert_eq!(presenter_first.resolved_host_style().opacity, Some(0.5));
953
+ }
954
+
955
+ #[test]
956
+ fn clearing_presenter_host_style_restores_defaults_and_identical_updates_are_silent() {
957
+ ffi::test::reset();
958
+ let host = flex_box();
959
+ Application::mount(host.clone());
960
+ let handle = host.handle().raw();
961
+ let _ = ffi::test::take_calls();
962
+ let presenter = crate::PresenterHostStyle::new()
963
+ .background(0x123456FF)
964
+ .padding(crate::EdgeInsets::all(7.0));
965
+
966
+ host.apply_presenter_style(presenter);
967
+ assert!(!ffi::test::take_calls().is_empty());
968
+ host.apply_presenter_style(presenter);
969
+ assert!(ffi::test::take_calls().is_empty());
970
+
971
+ host.clear_presenter_style();
972
+ assert_eq!(host.resolved_host_style(), crate::PresenterHostStyle::new());
973
+ let calls = ffi::test::take_calls();
974
+ assert!(calls.iter().any(|call| matches!(
975
+ call,
976
+ Call::SetBgColor { handle: call_handle, color }
977
+ if *call_handle == handle && *color == 0x00000000
978
+ )));
979
+ assert!(calls.iter().any(|call| matches!(
980
+ call,
981
+ Call::SetPadding { handle: call_handle, left, top, right, bottom }
982
+ if *call_handle == handle && *left == 0.0 && *top == 0.0
983
+ && *right == 0.0 && *bottom == 0.0
984
+ )));
985
+ }
986
+
987
+ #[test]
988
+ fn configuration_clear_methods_restore_live_theme_and_builtin_presenters() {
989
+ ffi::test::reset();
990
+ clear_control_templates();
991
+ let theme = current_theme();
992
+
993
+ let button_created = Rc::new(Cell::new(0));
994
+ let button = button("Reset button");
995
+ button
996
+ .template(Rc::new(TestButtonTemplate {
997
+ created: button_created.clone(),
998
+ border_color: 0x010203FF,
999
+ }))
1000
+ .colors(ButtonColors::new().background(0xAABBCCFF));
1001
+ Application::mount(button.clone());
1002
+ let button_handle = button.handle().raw();
1003
+ let _ = ffi::test::take_calls();
1004
+ button.clear_colors().clear_template();
1005
+ assert_eq!(button_created.get(), 1);
1006
+ let calls = ffi::test::take_calls();
1007
+ assert!(calls.iter().any(|call| matches!(
1008
+ call,
1009
+ Call::SetBgColor { handle, color }
1010
+ if *handle == button_handle && *color == theme.colors.accent
1011
+ )));
1012
+ assert!(calls.iter().any(|call| matches!(
1013
+ call,
1014
+ Call::SetBoxStyle { handle, border_color, .. }
1015
+ if *handle == button_handle && *border_color == theme.colors.border
1016
+ )));
1017
+
1018
+ let input = text_input();
1019
+ input.colors(TextInputColors::new().background(0x112233FF));
1020
+ Application::mount(input.clone());
1021
+ let input_handle = input.handle().raw();
1022
+ let _ = ffi::test::take_calls();
1023
+ input.clear_colors();
1024
+ assert!(ffi::test::take_calls().iter().any(|call| matches!(
1025
+ call,
1026
+ Call::SetBgColor { handle, color }
1027
+ if *handle == input_handle && *color == theme.colors.surface
1028
+ )));
1029
+
1030
+ let slider = slider();
1031
+ let default_thumb_size = create_default_slider_presenter(None).metrics().thumb_size;
1032
+ slider.sizing(SliderSizing::new().thumb_size(40.0));
1033
+ Application::mount(slider.clone());
1034
+ let _ = ffi::test::take_calls();
1035
+ slider.clear_sizing();
1036
+ assert!(ffi::test::take_calls().iter().any(|call| matches!(
1037
+ call,
1038
+ Call::SetWidth { value, unit_enum, .. }
1039
+ if (*value - default_thumb_size).abs() < f32::EPSILON
1040
+ && *unit_enum == Unit::Pixel as u32
1041
+ )));
1042
+ }
1043
+
1044
+ #[test]
1045
+ fn overlay_partial_appearances_follow_live_theme_and_clear_atomically() {
1046
+ ffi::test::reset();
1047
+ let dialog = dialog("Appearance", "Dialog appearance");
1048
+ dialog
1049
+ .appearance(DialogAppearance::new().card(SurfaceAppearance::new().background(0x102030FF)));
1050
+ Application::mount(dialog.clone());
1051
+ dialog.show();
1052
+ let _ = ffi::test::take_calls();
1053
+
1054
+ let next_theme = generate_theme(true, 0x445566FF);
1055
+ use_custom_theme(next_theme.clone());
1056
+ let calls = ffi::test::take_calls();
1057
+ assert!(
1058
+ calls.iter().any(|call| matches!(
1059
+ call,
1060
+ Call::SetBoxStyle { bg_color, border_color, .. }
1061
+ if *bg_color == 0x102030FF && *border_color == next_theme.colors.border
1062
+ )),
1063
+ "dialog theme calls: {calls:?}"
1064
+ );
1065
+
1066
+ dialog.clear_appearance();
1067
+ assert!(ffi::test::take_calls().iter().any(|call| matches!(
1068
+ call,
1069
+ Call::SetBgColor { color, .. } if *color == next_theme.colors.surface
1070
+ )));
1071
+
1072
+ let menu = ContextMenu::new();
1073
+ menu.items(vec![MenuItem::new(
1074
+ "Appearance item",
1075
+ ContextMenuAction::ReloadPage,
1076
+ )]);
1077
+ menu.appearance(
1078
+ ContextMenuAppearance::new()
1079
+ .panel(SurfaceAppearance::new().background(0xAABBCCFF))
1080
+ .item(ContextMenuItemAppearance::new().text_color(0x112233FF)),
1081
+ );
1082
+ Application::mount(menu.clone());
1083
+ menu.show(20.0, 20.0);
1084
+ let _ = ffi::test::take_calls();
1085
+ let light_theme = generate_theme(false, 0x778899FF);
1086
+ use_custom_theme(light_theme.clone());
1087
+ let calls = ffi::test::take_calls();
1088
+ assert!(calls.iter().any(|call| matches!(
1089
+ call,
1090
+ Call::SetBoxStyle { bg_color, border_color, .. }
1091
+ if *bg_color == 0xAABBCCFF
1092
+ && *border_color == light_theme.context_menu.panel_border_color
1093
+ )));
1094
+ menu.clear_appearance();
1095
+ assert!(ffi::test::take_calls().iter().any(|call| matches!(
1096
+ call,
1097
+ Call::SetBgColor { color, .. }
1098
+ if *color == light_theme.context_menu.panel_background
1099
+ )));
1100
+
1101
+ let popup = popup();
1102
+ Application::mount(popup.clone());
1103
+ popup.show_at_point(20.0, 20.0, 120.0, 80.0);
1104
+ let surface_handle = popup.surface().handle().raw();
1105
+ popup.appearance(PopupAppearance::new().panel(SurfaceAppearance::new().background(0xDEADBEEF)));
1106
+ let _ = ffi::test::take_calls();
1107
+ popup.clear_appearance();
1108
+ assert!(ffi::test::take_calls().iter().any(|call| matches!(
1109
+ call,
1110
+ Call::SetBgColor { handle, color }
1111
+ if *handle == surface_handle && *color == 0x00000000
1112
+ )));
1113
+ use_system_theme();
1114
+ }
1115
+
1116
+ #[test]
1117
+ fn retained_button_and_nav_link_keep_theme_subscriptions_after_wrappers_drop() {
1118
+ ffi::test::reset();
1119
+ let root = column();
1120
+ let button = button("Themed button");
1121
+ let link = NavLink::with_label("/next", "Themed link");
1122
+ root.child(&button).child(&link);
1123
+ Application::mount(root);
1124
+ let button_handle = button.handle().raw();
1125
+ let link_label_handle = link.label_node().handle().raw();
1126
+ drop(button);
1127
+ drop(link);
1128
+ let _ = ffi::test::take_calls();
1129
+
1130
+ let mut theme = current_theme();
1131
+ theme.colors.accent = 0x102030FF;
1132
+ theme.colors.border = 0x405060FF;
1133
+ use_custom_theme(theme.clone());
1134
+ let calls = ffi::test::take_calls();
1135
+
1136
+ assert!(calls.iter().any(|call| matches!(
1137
+ call,
1138
+ Call::SetBgColor { handle, color }
1139
+ if *handle == button_handle && *color == theme.colors.accent
1140
+ )));
1141
+ assert!(calls.iter().any(|call| matches!(
1142
+ call,
1143
+ Call::SetBoxStyle { handle, border_color, .. }
1144
+ if *handle == button_handle && *border_color == theme.colors.border
1145
+ )));
1146
+ assert!(calls.iter().any(|call| matches!(
1147
+ call,
1148
+ Call::SetTextColor { handle, color }
1149
+ if *handle == link_label_handle && *color == theme.colors.accent
1150
+ )));
1151
+ use_system_theme();
1152
+ }
1153
+
1154
+ #[test]
1155
+ fn retained_pressable_and_slider_keep_theme_subscriptions_after_wrappers_drop() {
1156
+ ffi::test::reset();
1157
+ let root = column();
1158
+ let checkbox = checkbox("Themed checkbox");
1159
+ let slider = slider();
1160
+ root.child(&checkbox).child(&slider);
1161
+ Application::mount(root);
1162
+ drop(checkbox);
1163
+ drop(slider);
1164
+ let _ = ffi::test::take_calls();
1165
+
1166
+ let mut theme = current_theme();
1167
+ theme.colors.text_primary = 0x112233FF;
1168
+ theme.colors.accent = 0x445566FF;
1169
+ theme.colors.border = 0x778899FF;
1170
+ use_custom_theme(theme.clone());
1171
+ let calls = ffi::test::take_calls();
1172
+
1173
+ assert!(calls.iter().any(|call| matches!(
1174
+ call,
1175
+ Call::SetTextColor { color, .. } if *color == theme.colors.text_primary
1176
+ )));
1177
+ assert!(calls.iter().any(|call| matches!(
1178
+ call,
1179
+ Call::SetBgColor { color, .. } if *color == theme.colors.accent
1180
+ )));
1181
+ assert!(calls.iter().any(|call| matches!(
1182
+ call,
1183
+ Call::SetTextColor { color, .. } if *color == theme.colors.text_primary
1184
+ )));
1185
+ use_system_theme();
1186
+ }
1187
+
1188
+ #[test]
1189
+ fn retained_scroll_box_keeps_scrollbar_theme_subscriptions_after_wrapper_drop() {
1190
+ ffi::test::reset();
1191
+ let root = column();
1192
+ let scroll = crate::scroll_box();
1193
+ let vertical_track = scroll.vertical_scrollbar().render();
1194
+ scroll
1195
+ .vertical_scrollbar_visibility(ScrollBarVisibility::Always)
1196
+ .height(120.0, Unit::Pixel)
1197
+ .child(&column().height(400.0, Unit::Pixel).clone());
1198
+ root.child(&scroll);
1199
+ Application::mount(root);
1200
+ let track_handle = vertical_track.handle().raw();
1201
+ drop(scroll);
1202
+ drop(vertical_track);
1203
+ let _ = ffi::test::take_calls();
1204
+
1205
+ let mut theme = current_theme();
1206
+ theme.colors.scrollbar_track = 0x314159FF;
1207
+ theme.colors.scrollbar_thumb = 0x271828FF;
1208
+ use_custom_theme(theme.clone());
1209
+ let calls = ffi::test::take_calls();
1210
+
1211
+ assert!(calls.iter().any(|call| matches!(
1212
+ call,
1213
+ Call::SetBgColor { handle, color }
1214
+ if *handle == track_handle && *color == theme.colors.scrollbar_track
1215
+ )));
1216
+ use_system_theme();
1217
+ }
1218
+
1219
+ #[test]
1220
+ fn retained_dropdown_keeps_theme_subscription_after_wrapper_drop() {
1221
+ ffi::test::reset();
1222
+ let root = column();
1223
+ let dropdown = dropdown();
1224
+ dropdown.items(vec![DropdownItem::from_value("Alpha")]);
1225
+ root.child(&dropdown);
1226
+ Application::mount(root);
1227
+ drop(dropdown);
1228
+ let _ = ffi::test::take_calls();
1229
+
1230
+ let mut theme = current_theme();
1231
+ theme.colors.surface = 0x135724FF;
1232
+ theme.colors.border = 0x246813FF;
1233
+ theme.colors.text_primary = 0xABCDEFff;
1234
+ use_custom_theme(theme.clone());
1235
+ let calls = ffi::test::take_calls();
1236
+
1237
+ assert!(calls.iter().any(|call| matches!(
1238
+ call,
1239
+ Call::SetBoxStyle { border_color, .. } if *border_color == theme.colors.border
1240
+ )));
1241
+ assert!(calls.iter().any(|call| matches!(
1242
+ call,
1243
+ Call::SetTextColor { color, .. } if *color == theme.colors.text_primary
1244
+ )));
1245
+ use_system_theme();
1246
+ }
1247
+
1248
+ #[test]
1249
+ fn retained_progress_bar_keeps_theme_subscription_after_wrapper_drop() {
1250
+ ffi::test::reset();
1251
+ let root = column();
1252
+ let progress = progress_bar();
1253
+ root.child(&progress);
1254
+ Application::mount(root);
1255
+ let handle = progress.handle().raw();
1256
+ drop(progress);
1257
+ let _ = ffi::test::take_calls();
1258
+
1259
+ let mut theme = current_theme();
1260
+ theme.colors.scrollbar_track = 0x123456FF;
1261
+ use_custom_theme(theme.clone());
1262
+ let calls = ffi::test::take_calls();
1263
+ assert!(calls.iter().any(|call| matches!(
1264
+ call,
1265
+ Call::SetBgColor { handle: call_handle, color }
1266
+ if *call_handle == handle && *color == theme.colors.scrollbar_track
1267
+ )));
1268
+ use_system_theme();
1269
+ }
1270
+
1271
+ #[test]
1272
+ fn retained_text_editors_keep_visual_subscriptions_after_wrappers_drop() {
1273
+ ffi::test::reset();
1274
+ let root = column();
1275
+ let input = text_input();
1276
+ let area = text_area();
1277
+ root.child(&input).child(&area);
1278
+ Application::mount(root);
1279
+ drop(input);
1280
+ drop(area);
1281
+ let _ = ffi::test::take_calls();
1282
+
1283
+ let mut theme = current_theme();
1284
+ theme.colors.surface = 0x102938FF;
1285
+ theme.colors.border = 0x203948FF;
1286
+ theme.colors.text_primary = 0x304958FF;
1287
+ theme.colors.accent = 0x405968FF;
1288
+ use_custom_theme(theme.clone());
1289
+ let calls = ffi::test::take_calls();
1290
+
1291
+ assert!(calls.iter().any(|call| matches!(
1292
+ call,
1293
+ Call::SetBoxStyle { border_color, .. } if *border_color == theme.colors.border
1294
+ )));
1295
+ assert!(calls.iter().any(|call| matches!(
1296
+ call,
1297
+ Call::SetTextColor { color, .. } if *color == theme.colors.text_primary
1298
+ )));
1299
+ use_system_theme();
1300
+ }
1301
+
1302
+ #[test]
1303
+ fn retained_combo_box_keeps_theme_subscription_after_wrapper_drop() {
1304
+ ffi::test::reset();
1305
+ let root = column();
1306
+ let combo = combo_box();
1307
+ combo.items(["Alpha"]);
1308
+ root.child(&combo);
1309
+ Application::mount(root);
1310
+ drop(combo);
1311
+ let _ = ffi::test::take_calls();
1312
+
1313
+ let mut theme = current_theme();
1314
+ theme.colors.surface = 0x516171FF;
1315
+ theme.colors.border = 0x617181FF;
1316
+ theme.colors.text_primary = 0x718191FF;
1317
+ use_custom_theme(theme.clone());
1318
+ let calls = ffi::test::take_calls();
1319
+
1320
+ assert!(calls.iter().any(|call| matches!(
1321
+ call,
1322
+ Call::SetBgColor { color, .. } if *color == theme.colors.surface
1323
+ )));
1324
+ assert!(calls.iter().any(|call| matches!(
1325
+ call,
1326
+ Call::SetBoxStyle { border_color, .. } if *border_color == theme.colors.border
1327
+ )));
1328
+ assert!(calls.iter().any(|call| matches!(
1329
+ call,
1330
+ Call::SetTextColor { color, .. } if *color == theme.colors.text_primary
1331
+ )));
1332
+ use_system_theme();
1333
+ }
1334
+
1335
+ #[test]
1336
+ fn retained_overlays_keep_theme_subscriptions_without_wrapper_cycles() {
1337
+ ffi::test::reset();
1338
+ let root = column();
1339
+ let dialog = dialog("Retained dialog", "Body");
1340
+ let menu = context_menu(Vec::<MenuItem>::new());
1341
+ root.child(&dialog).child(&menu);
1342
+ Application::mount(root);
1343
+ dialog.show();
1344
+ menu.show(24.0, 24.0);
1345
+ drop(dialog);
1346
+ drop(menu);
1347
+ let _ = ffi::test::take_calls();
1348
+
1349
+ let mut theme = current_theme();
1350
+ theme.colors.surface = 0x8292A2FF;
1351
+ theme.colors.border = 0x92A2B2FF;
1352
+ theme.colors.text_primary = 0xA2B2C2FF;
1353
+ use_custom_theme(theme.clone());
1354
+ let calls = ffi::test::take_calls();
1355
+
1356
+ assert!(calls.iter().any(|call| matches!(
1357
+ call,
1358
+ Call::SetTextColor { color, .. } if *color == theme.colors.text_primary
1359
+ )));
1360
+ use_system_theme();
1361
+ }
1362
+
1363
+ #[test]
1364
+ fn retained_visual_subscriptions_stop_after_unmount() {
1365
+ ffi::test::reset();
1366
+ let input = text_input();
1367
+ Application::mount(input.clone());
1368
+ let input_handle = input.handle().raw();
1369
+ drop(input);
1370
+ Application::unmount();
1371
+ let _ = ffi::test::take_calls();
1372
+
1373
+ let mut theme = current_theme();
1374
+ theme.colors.surface = 0xB3C3D3FF;
1375
+ use_custom_theme(theme);
1376
+ let calls = ffi::test::take_calls();
1377
+ assert!(!calls.iter().any(|call| matches!(
1378
+ call,
1379
+ Call::SetBgColor { handle, .. } if *handle == input_handle
1380
+ )));
1381
+ use_system_theme();
1382
+ }
1383
+
1384
+ #[test]
1385
+ fn retained_visual_subscription_fires_once_per_theme_change() {
1386
+ ffi::test::reset();
1387
+ let progress = progress_bar();
1388
+ Application::mount(progress.clone());
1389
+ let handle = progress.handle().raw();
1390
+ drop(progress);
1391
+ let _ = ffi::test::take_calls();
1392
+
1393
+ let mut first = current_theme();
1394
+ first.colors.scrollbar_track = 0xC4D4E4FF;
1395
+ use_custom_theme(first);
1396
+ let first_calls = ffi::test::take_calls();
1397
+ assert_eq!(
1398
+ first_calls
1399
+ .iter()
1400
+ .filter(|call| matches!(
1401
+ call,
1402
+ Call::SetBgColor { handle: call_handle, color: 0xC4D4E4FF }
1403
+ if *call_handle == handle
1404
+ ))
1405
+ .count(),
1406
+ 1
1407
+ );
1408
+
1409
+ let mut second = current_theme();
1410
+ second.colors.scrollbar_track = 0xD5E5F5FF;
1411
+ use_custom_theme(second);
1412
+ let second_calls = ffi::test::take_calls();
1413
+ assert_eq!(
1414
+ second_calls
1415
+ .iter()
1416
+ .filter(|call| matches!(
1417
+ call,
1418
+ Call::SetBgColor { handle: call_handle, color: 0xD5E5F5FF }
1419
+ if *call_handle == handle
1420
+ ))
1421
+ .count(),
1422
+ 1
1423
+ );
1424
+ use_system_theme();
1425
+ }
1426
+
687
1427
  #[test]
688
1428
  fn button_uses_app_level_template_when_no_local_template_is_set() {
689
1429
  ffi::test::reset();
690
1430
  clear_control_templates();
691
1431
  let created = Rc::new(Cell::new(0));
692
- use_control_templates(Some(ControlTemplateSet {
1432
+ use_control_templates(ControlTemplateSet {
693
1433
  button: Some(Rc::new(TestButtonTemplate {
694
1434
  created: created.clone(),
695
1435
  border_color: 0xAABBCCFF,
696
1436
  })),
697
1437
  ..Default::default()
698
- }));
1438
+ });
699
1439
 
700
1440
  let button = button("Template action");
701
1441
  Application::mount(button.clone());
@@ -722,10 +1462,10 @@ fn button_local_template_replaces_presenter_tree_and_retains_label_updates() {
722
1462
  let _ = ffi::test::take_calls();
723
1463
 
724
1464
  let created = Rc::new(Cell::new(0));
725
- button.template(Some(Rc::new(TestButtonTemplate {
1465
+ button.template(Rc::new(TestButtonTemplate {
726
1466
  created: created.clone(),
727
1467
  border_color: 0x445566FF,
728
- })));
1468
+ }));
729
1469
  let calls = ffi::test::take_calls();
730
1470
  let button_handle = button.retained_node_ref().handle().raw();
731
1471
  assert_eq!(created.get(), 1);
@@ -1549,10 +2289,10 @@ fn checkbox_template_and_sizing_follow_fui_as_surface() {
1549
2289
  })),
1550
2290
  ..Default::default()
1551
2291
  };
1552
- use_control_templates(Some(templates));
2292
+ use_control_templates(templates);
1553
2293
  let checkbox = checkbox("Agree");
1554
2294
 
1555
- checkbox.sizing(Some(LabeledControlSizing::new().label_font_size(21.0)));
2295
+ checkbox.sizing(LabeledControlSizing::new().label_font_size(21.0));
1556
2296
  checkbox.check(true);
1557
2297
 
1558
2298
  assert_eq!(created.get(), 2);
@@ -1570,12 +2310,12 @@ fn checkbox_local_template_replaces_indicator_before_mount() {
1570
2310
  let applied_colors = Rc::new(Cell::new(None));
1571
2311
  let checkbox = checkbox("Local template");
1572
2312
  checkbox
1573
- .template(Some(Rc::new(TestCheckboxIndicatorTemplate {
2313
+ .template(Rc::new(TestCheckboxIndicatorTemplate {
1574
2314
  created: created.clone(),
1575
2315
  applied_checked_state: applied_checked_state.clone(),
1576
2316
  applied_state: applied_state.clone(),
1577
2317
  applied_colors: applied_colors.clone(),
1578
- })))
2318
+ }))
1579
2319
  .check(true);
1580
2320
 
1581
2321
  Application::mount(checkbox.clone());
@@ -1593,7 +2333,7 @@ fn checkbox_presenter_receives_fui_as_visual_state_and_colors() {
1593
2333
  let applied_checked_state = Rc::new(Cell::new(SemanticCheckedState::False));
1594
2334
  let applied_state = Rc::new(RefCell::new(None));
1595
2335
  let applied_colors = Rc::new(Cell::new(None));
1596
- use_control_templates(Some(ControlTemplateSet {
2336
+ use_control_templates(ControlTemplateSet {
1597
2337
  checkbox_indicator: Some(Rc::new(TestCheckboxIndicatorTemplate {
1598
2338
  created,
1599
2339
  applied_checked_state,
@@ -1601,7 +2341,7 @@ fn checkbox_presenter_receives_fui_as_visual_state_and_colors() {
1601
2341
  applied_colors: applied_colors.clone(),
1602
2342
  })),
1603
2343
  ..Default::default()
1604
- }));
2344
+ });
1605
2345
  let colors = LabeledControlColors::new()
1606
2346
  .accent(0xAA00AAFF)
1607
2347
  .background(0x111111FF)
@@ -1609,7 +2349,7 @@ fn checkbox_presenter_receives_fui_as_visual_state_and_colors() {
1609
2349
  .text_primary(0x333333FF)
1610
2350
  .text_muted(0x444444FF);
1611
2351
  let checkbox = checkbox("Agree");
1612
- checkbox.tri_state(true).colors(Some(colors));
2352
+ checkbox.tri_state(true).colors(colors);
1613
2353
  Application::mount(checkbox.clone());
1614
2354
  let node_ref = checkbox.retained_node_ref();
1615
2355
 
@@ -1669,12 +2409,12 @@ fn labeled_control_sizing_and_label_colors_reach_host_calls() {
1669
2409
  ffi::test::reset();
1670
2410
  let checkbox = checkbox("Agree");
1671
2411
  checkbox
1672
- .sizing(Some(LabeledControlSizing::new().label_font_size(23.0)))
1673
- .colors(Some(
2412
+ .sizing(LabeledControlSizing::new().label_font_size(23.0))
2413
+ .colors(
1674
2414
  LabeledControlColors::new()
1675
2415
  .text_primary(0x123456FF)
1676
2416
  .text_muted(0xABCDEF88),
1677
- ));
2417
+ );
1678
2418
  Application::mount(checkbox.clone());
1679
2419
 
1680
2420
  let calls = ffi::test::take_calls();
@@ -1710,11 +2450,11 @@ fn radio_button_template_and_sizing_follow_fui_as_surface() {
1710
2450
  })),
1711
2451
  ..Default::default()
1712
2452
  };
1713
- use_control_templates(Some(templates));
2453
+ use_control_templates(templates);
1714
2454
  let radio = radio_button("alpha");
1715
2455
 
1716
2456
  radio
1717
- .sizing(Some(LabeledControlSizing::new().label_font_size(19.0)))
2457
+ .sizing(LabeledControlSizing::new().label_font_size(19.0))
1718
2458
  .check(true);
1719
2459
 
1720
2460
  assert_eq!(created.get(), 2);
@@ -1735,7 +2475,7 @@ fn switch_template_surface_follows_fui_as() {
1735
2475
  })),
1736
2476
  ..Default::default()
1737
2477
  };
1738
- use_control_templates(Some(templates));
2478
+ use_control_templates(templates);
1739
2479
  let toggle = switch("Toggle");
1740
2480
 
1741
2481
  toggle.check(true);
@@ -1750,11 +2490,11 @@ fn switch_sizing_updates_default_indicator_and_recreates_custom_template() {
1750
2490
  ffi::test::reset();
1751
2491
  clear_control_templates();
1752
2492
  let toggle = switch("Sized switch");
1753
- toggle.sizing(Some(
2493
+ toggle.sizing(
1754
2494
  LabeledControlSizing::new()
1755
2495
  .indicator_size(32.0)
1756
2496
  .label_font_size(18.0),
1757
- ));
2497
+ );
1758
2498
  Application::mount(toggle.clone());
1759
2499
  let calls = ffi::test::take_calls();
1760
2500
  assert!(calls.iter().any(|call| matches!(
@@ -1767,11 +2507,11 @@ fn switch_sizing_updates_default_indicator_and_recreates_custom_template() {
1767
2507
  let checked = Rc::new(Cell::new(false));
1768
2508
  let toggle = switch("Template switch");
1769
2509
  toggle
1770
- .template(Some(Rc::new(TestSwitchIndicatorTemplate {
2510
+ .template(Rc::new(TestSwitchIndicatorTemplate {
1771
2511
  created: created.clone(),
1772
2512
  checked: checked.clone(),
1773
- })))
1774
- .sizing(Some(LabeledControlSizing::new().indicator_size(30.0)));
2513
+ }))
2514
+ .sizing(LabeledControlSizing::new().indicator_size(30.0));
1775
2515
  assert_eq!(created.get(), 2);
1776
2516
  }
1777
2517
 
@@ -1780,9 +2520,7 @@ fn slider_default_presenter_uses_sizing_for_geometry() {
1780
2520
  ffi::test::reset();
1781
2521
  let slider = slider();
1782
2522
  slider
1783
- .sizing(Some(
1784
- SliderSizing::new().thumb_size(24.0).track_thickness(8.0),
1785
- ))
2523
+ .sizing(SliderSizing::new().thumb_size(24.0).track_thickness(8.0))
1786
2524
  .length(120.0);
1787
2525
  Application::mount(slider.clone());
1788
2526
  let calls = ffi::test::take_calls();
@@ -1840,8 +2578,8 @@ fn slider_template_replacement_and_colors_follow_fui_as_surface() {
1840
2578
  .thumb(0x778899FF);
1841
2579
 
1842
2580
  slider
1843
- .template(Some(template))
1844
- .colors(Some(colors))
2581
+ .template(template)
2582
+ .colors(colors)
1845
2583
  .orientation(Orientation::Vertical)
1846
2584
  .length(140.0)
1847
2585
  .value(50.0);
@@ -1872,7 +2610,7 @@ fn slider_control_template_set_and_local_template_precedence_follow_fui_as() {
1872
2610
  let app_layout_length = Rc::new(Cell::new(0.0));
1873
2611
  let app_apply_state = Rc::new(RefCell::new(None));
1874
2612
  let app_apply_colors = Rc::new(Cell::new(None));
1875
- use_control_templates(Some(ControlTemplateSet {
2613
+ use_control_templates(ControlTemplateSet {
1876
2614
  slider: Some(Rc::new(TestSliderTemplate {
1877
2615
  created: app_created.clone(),
1878
2616
  metrics: SliderPresenterMetrics::new(22.0, 7.0),
@@ -1882,26 +2620,26 @@ fn slider_control_template_set_and_local_template_precedence_follow_fui_as() {
1882
2620
  last_apply_colors: app_apply_colors,
1883
2621
  })),
1884
2622
  ..Default::default()
1885
- }));
2623
+ });
1886
2624
 
1887
2625
  let slider = slider();
1888
- slider.sizing(Some(SliderSizing::new().thumb_size(30.0)));
2626
+ slider.sizing(SliderSizing::new().thumb_size(30.0));
1889
2627
  assert_eq!(app_created.get(), 2);
1890
2628
 
1891
2629
  let local_layout_state = Rc::new(RefCell::new(None));
1892
2630
  let local_layout_length = Rc::new(Cell::new(0.0));
1893
2631
  let local_apply_state = Rc::new(RefCell::new(None));
1894
2632
  let local_apply_colors = Rc::new(Cell::new(None));
1895
- slider.template(Some(Rc::new(TestSliderTemplate {
2633
+ slider.template(Rc::new(TestSliderTemplate {
1896
2634
  created: local_created.clone(),
1897
2635
  metrics: SliderPresenterMetrics::new(28.0, 9.0),
1898
2636
  last_layout_state: local_layout_state.clone(),
1899
2637
  last_layout_length: local_layout_length,
1900
2638
  last_apply_state: local_apply_state,
1901
2639
  last_apply_colors: local_apply_colors,
1902
- })));
2640
+ }));
1903
2641
  slider
1904
- .sizing(Some(SliderSizing::new().thumb_size(36.0)))
2642
+ .sizing(SliderSizing::new().thumb_size(36.0))
1905
2643
  .length(160.0)
1906
2644
  .value(40.0);
1907
2645
 
@@ -1923,14 +2661,14 @@ fn slider_presenter_receives_hover_drag_and_disabled_state() {
1923
2661
  let last_apply_state = Rc::new(RefCell::new(None));
1924
2662
  let last_apply_colors = Rc::new(Cell::new(None));
1925
2663
  let slider = slider();
1926
- slider.template(Some(Rc::new(TestSliderTemplate {
2664
+ slider.template(Rc::new(TestSliderTemplate {
1927
2665
  created: Rc::new(Cell::new(0)),
1928
2666
  metrics: SliderPresenterMetrics::new(18.0, 6.0),
1929
2667
  last_layout_state: last_layout_state.clone(),
1930
2668
  last_layout_length,
1931
2669
  last_apply_state: last_apply_state.clone(),
1932
2670
  last_apply_colors,
1933
- })));
2671
+ }));
1934
2672
  Application::mount(slider.clone());
1935
2673
  let _ = ffi::test::take_calls();
1936
2674
  let node_ref = slider.retained_node_ref();
@@ -2410,10 +3148,10 @@ fn progress_bar_default_semantic_label_does_not_overwrite_explicit_label() {
2410
3148
  }
2411
3149
 
2412
3150
  #[test]
2413
- fn progress_bar_invalid_length_and_thickness_warn_and_clamp() {
3151
+ fn progress_bar_invalid_sizing_warns_and_falls_back_to_defaults() {
2414
3152
  ffi::test::reset();
2415
3153
  let progress = progress_bar();
2416
- progress.length(0.0).thickness(-2.0);
3154
+ progress.sizing(ProgressBarSizing::new().length(0.0).thickness(-2.0));
2417
3155
 
2418
3156
  Application::mount(progress);
2419
3157
  let calls = ffi::test::take_calls();
@@ -2422,23 +3160,23 @@ fn progress_bar_invalid_length_and_thickness_warn_and_clamp() {
2422
3160
  call,
2423
3161
  Call::Log { category, message }
2424
3162
  if category == "Warning/Layout"
2425
- && message == "ProgressBar.length() received 0; clamping to 1.0."
3163
+ && message == "ProgressBarSizing.length() received 0; ignoring."
2426
3164
  )));
2427
3165
  assert!(calls.iter().any(|call| matches!(
2428
3166
  call,
2429
3167
  Call::Log { category, message }
2430
3168
  if category == "Warning/Layout"
2431
- && message == "ProgressBar.thickness() received -2; clamping to 1.0."
3169
+ && message == "ProgressBarSizing.thickness() received -2; ignoring."
2432
3170
  )));
2433
3171
  assert!(calls.iter().any(|call| matches!(
2434
3172
  call,
2435
3173
  Call::SetWidth { value, unit_enum, .. }
2436
- if *value == 1.0 && *unit_enum == Unit::Pixel as u32
3174
+ if *value == PROGRESS_LENGTH && *unit_enum == Unit::Pixel as u32
2437
3175
  )));
2438
3176
  assert!(calls.iter().any(|call| matches!(
2439
3177
  call,
2440
3178
  Call::SetHeight { value, unit_enum, .. }
2441
- if *value == 1.0 && *unit_enum == Unit::Pixel as u32
3179
+ if *value == PROGRESS_THICKNESS && *unit_enum == Unit::Pixel as u32
2442
3180
  )));
2443
3181
  }
2444
3182
 
@@ -2475,8 +3213,7 @@ fn progress_bar_overrides_survive_theme_changes() {
2475
3213
  ffi::test::reset();
2476
3214
  let progress = progress_bar();
2477
3215
  progress
2478
- .track_color(0x123456FF)
2479
- .fill_color(0xABCDEF88)
3216
+ .colors(ProgressBarColors::new().track(0x123456FF).fill(0xABCDEF88))
2480
3217
  .corner_radius(9.0);
2481
3218
  Application::mount(progress.clone());
2482
3219
  let calls = ffi::test::take_calls();
@@ -2489,10 +3226,13 @@ fn progress_bar_overrides_survive_theme_changes() {
2489
3226
  use_custom_theme(generate_theme(true, 0x445566FF));
2490
3227
  let calls = ffi::test::take_calls();
2491
3228
 
2492
- assert!(calls.iter().any(|call| matches!(
3229
+ assert_eq!(
3230
+ progress.flex_box_root().resolved_host_style().background,
3231
+ Some(0x123456FF)
3232
+ );
3233
+ assert!(!calls.iter().any(|call| matches!(
2493
3234
  call,
2494
- Call::SetBgColor { handle, color }
2495
- if *handle == root_handle && *color == 0x123456FF
3235
+ Call::SetBgColor { handle, .. } if *handle == root_handle
2496
3236
  )));
2497
3237
  assert!(calls.iter().any(|call| matches!(
2498
3238
  call,
@@ -3671,7 +4411,7 @@ fn text_area_reports_internal_scroll_offsets_and_uses_text_area_template_slot()
3671
4411
  let text_area_created = Rc::new(Cell::new(0));
3672
4412
  let text_input_state = Rc::new(RefCell::new(None));
3673
4413
  let text_area_state = Rc::new(RefCell::new(None));
3674
- use_control_templates(Some(ControlTemplateSet {
4414
+ use_control_templates(ControlTemplateSet {
3675
4415
  text_input: Some(Rc::new(TestTextInputTemplate {
3676
4416
  created: text_input_created.clone(),
3677
4417
  last_state: text_input_state,
@@ -3681,7 +4421,7 @@ fn text_area_reports_internal_scroll_offsets_and_uses_text_area_template_slot()
3681
4421
  last_state: text_area_state.clone(),
3682
4422
  })),
3683
4423
  ..Default::default()
3684
- }));
4424
+ });
3685
4425
 
3686
4426
  let input = text_area();
3687
4427
  input