@effindomv2/fui-rs 0.1.22 → 0.1.24

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 (46) hide show
  1. package/Cargo.toml +1 -1
  2. package/README.md +12 -0
  3. package/package.json +1 -1
  4. package/src/controls/anti_selection_area.rs +0 -18
  5. package/src/controls/button.rs +14 -42
  6. package/src/controls/checkbox.rs +7 -0
  7. package/src/controls/combobox.rs +7 -6
  8. package/src/controls/context_menu.rs +38 -5
  9. package/src/controls/dialog.rs +41 -3
  10. package/src/controls/dropdown.rs +2 -2
  11. package/src/controls/form.rs +16 -20
  12. package/src/controls/internal/button_presenter.rs +5 -5
  13. package/src/controls/internal/checkbox_indicator_presenter.rs +1 -1
  14. package/src/controls/internal/pressable_labeled_control.rs +21 -7
  15. package/src/controls/internal/selectable_popup_list.rs +2 -2
  16. package/src/controls/internal/text_input_core.rs +38 -68
  17. package/src/controls/internal/text_input_presenter.rs +4 -4
  18. package/src/controls/mod.rs +11 -4
  19. package/src/controls/popup.rs +35 -20
  20. package/src/controls/radio_button.rs +7 -0
  21. package/src/controls/radio_group.rs +17 -0
  22. package/src/controls/selection_area.rs +0 -18
  23. package/src/controls/shared.rs +2 -17
  24. package/src/controls/switch.rs +7 -0
  25. package/src/controls/tests.rs +230 -6
  26. package/src/controls/text_area.rs +28 -142
  27. package/src/controls/text_editor_surface.rs +167 -0
  28. package/src/controls/text_input.rs +30 -144
  29. package/src/lib.rs +26 -20
  30. package/src/mobile_text_selection_toolbar.rs +2 -2
  31. package/src/node/core.rs +451 -60
  32. package/src/node/custom_drawable.rs +20 -0
  33. package/src/node/flex_box.rs +39 -47
  34. package/src/node/grid.rs +43 -128
  35. package/src/node/helpers.rs +34 -43
  36. package/src/node/image.rs +47 -56
  37. package/src/node/mod.rs +398 -73
  38. package/src/node/scroll_bar.rs +6 -0
  39. package/src/node/scroll_box.rs +8 -21
  40. package/src/node/scroll_view.rs +144 -13
  41. package/src/node/svg_node.rs +47 -66
  42. package/src/node/text_node.rs +358 -79
  43. package/src/node/virtual_list.rs +17 -0
  44. package/src/popup_presenter.rs +31 -0
  45. package/src/text.rs +59 -0
  46. package/src/text_indices.rs +48 -0
