@effindomv2/fui-rs 0.1.23 → 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 (45) 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 +444 -64
  32. package/src/node/flex_box.rs +39 -47
  33. package/src/node/grid.rs +43 -128
  34. package/src/node/helpers.rs +34 -43
  35. package/src/node/image.rs +47 -56
  36. package/src/node/mod.rs +398 -73
  37. package/src/node/scroll_bar.rs +6 -0
  38. package/src/node/scroll_box.rs +8 -21
  39. package/src/node/scroll_view.rs +144 -13
  40. package/src/node/svg_node.rs +47 -66
  41. package/src/node/text_node.rs +358 -79
  42. package/src/node/virtual_list.rs +17 -0
  43. package/src/popup_presenter.rs +31 -0
  44. package/src/text.rs +59 -0
  45. package/src/text_indices.rs +48 -0
@@ -151,6 +151,12 @@ struct ScrollBarInner {
151
151
  }
152
152
 
153
153
  #[derive(Clone)]
154
+ /// Scroll chrome helper matching FUI-AS `ScrollBar`.
155
+ ///
156
+ /// This is not a retained `Node`; attach [`render`](Self::render) to a retained
157
+ /// tree. It follows the active theme automatically. Per-instance theme
158
+ /// callbacks belong on the rendered `FlexBox`, avoiding a root/state ownership
159
+ /// cycle in this helper.
154
160
  pub struct ScrollBar {
155
161
  inner: Rc<ScrollBarInner>,
156
162
  }
@@ -124,27 +124,6 @@ impl ScrollBox {
124
124
  self.inner.horizontal_scrollbar.clone()
125
125
  }
126
126
 