@@ -8,26 +8,28 @@ use crate::ffi::{
8
8
  use crate::focus_visibility;
9
9
  use crate::generated::ffi::NodeType;
10
10
  use crate::node::Node;
11
- use crate::node::{column, text, Child, FlexBoxSurface};
11
+ use crate::node::{
12
+ column, text, BoxStyleSurface, Child, ChildContainerSurface, FlexLayoutSurface, LayoutSurface,
13
+ };
12
14
  use crate::theme::{current_theme, generate_theme, use_custom_theme, use_system_theme};
13
15
  use crate::Application;
14
16
  use crate::Border;
15
17
  use crate::PointerType;
16
18
  use crate::ScrollBarVisibility;
17
- use crate::TextCore;
19
+ use crate::TextNode;
18
20
  use std::cell::Cell;
19
21
  use std::rc::Rc;
20
22
 
21
23
  #[derive(Clone)]
22
24
  struct TestButtonPresenter {
23
25
  content_root: FlexBox,
24
- label: TextCore,
26
+ label: TextNode,
25
27
  border_color: u32,
26
28
  }
27
29
 
28
30
  impl TestButtonPresenter {
29
31
  fn new(border_color: u32) -> Self {
30
- let label = TextCore::new("");
32
+ let label = TextNode::new_core("");
31
33
  let content_root = flex_box();
32
34
  content_root.child(&label);
33
35
  Self {
@@ -43,7 +45,7 @@ impl ButtonPresenter for TestButtonPresenter {
43
45
  self.content_root.clone()
44
46
  }
45
47
 
46
- fn label_node(&self) -> TextCore {
48
+ fn label_node(&self) -> TextNode {
47
49
  self.label.clone()
48
50
  }
49
51
 
@@ -76,7 +78,7 @@ struct TestTextInputPresenter {
76
78
  }
77
79
 
78
80
  impl TextInputPresenter for TestTextInputPresenter {
79
- fn bind(&self, _editor_host: TextCore, _placeholder_host: FlexBox) {}
81
+ fn bind(&self, _editor_host: TextNode, _placeholder_host: FlexBox) {}
80
82
 
81
83
  fn present(
82
84
  &self,
@@ -1330,6 +1332,114 @@ fn flex_box_backed_controls_preserve_concrete_bind_theme_dx() {
1330
1332
  assert_eq!(invocations.get(), 13);
1331
1333
  }
1332
1334
 
1335
+ #[test]
1336
+ fn newly_covered_composed_controls_keep_one_theme_callback_after_wrapper_drop() {
1337
+ ffi::test::reset();
1338
+ let previous_theme = current_theme();
1339
+ let invocations = Rc::new(Cell::new(0));
1340
+ let invoked_controls = Rc::new(Cell::new(0u32));
1341
+ let root = column();
1342
+
1343
+ let form = form();
1344
+ form.bind_theme({
1345
+ let invocations = invocations.clone();
1346
+ let invoked_controls = invoked_controls.clone();
1347
+ move |control, theme| {
1348
+ invocations.set(invocations.get() + 1);
1349
+ invoked_controls.set(invoked_controls.get() | 1);
1350
+ control.bg_color(theme.colors.surface);
1351
+ }
1352
+ });
1353
+ let group = radio_group();
1354
+ group.bind_theme({
1355
+ let invocations = invocations.clone();
1356
+ let invoked_controls = invoked_controls.clone();
1357
+ move |control, theme| {
1358
+ invocations.set(invocations.get() + 1);
1359
+ invoked_controls.set(invoked_controls.get() | 2);
1360
+ control.bg_color(theme.colors.surface);
1361
+ }
1362
+ });
1363
+ let popup = popup();
1364
+ popup.bind_theme({
1365
+ let invocations = invocations.clone();
1366
+ let invoked_controls = invoked_controls.clone();
1367
+ move |control, theme| {
1368
+ invocations.set(invocations.get() + 1);
1369
+ invoked_controls.set(invoked_controls.get() | 4);
1370
+ control.surface().bg_color(theme.colors.surface);
1371
+ }
1372
+ });
1373
+ let dialog = dialog("Theme", "Dialog");
1374
+ dialog.bind_theme({
1375
+ let invocations = invocations.clone();
1376
+ let invoked_controls = invoked_controls.clone();
1377
+ move |control, _theme| {
1378
+ invocations.set(invocations.get() + 1);
1379
+ invoked_controls.set(invoked_controls.get() | 8);
1380
+ control.content("Theme", "Dialog");
1381
+ }
1382
+ });
1383
+ let menu = ContextMenu::new();
1384
+ menu.bind_theme({
1385
+ let invocations = invocations.clone();
1386
+ let invoked_controls = invoked_controls.clone();
1387
+ move |control, theme| {
1388
+ invocations.set(invocations.get() + 1);
1389
+ invoked_controls.set(invoked_controls.get() | 16);
1390
+ control.appearance(
1391
+ ContextMenuAppearance::new()
1392
+ .panel(SurfaceAppearance::new().background(theme.colors.surface)),
1393
+ );
1394
+ }
1395
+ });
1396
+
1397
+ assert_eq!(invocations.get(), 5);
1398
+ assert_eq!(invoked_controls.replace(0), 31);
1399
+ root.child(&form)
1400
+ .child(&group)
1401
+ .child(&popup)
1402
+ .child(&dialog)
1403
+ .child(&menu);
1404
+ Application::mount(root);
1405
+ drop(form);
1406
+ drop(group);
1407
+ drop(popup);
1408
+ drop(dialog);
1409
+ drop(menu);
1410
+
1411
+ use_custom_theme(generate_theme(false, 0x2A6F97FF));
1412
+ assert_eq!(
1413
+ invoked_controls.get(),
1414
+ 31,
1415
+ "missing retained theme callback bit; form=1 group=2 popup=4 dialog=8 menu=16"
1416
+ );
1417
+ assert_eq!(invocations.get(), 10);
1418
+ use_custom_theme(previous_theme);
1419
+ Application::unmount();
1420
+ }
1421
+
1422
+ #[test]
1423
+ fn composed_theme_subscription_stops_after_unmount_and_releases_retained_root() {
1424
+ let previous_theme = current_theme();
1425
+ let invocations = Rc::new(Cell::new(0));
1426
+ let form = form();
1427
+ let weak_root = form.flex_box_root().downgrade();
1428
+ form.bind_theme({
1429
+ let invocations = invocations.clone();
1430
+ move |_control, _theme| invocations.set(invocations.get() + 1)
1431
+ });
1432
+ Application::mount(form.clone());
1433
+ assert_eq!(invocations.get(), 1);
1434
+
1435
+ Application::unmount();
1436
+ drop(form);
1437
+ use_custom_theme(generate_theme(false, 0x314159FF));
1438
+ assert_eq!(invocations.get(), 1);
1439
+ assert!(weak_root.upgrade().is_none());
1440
+ use_custom_theme(previous_theme);
1441
+ }
1442
+
1333
1443
  #[test]
1334
1444
  fn retained_pressable_and_slider_keep_theme_subscriptions_after_wrappers_drop() {
1335
1445
  ffi::test::reset();
@@ -1676,11 +1786,16 @@ fn button_local_template_replaces_presenter_tree_and_retains_label_updates() {
1676
1786
  fn pressable_labeled_enabled_forwarding_blocks_activation() {
1677
1787
  ffi::test::reset();
1678
1788
  let checkbox_changes = Rc::new(Cell::new(0));
1789
+ let checkbox_clicks = Rc::new(Cell::new(0));
1679
1790
  let checkbox_changes_clone = checkbox_changes.clone();
1791
+ let checkbox_clicks_clone = checkbox_clicks.clone();
1680
1792
  let checkbox = checkbox("Agree");
1681
1793
  checkbox.on_changed(move |_event| {
1682
1794
  checkbox_changes_clone.set(checkbox_changes_clone.get() + 1);
1683
1795
  });
1796
+ checkbox.on_click(move |_event| {
1797
+ checkbox_clicks_clone.set(checkbox_clicks_clone.get() + 1);
1798
+ });
1684
1799
  checkbox.enabled(false);
1685
1800
  Application::mount(checkbox.clone());
1686
1801
  let calls = ffi::test::take_calls();
@@ -1698,14 +1813,20 @@ fn pressable_labeled_enabled_forwarding_blocks_activation() {
1698
1813
  key_event(KeyEventType::Down, "Space", 0);
1699
1814
  key_event(KeyEventType::Up, "Space", 0);
1700
1815
  assert_eq!(checkbox_changes.get(), 0);
1816
+ assert_eq!(checkbox_clicks.get(), 0);
1701
1817
 
1702
1818
  ffi::test::reset();
1703
1819
  let radio_changes = Rc::new(Cell::new(0));
1820
+ let radio_clicks = Rc::new(Cell::new(0));
1704
1821
  let radio_changes_clone = radio_changes.clone();
1822
+ let radio_clicks_clone = radio_clicks.clone();
1705
1823
  let radio = radio_button("Option");
1706
1824
  radio.on_changed(move |_event| {
1707
1825
  radio_changes_clone.set(radio_changes_clone.get() + 1);
1708
1826
  });
1827
+ radio.on_click(move |_event| {
1828
+ radio_clicks_clone.set(radio_clicks_clone.get() + 1);
1829
+ });
1709
1830
  radio.enabled(false);
1710
1831
  Application::mount(radio.clone());
1711
1832
  let radio_ref = radio.retained_node_ref();
@@ -1715,14 +1836,20 @@ fn pressable_labeled_enabled_forwarding_blocks_activation() {
1715
1836
  key_event(KeyEventType::Down, "Space", 0);
1716
1837
  key_event(KeyEventType::Up, "Space", 0);
1717
1838
  assert_eq!(radio_changes.get(), 0);
1839
+ assert_eq!(radio_clicks.get(), 0);
1718
1840
 
1719
1841
  ffi::test::reset();
1720
1842
  let switch_changes = Rc::new(Cell::new(0));
1843
+ let switch_clicks = Rc::new(Cell::new(0));
1721
1844
  let switch_changes_clone = switch_changes.clone();
1845
+ let switch_clicks_clone = switch_clicks.clone();
1722
1846
  let switch = switch("Toggle");
1723
1847
  switch.on_changed(move |_event| {
1724
1848
  switch_changes_clone.set(switch_changes_clone.get() + 1);
1725
1849
  });
1850
+ switch.on_click(move |_event| {
1851
+ switch_clicks_clone.set(switch_clicks_clone.get() + 1);
1852
+ });
1726
1853
  switch.enabled(false);
1727
1854
  Application::mount(switch.clone());
1728
1855
  let switch_ref = switch.retained_node_ref();
@@ -1732,6 +1859,7 @@ fn pressable_labeled_enabled_forwarding_blocks_activation() {
1732
1859
  key_event(KeyEventType::Down, "Space", 0);
1733
1860
  key_event(KeyEventType::Up, "Space", 0);
1734
1861
  assert_eq!(switch_changes.get(), 0);
1862
+ assert_eq!(switch_clicks.get(), 0);
1735
1863
  }
1736
1864
 
1737
1865
  #[test]
@@ -2986,17 +3114,21 @@ fn checkbox_persisted_state_restores_and_emits_without_announcement() {
2986
3114
  )));
2987
3115
 
2988
3116
  let changes = Rc::new(Cell::new(0));
3117
+ let clicks = Rc::new(Cell::new(0));
2989
3118
  let changes_clone = changes.clone();
3119
+ let clicks_clone = clicks.clone();
2990
3120
  let restored = checkbox("Agree");
2991
3121
  restored.node_id("checkbox-persisted");
2992
3122
  restored.tri_state(true);
2993
3123
  restored.on_changed(move |_event| changes_clone.set(changes_clone.get() + 1));
3124
+ restored.on_click(move |_event| clicks_clone.set(clicks_clone.get() + 1));
2994
3125
  Application::mount(restored.clone());
2995
3126
  let _ = ffi::test::take_calls();
2996
3127
  Application::restore_persisted_ui_state();
2997
3128
 
2998
3129
  assert_eq!(restored.checked_state(), CheckState::Mixed);
2999
3130
  assert_eq!(changes.get(), 1);
3131
+ assert_eq!(clicks.get(), 0);
3000
3132
  let calls = ffi::test::take_calls();
3001
3133
  assert!(!calls
3002
3134
  .iter()
@@ -3023,16 +3155,20 @@ fn switch_persisted_state_restores_and_emits_without_announcement() {
3023
3155
  )));
3024
3156
 
3025
3157
  let changes = Rc::new(Cell::new(0));
3158
+ let clicks = Rc::new(Cell::new(0));
3026
3159
  let changes_clone = changes.clone();
3160
+ let clicks_clone = clicks.clone();
3027
3161
  let restored = switch("Toggle");
3028
3162
  restored.node_id("switch-persisted");
3029
3163
  restored.on_changed(move |_event| changes_clone.set(changes_clone.get() + 1));
3164
+ restored.on_click(move |_event| clicks_clone.set(clicks_clone.get() + 1));
3030
3165
  Application::mount(restored.clone());
3031
3166
  let _ = ffi::test::take_calls();
3032
3167
  Application::restore_persisted_ui_state();
3033
3168
 
3034
3169
  assert!(restored.is_checked());
3035
3170
  assert_eq!(changes.get(), 1);
3171
+ assert_eq!(clicks.get(), 0);
3036
3172
  let calls = ffi::test::take_calls();
3037
3173
  assert!(!calls
3038
3174
  .iter()
@@ -4240,6 +4376,94 @@ fn text_input_text_replaced_event_updates_value_and_emits_changed() {
4240
4376
  Application::unmount();
4241
4377
  }
4242
4378
 
4379
+ #[test]
4380
+ fn editable_controls_implement_the_shared_text_editor_surface() {
4381
+ fn require<T: TextEditorSurface + Node + HasFlexBoxRoot + crate::ThemeBindable>() {}
4382
+
4383
+ require::<TextInput>();
4384
+ require::<TextArea>();
4385
+ }
4386
+
4387
+ #[test]
4388
+ fn editable_node_metadata_and_focus_route_to_editor_while_layout_routes_to_shell() {
4389
+ ffi::test::reset();
4390
+ let input = text_input();
4391
+ let focused = Rc::new(Cell::new(false));
4392
+ input
4393
+ .width(320.0, Unit::Pixel)
4394
+ .node_id("routed-editor")
4395
+ .semantic_label("Explicit editor label")
4396
+ .focusable(true, 4)
4397
+ .on_focus_changed({
4398
+ let focused = focused.clone();
4399
+ move |event| focused.set(event.focused)
4400
+ });
4401
+
4402
+ Application::mount(input.clone());
4403
+ let calls = ffi::test::take_calls();
4404
+ let root_handle = input.retained_node_ref().handle().raw();
4405
+ let editor_handle = calls
4406
+ .iter()
4407
+ .find_map(|call| match call {
4408
+ Call::SetNodeId { handle, node_id } if node_id == "routed-editor" => Some(*handle),
4409
+ _ => None,
4410
+ })
4411
+ .expect("expected editor node id");
4412
+
4413
+ assert_ne!(root_handle, editor_handle);
4414
+ assert!(calls.iter().any(|call| matches!(
4415
+ call,
4416
+ Call::SetWidth { handle, value, unit_enum }
4417
+ if *handle == root_handle && *value == 320.0 && *unit_enum == Unit::Pixel as u32
4418
+ )));
4419
+ assert!(calls.iter().any(|call| matches!(
4420
+ call,
4421
+ Call::SetSemanticLabel { handle, label }
4422
+ if *handle == editor_handle && label == "Explicit editor label"
4423
+ )));
4424
+ assert!(calls.iter().any(|call| matches!(
4425
+ call,
4426
+ Call::SetFocusable { handle, focusable, tab_index }
4427
+ if *handle == editor_handle && *focusable && *tab_index == 4
4428
+ )));
4429
+
4430
+ event::__fui_on_focus_changed(editor_handle, true);
4431
+ assert!(focused.get());
4432
+ input.focus_now();
4433
+ let calls = ffi::test::take_calls();
4434
+ assert!(calls.iter().any(|call| matches!(
4435
+ call,
4436
+ Call::RequestFocus { handle } if *handle == editor_handle
4437
+ )));
4438
+ Application::unmount();
4439
+ }
4440
+
4441
+ #[test]
4442
+ fn text_area_prebuild_utf8_selection_replays_scalar_range_as_runtime_bytes() {
4443
+ ffi::test::reset();
4444
+ let input = text_area();
4445
+ input
4446
+ .node_id("prebuild-utf8-area")
4447
+ .text("a😄b")
4448
+ .selection_range(1, 2);
4449
+ Application::mount(input.clone());
4450
+ let calls = ffi::test::take_calls();
4451
+ let editor_handle = calls
4452
+ .iter()
4453
+ .find_map(|call| match call {
4454
+ Call::SetNodeId { handle, node_id } if node_id == "prebuild-utf8-area" => Some(*handle),
4455
+ _ => None,
4456
+ })
4457
+ .expect("expected text area editor node id");
4458
+
4459
+ assert!(calls.iter().any(|call| matches!(
4460
+ call,
4461
+ Call::SetTextSelectionRange { handle, start, end }
4462
+ if *handle == editor_handle && *start == 1 && *end == 5
4463
+ )));
4464
+ Application::unmount();
4465
+ }
4466
+
4243
4467
  #[test]
4244
4468
  fn text_input_selection_range_uses_char_indices_and_sends_utf8_bytes() {
4245
4469
  ffi::test::reset();
@@ -1,9 +1,7 @@
1
1
  use super::internal::text_input_core::TextInputCore;
2
- use super::internal::text_input_presenter::TextInputTemplate;
3
- use super::TextInputColors;
4
- use crate::event::{FocusChangedEventArgs, SelectionChangedEventArgs, TextChangedEventArgs};
2
+ use super::text_editor_surface::impl_text_editor_surface;
3
+ use crate::event::FocusChangedEventArgs;
5
4
  use crate::node::{FlexBox, HasFlexBoxRoot, Node, NodeRef, ScrollBarVisibility};
6
- use crate::FontFamily;
7
5
  use std::any::Any;
8
6
  use std::rc::Rc;
9
7
 
@@ -25,116 +23,6 @@ impl TextArea {
25
23
  Self { core }
26
24
  }
27
25
 
28
- pub fn text(&self, value: impl Into<String>) -> &Self {
29
- self.core.text(value);
30
- self
31
- }
32
-
33
- pub fn value(&self) -> String {
34
- self.core.value()
35
- }
36
-
37
- pub fn selection_start(&self) -> u32 {
38
- self.core.selection_start()
39
- }
40
-
41
- pub fn selection_end(&self) -> u32 {
42
- self.core.selection_end()
43
- }
44
-
45
- pub fn selection_start_byte_offset(&self) -> u32 {
46
- self.core.selection_start_byte_offset()
47
- }
48
-
49
- pub fn selection_end_byte_offset(&self) -> u32 {
50
- self.core.selection_end_byte_offset()
51
- }
52
-
53
- pub fn placeholder(&self, value: impl Into<String>) -> &Self {
54
- self.core.placeholder(value);
55
- self
56
- }
57
-
58
- pub fn max_chars(&self, limit: i32) -> &Self {
59
- self.core.max_chars(limit);
60
- self
61
- }
62
-
63
- pub fn read_only(&self, flag: bool) -> &Self {
64
- self.core.read_only(flag);
65
- self
66
- }
67
-
68
- pub fn accepts_tab(&self, flag: bool) -> &Self {
69
- self.core.accepts_tab(flag);
70
- self
71
- }
72
-
73
- pub fn selection_range(&self, start: u32, end: u32) -> &Self {
74
- self.core.selection_range(start, end);
75
- self
76
- }
77
-
78
- pub fn caret(&self, position: u32) -> &Self {
79
- self.core.caret(position);
80
- self
81
- }
82
-
83
- pub fn caret_to_end(&self) -> &Self {
84
- self.core.caret_to_end();
85
- self
86
- }
87
-
88
- pub fn colors(&self, colors: TextInputColors) -> &Self {
89
- self.core.colors(colors);
90
- self
91
- }
92
-
93
- pub fn clear_colors(&self) -> &Self {
94
- self.core.clear_colors();
95
- self
96
- }
97
-
98
- pub fn template(&self, template: Rc<dyn TextInputTemplate>) -> &Self {
99
- self.core.template(template);
100
- self
101
- }
102
-
103
- pub fn clear_template(&self) -> &Self {
104
- self.core.clear_template();
105
- self
106
- }
107
-
108
- pub fn enabled(&self, enabled: bool) -> &Self {
109
- self.core.enabled(enabled);
110
- self
111
- }
112
-
113
- pub fn focusable(&self, enabled: bool, tab_index: i32) -> &Self {
114
- self.core.focusable(enabled, tab_index);
115
- self
116
- }
117
-
118
- pub fn node_id(&self, id: impl Into<String>) -> &Self {
119
- self.core.node_id(id);
120
- self
121
- }
122
-
123
- pub fn line_height(&self, value: f32) -> &Self {
124
- self.core.line_height(value);
125
- self
126
- }
127
-
128
- pub fn font_family(&self, family: FontFamily) -> &Self {
129
- self.core.font_family(family);
130
- self
131
- }
132
-
133
- pub fn font_size(&self, size: f32) -> &Self {
134
- self.core.font_size(size);
135
- self
136
- }
137
-
138
26
  pub fn wrapping(&self, flag: bool) -> &Self {
139
27
  self.core.wrapping(flag);
140
28
  self
@@ -150,34 +38,6 @@ impl TextArea {
150
38
  self
151
39
  }
152
40
 
153
- pub fn on_changed(&self, handler: impl Fn(TextChangedEventArgs) + 'static) -> &Self {
154
- self.core.on_changed(handler);
155
- self
156
- }
157
-
158
- pub fn on_text_changed(&self, handler: impl Fn(TextChangedEventArgs) + 'static) -> &Self {
159
- self.core.on_text_changed(handler);
160
- self
161
- }
162
-
163
- pub fn on_selection_changed(
164
- &self,
165
- handler: impl Fn(SelectionChangedEventArgs) + 'static,
166
- ) -> &Self {
167
- self.core.on_selection_changed(handler);
168
- self
169
- }
170
-
171
- pub fn on_focus_changed(&self, handler: impl Fn(FocusChangedEventArgs) + 'static) -> &Self {
172
- self.core.on_focus_changed(handler);
173
- self
174
- }
175
-
176
- pub fn focus_now(&self) -> &Self {
177
- self.core.focus_now();
178
- self
179
- }
180
-
181
41
  pub fn scroll_offset_x(&self) -> f32 {
182
42
  self.core.scroll_offset_x()
183
43
  }
@@ -192,6 +52,8 @@ impl TextArea {
192
52
  }
193
53
  }
194
54
 
55
+ impl_text_editor_surface!(TextArea);
56
+
195
57
  impl HasFlexBoxRoot for TextArea {
196
58
  fn flex_box_root(&self) -> &FlexBox {
197
59
  self.core.flex_box_root()
@@ -214,6 +76,30 @@ impl crate::ThemeBindable for TextArea {
214
76
  }
215
77
 
216
78
  impl Node for TextArea {
79
+ fn apply_node_id(&self, node_id: String) {
80
+ self.core.node_id(node_id);
81
+ }
82
+
83
+ fn apply_semantic_label(&self, label: String) {
84
+ self.core.semantic_label(label);
85
+ }
86
+
87
+ fn apply_focusable(&self, enabled: bool, tab_index: i32) {
88
+ self.core.focusable(enabled, tab_index);
89
+ }
90
+
91
+ fn apply_focus_now(&self) {
92
+ self.core.focus_now();
93
+ }
94
+
95
+ fn apply_enabled(&self, enabled: bool) {
96
+ self.core.enabled(enabled);
97
+ }
98
+
99
+ fn apply_focus_changed_handler(&self, handler: Rc<dyn Fn(FocusChangedEventArgs)>) {
100
+ self.core.set_focus_changed_callback(handler);
101
+ }
102
+
217
103
  fn retained_node_ref(&self) -> NodeRef {
218
104
  let core = self.core.clone();
219
105
  self.core