127
- pub fn child<T: Node>(&self, node: &T) -> &Self {
128
- self.inner.viewport.child(node);
129
- self.bind_content_scroll_proxy_targets();
130
- self
131
- }
132
-
133
- pub fn children<I, C>(&self, nodes: I) -> &Self
134
- where
135
- I: IntoIterator<Item = C>,
136
- C: Into<Child>,
137
- {
138
- for node in nodes {
139
- self.inner
140
- .viewport
141
- .retained_node_ref()
142
- .append_child_ref(&node.into().node_ref);
143
- }
144
- self.bind_content_scroll_proxy_targets();
145
- self
146
- }
147
-
148
127
  pub fn scroll_enabled_x(&self, enabled: bool) -> &Self {
149
128
  self.inner.horizontal_scroll_enabled.set(enabled);
150
129
  self.inner.viewport.scroll_enabled_x(enabled);
@@ -518,6 +497,14 @@ impl HasFlexBoxRoot for ScrollBox {
518
497
  fn flex_box_root(&self) -> &FlexBox {
519
498
  &self.inner.root
520
499
  }
500
+
501
+ fn append_flex_box_surface_child(&self, child: NodeRef) {
502
+ self.inner
503
+ .viewport
504
+ .retained_node_ref()
505
+ .append_child_ref(&child);
506
+ self.bind_content_scroll_proxy_targets();
507
+ }
521
508
  }
522
509
 
523
510
  impl ThemeBindable for ScrollBox {
@@ -36,8 +36,6 @@ impl ScrollView {
36
36
  props: Rc::new(RefCell::new(ScrollViewProps {
37
37
  width: None,
38
38
  height: None,
39
- bg_color: None,
40
- padding: None,
41
39
  enable_scroll_x: true,
42
40
  enable_scroll_y: true,
43
41
  friction: None,
@@ -47,7 +45,7 @@ impl ScrollView {
47
45
  persist_scroll: true,
48
46
  transitions: None,
49
47
  })),
50
- bound_scroll_state: Rc::new(RefCell::new(None)),
48
+ bound_scroll_state: Rc::new(RefCell::new(Some(ScrollState::new()))),
51
49
  active_animations: Rc::new(RefCell::new(ScrollViewAnimationState::default())),
52
50
  }
53
51
  }
@@ -76,12 +74,32 @@ impl ScrollView {
76
74
  self
77
75
  }
78
76
 
77
+ pub fn scroll_state(&self) -> ScrollState {
78
+ self.bound_scroll_state
79
+ .borrow()
80
+ .clone()
81
+ .expect("ScrollView always owns a scroll state")
82
+ }
83
+
84
+ pub fn is_vertical_scroll_enabled(&self) -> bool {
85
+ self.props.borrow().enable_scroll_y
86
+ }
87
+
88
+ pub fn is_horizontal_scroll_enabled(&self) -> bool {
89
+ self.props.borrow().enable_scroll_x
90
+ }
91
+
79
92
  pub fn width(&self, width: f32, unit: Unit) -> &Self {
80
93
  self.props.borrow_mut().width = Some((width, unit));
81
94
  self.set_viewport_width_if_pixel(width, unit);
82
95
  let mut core = self.core.borrow_mut();
83
96
  core.behavior.fill_width = false;
84
97
  core.behavior.fill_width_percent = None;
98
+ drop(core);
99
+ if self.has_built_handle() {
100
+ ui::set_width(self.handle().raw(), width, unit as u32);
101
+ self.notify_retained_layout_mutation();
102
+ }
85
103
  self
86
104
  }
87
105
 
@@ -96,6 +114,11 @@ impl ScrollView {
96
114
  let mut core = self.core.borrow_mut();
97
115
  core.behavior.fill_height = false;
98
116
  core.behavior.fill_height_percent = None;
117
+ drop(core);
118
+ if self.has_built_handle() {
119
+ ui::set_height(self.handle().raw(), height, unit as u32);
120
+ self.notify_retained_layout_mutation();
121
+ }
99
122
  self
100
123
  }
101
124
 
@@ -109,6 +132,11 @@ impl ScrollView {
109
132
  let mut core = self.core.borrow_mut();
110
133
  core.behavior.fill_width = true;
111
134
  core.behavior.fill_width_percent = None;
135
+ drop(core);
136
+ if self.has_built_handle() {
137
+ ui::set_fill_width(self.handle().raw(), true);
138
+ self.notify_retained_layout_mutation();
139
+ }
112
140
  self
113
141
  }
114
142
 
@@ -117,6 +145,11 @@ impl ScrollView {
117
145
  let mut core = self.core.borrow_mut();
118
146
  core.behavior.fill_height = true;
119
147
  core.behavior.fill_height_percent = None;
148
+ drop(core);
149
+ if self.has_built_handle() {
150
+ ui::set_fill_height(self.handle().raw(), true);
151
+ self.notify_retained_layout_mutation();
152
+ }
120
153
  self
121
154
  }
122
155
 
@@ -126,13 +159,74 @@ impl ScrollView {
126
159
  self
127
160
  }
128
161
 
129
- pub fn bg_color(&self, color: u32) -> &Self {
130
- self.props.borrow_mut().bg_color = Some(color);
162
+ pub fn fill_width_percent(&self, percent: f32) -> &Self {
163
+ self.props.borrow_mut().width = None;
164
+ let mut core = self.core.borrow_mut();
165
+ core.behavior.fill_width = false;
166
+ core.behavior.fill_width_percent = Some(percent);
167
+ drop(core);
168
+ if self.has_built_handle() {
169
+ ui::set_fill_width_percent(self.handle().raw(), percent);
170
+ self.notify_retained_layout_mutation();
171
+ }
172
+ self
173
+ }
174
+
175
+ pub fn fill_height_percent(&self, percent: f32) -> &Self {
176
+ self.props.borrow_mut().height = None;
177
+ let mut core = self.core.borrow_mut();
178
+ core.behavior.fill_height = false;
179
+ core.behavior.fill_height_percent = Some(percent);
180
+ drop(core);
181
+ if self.has_built_handle() {
182
+ ui::set_fill_height_percent(self.handle().raw(), percent);
183
+ self.notify_retained_layout_mutation();
184
+ }
185
+ self
186
+ }
187
+
188
+ pub fn min_width(&self, value: f32, unit: Unit) -> &Self {
189
+ self.core.borrow_mut().behavior.min_width = Some((value, unit));
190
+ if self.has_built_handle() {
191
+ ui::set_min_width(self.handle().raw(), value, unit as u32);
192
+ self.notify_retained_layout_mutation();
193
+ }
194
+ self
195
+ }
196
+
197
+ pub fn max_width(&self, value: f32, unit: Unit) -> &Self {
198
+ self.core.borrow_mut().behavior.max_width = Some((value, unit));
199
+ if self.has_built_handle() {
200
+ ui::set_max_width(self.handle().raw(), value, unit as u32);
201
+ self.notify_retained_layout_mutation();
202
+ }
203
+ self
204
+ }
205
+
206
+ pub fn min_height(&self, value: f32, unit: Unit) -> &Self {
207
+ self.core.borrow_mut().behavior.min_height = Some((value, unit));
208
+ if self.has_built_handle() {
209
+ ui::set_min_height(self.handle().raw(), value, unit as u32);
210
+ self.notify_retained_layout_mutation();
211
+ }
212
+ self
213
+ }
214
+
215
+ pub fn max_height(&self, value: f32, unit: Unit) -> &Self {
216
+ self.core.borrow_mut().behavior.max_height = Some((value, unit));
217
+ if self.has_built_handle() {
218
+ ui::set_max_height(self.handle().raw(), value, unit as u32);
219
+ self.notify_retained_layout_mutation();
220
+ }
131
221
  self
132
222
  }
133
223
 
134
- pub fn padding(&self, left: f32, top: f32, right: f32, bottom: f32) -> &Self {
135
- self.props.borrow_mut().padding = Some((left, top, right, bottom));
224
+ pub fn flex_basis(&self, value: f32) -> &Self {
225
+ self.core.borrow_mut().behavior.flex_basis = Some(value);
226
+ if self.has_built_handle() {
227
+ ui::set_flex_basis(self.handle().raw(), value);
228
+ self.notify_retained_layout_mutation();
229
+ }
136
230
  self
137
231
  }
138
232
 
@@ -142,6 +236,10 @@ impl ScrollView {
142
236
  props.enable_scroll_y = enabled_y;
143
237
  self.retained_node_ref()
144
238
  .set_scroll_routing_enabled(enabled_x, enabled_y);
239
+ if self.has_built_handle() {
240
+ ui::set_scroll_enabled(self.handle().raw(), enabled_x, enabled_y);
241
+ self.notify_retained_mutation();
242
+ }
145
243
  self
146
244
  }
147
245
 
@@ -154,6 +252,10 @@ impl ScrollView {
154
252
  .unwrap_or(true);
155
253
  self.retained_node_ref()
156
254
  .set_scroll_routing_enabled(enabled, enabled_y);
255
+ if self.has_built_handle() {
256
+ ui::set_scroll_enabled(self.handle().raw(), enabled, enabled_y);
257
+ self.notify_retained_mutation();
258
+ }
157
259
  self
158
260
  }
159
261
 
@@ -166,11 +268,19 @@ impl ScrollView {
166
268
  .unwrap_or(true);
167
269
  self.retained_node_ref()
168
270
  .set_scroll_routing_enabled(enabled_x, enabled);
271
+ if self.has_built_handle() {
272
+ ui::set_scroll_enabled(self.handle().raw(), enabled_x, enabled);
273
+ self.notify_retained_mutation();
274
+ }
169
275
  self
170
276
  }
171
277
 
172
- pub fn scroll_friction(&self, friction: f32) -> &Self {
278
+ pub fn friction(&self, friction: f32) -> &Self {
173
279
  self.props.borrow_mut().friction = Some(friction);
280
+ if self.has_built_handle() {
281
+ ui::set_scroll_friction(self.handle().raw(), friction);
282
+ self.notify_retained_mutation();
283
+ }
174
284
  self
175
285
  }
176
286
 
@@ -204,16 +314,16 @@ impl ScrollView {
204
314
  self
205
315
  }
206
316
 
207
- pub fn content_size(&self, width: f32, height: f32) -> &Self {
317
+ pub fn scroll_content_size(&self, width: f32, height: f32) -> &Self {
208
318
  self.props.borrow_mut().content_size = Some((width, height));
209
319
  self.set_content_size_metrics(width, height);
320
+ if self.has_built_handle() {
321
+ ui::set_scroll_content_size(self.handle().raw(), width, height);
322
+ self.notify_retained_mutation();
323
+ }
210
324
  self
211
325
  }
212
326
 
213
- pub fn scroll_content_size(&self, width: f32, height: f32) -> &Self {
214
- self.content_size(width, height)
215
- }
216
-
217
327
  pub fn persist_scroll(&self, persist: bool) -> &Self {
218
328
  self.props.borrow_mut().persist_scroll = persist;
219
329
  self
@@ -493,6 +603,27 @@ impl Node for ScrollView {
493
603
  }
494
604
  }
495
605
 
606
+ impl ThemeBindable for ScrollView {
607
+ fn theme_binding_node(&self) -> NodeRef {
608
+ self.retained_node_ref()
609
+ }
610
+
611
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
612
+ let core = Rc::downgrade(&self.core);
613
+ let props = Rc::downgrade(&self.props);
614
+ let bound_scroll_state = Rc::downgrade(&self.bound_scroll_state);
615
+ let active_animations = Rc::downgrade(&self.active_animations);
616
+ Box::new(move || {
617
+ Some(Self {
618
+ core: core.upgrade()?,
619
+ props: props.upgrade()?,
620
+ bound_scroll_state: bound_scroll_state.upgrade()?,
621
+ active_animations: active_animations.upgrade()?,
622
+ })
623
+ })
624
+ }
625
+ }
626
+
496
627
  #[cfg(test)]
497
628
  mod tests {
498
629
  use super::*;
@@ -40,24 +40,23 @@ impl Default for SvgState {
40
40
 
41
41
  #[derive(Clone)]
42
42
  pub struct SvgNode {
43
- core: Rc<RefCell<NodeCore>>,
43
+ base: FlexBox,
44
44
  props: Rc<RefCell<SvgProps>>,
45
45
  state: Rc<RefCell<SvgState>>,
46
46
  }
47
47
 
48
48
  impl SvgNode {
49
49
  pub fn new(svg_id: u32) -> Self {
50
+ let base = FlexBox::default();
51
+ base.core.borrow_mut().kind = NodeKind::Svg;
50
52
  let node = Self {
51
- core: Rc::new(RefCell::new(NodeCore::new(NodeKind::Svg))),
53
+ base,
52
54
  props: Rc::new(RefCell::new(SvgProps {
53
- width: None,
54
- height: None,
55
55
  svg_id,
56
56
  source_url: None,
57
57
  tint_color: 0,
58
58
  sampling_kind: ImageSamplingKind::Linear,
59
59
  max_aniso: 0,
60
- opacity: None,
61
60
  })),
62
61
  state: Rc::new(RefCell::new(SvgState {
63
62
  requested_width_value: 0.0,
@@ -141,10 +140,6 @@ impl SvgNode {
141
140
  self
142
141
  }
143
142
 
144
- pub fn source_url(&self, url: impl Into<String>) -> &Self {
145
- self.source(url)
146
- }
147
-
148
143
  pub fn clear_source(&self) -> &Self {
149
144
  self.release_owned_source_asset();
150
145
  {
@@ -166,10 +161,6 @@ impl SvgNode {
166
161
  self
167
162
  }
168
163
 
169
- pub fn clear_source_url(&self) -> &Self {
170
- self.clear_source()
171
- }
172
-
173
164
  pub fn tint(&self, tint_color: u32) -> &Self {
174
165
  self.props.borrow_mut().tint_color = tint_color;
175
166
  if self.has_built_handle() {
@@ -179,15 +170,6 @@ impl SvgNode {
179
170
  self
180
171
  }
181
172
 
182
- pub fn opacity(&self, value: f32) -> &Self {
183
- self.props.borrow_mut().opacity = Some(value.clamp(0.0, 1.0));
184
- if self.has_built_handle() {
185
- self.build_self();
186
- self.notify_retained_mutation();
187
- }
188
- self
189
- }
190
-
191
173
  pub fn alt_text(&self, value: impl Into<String>) -> &Self {
192
174
  let value = value.into();
193
175
  self.semantic_role(SemanticRole::Image);
@@ -207,17 +189,6 @@ impl SvgNode {
207
189
  self
208
190
  }
209
191
 
210
- pub fn interactive(&self, interactive: bool) -> &Self {
211
- self.core.borrow_mut().behavior.interactive = interactive;
212
- self
213
- }
214
-
215
- pub fn on_click(&self, handler: impl Fn(&mut PointerEventArgs) + 'static) -> &Self {
216
- self.core.borrow_mut().handlers.pointer_click = Some(Rc::new(handler));
217
- self.retained_node_ref().require_interactive();
218
- self
219
- }
220
-
221
192
  pub fn asset_state_signal(&self) -> crate::assets::AssetStateSignal {
222
193
  get_svg_asset_state(self.props.borrow().svg_id)
223
194
  }
@@ -284,8 +255,6 @@ impl SvgNode {
284
255
  let asset_width = self.asset_width();
285
256
  let asset_height = self.asset_height();
286
257
  let has_intrinsic_size = asset_width > 0.0 && asset_height > 0.0;
287
- let mut props = self.props.borrow_mut();
288
-
289
258
  if has_requested_width {
290
259
  let mut resolved_width_value = requested_width_value;
291
260
  let mut resolved_width_unit = requested_width_unit;
@@ -297,14 +266,7 @@ impl SvgNode {
297
266
  }
298
267
  resolved_width_unit = Unit::Pixel;
299
268
  }
300
- props.width = Some((resolved_width_value, resolved_width_unit));
301
- if self.has_built_handle() {
302
- ui::set_width(
303
- self.handle().raw(),
304
- resolved_width_value,
305
- resolved_width_unit as u32,
306
- );
307
- }
269
+ self.base.width(resolved_width_value, resolved_width_unit);
308
270
  }
309
271
 
310
272
  if has_requested_height {
@@ -318,19 +280,8 @@ impl SvgNode {
318
280
  }
319
281
  resolved_height_unit = Unit::Pixel;
320
282
  }
321
- props.height = Some((resolved_height_value, resolved_height_unit));
322
- if self.has_built_handle() {
323
- ui::set_height(
324
- self.handle().raw(),
325
- resolved_height_value,
326
- resolved_height_unit as u32,
327
- );
328
- }
329
- }
330
-
331
- drop(props);
332
- if self.has_built_handle() {
333
- self.notify_retained_layout_mutation();
283
+ self.base
284
+ .height(resolved_height_value, resolved_height_unit);
334
285
  }
335
286
  }
336
287
 
@@ -348,16 +299,16 @@ impl SvgNode {
348
299
  }
349
300
  }
350
301
 
351
- let core_weak: Weak<RefCell<NodeCore>> = Rc::downgrade(&self.core);
302
+ let base_weak = self.base.downgrade();
352
303
  let props_weak: Weak<RefCell<SvgProps>> = Rc::downgrade(&self.props);
353
304
  let state_weak: Weak<RefCell<SvgState>> = Rc::downgrade(&self.state);
354
305
  let callback: Callback = Rc::new(move || {
355
- if let (Some(core), Some(props), Some(state)) = (
356
- core_weak.upgrade(),
306
+ if let (Some(base), Some(props), Some(state)) = (
307
+ base_weak.upgrade(),
357
308
  props_weak.upgrade(),
358
309
  state_weak.upgrade(),
359
310
  ) {
360
- let node = SvgNode { core, props, state };
311
+ let node = SvgNode { base, props, state };
361
312
  node.apply_resolved_sizing();
362
313
  }
363
314
  });
@@ -383,16 +334,46 @@ impl SvgNode {
383
334
 
384
335
  impl Node for SvgNode {
385
336
  fn retained_node_ref(&self) -> NodeRef {
386
- NodeRef::from_node(self.core.clone(), self.clone())
337
+ NodeRef::from_node(self.base.core.clone(), self.clone())
387
338
  }
388
339
 
389
340
  fn build_self(&self) {
390
341
  self.apply_resolved_sizing();
391
- apply_svg_props(
392
- self.handle(),
393
- &self.props.borrow(),
394
- self.core.borrow().behavior.clone(),
395
- );
342
+ self.base.build_self();
343
+ apply_svg_props(self.handle(), &self.props.borrow());
344
+ }
345
+ }
346
+
347
+ impl HasFlexBoxRoot for SvgNode {
348
+ fn flex_box_root(&self) -> &FlexBox {
349
+ &self.base
350
+ }
351
+
352
+ fn set_flex_box_surface_width(&self, value: f32, unit: Unit) {
353
+ self.width(value, unit);
354
+ }
355
+
356
+ fn set_flex_box_surface_height(&self, value: f32, unit: Unit) {
357
+ self.height(value, unit);
358
+ }
359
+ }
360
+
361
+ impl ThemeBindable for SvgNode {
362
+ fn theme_binding_node(&self) -> NodeRef {
363
+ self.retained_node_ref()
364
+ }
365
+
366
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
367
+ let base = self.base.downgrade();
368
+ let props = Rc::downgrade(&self.props);
369
+ let state = Rc::downgrade(&self.state);
370
+ Box::new(move || {
371
+ Some(Self {
372
+ base: base.upgrade()?,
373
+ props: props.upgrade()?,
374
+ state: state.upgrade()?,
375
+ })
376
+ })
396
377
  }
397
378
  }
398
379