@effindomv2/fui-rs 0.1.12 → 0.1.13

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.
@@ -1,4 +1,6 @@
1
- use super::internal::pressable_labeled_control::PressableLabeledControlState;
1
+ use super::internal::pressable_labeled_control::{
2
+ PressableLabeledControlState, WeakPressableLabeledControl,
3
+ };
2
4
  use super::internal::radio_indicator_presenter::{
3
5
  create_default_radio_indicator_presenter, RadioIndicatorPresenter, RadioIndicatorTemplate,
4
6
  RadioIndicatorVisualState,
@@ -250,9 +252,24 @@ impl HasFlexBoxRoot for RadioButton {
250
252
  }
251
253
  }
252
254
 
255
+ impl ThemeBindable for RadioButton {
256
+ fn theme_binding_node(&self) -> NodeRef {
257
+ self.root.retained_node_ref()
258
+ }
259
+
260
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
261
+ let target = RadioEventTarget::from_radio(self);
262
+ Box::new(move || target.upgrade())
263
+ }
264
+ }
265
+
253
266
  #[derive(Clone)]
254
267
  struct RadioEventTarget {
268
+ base: WeakPressableLabeledControl,
255
269
  presenter: Rc<RefCell<Rc<dyn RadioIndicatorPresenter>>>,
270
+ template_override: Rc<RefCell<Option<Rc<dyn RadioIndicatorTemplate>>>>,
271
+ sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
272
+ value_text: String,
256
273
  checked: Rc<Cell<bool>>,
257
274
  colors_value: Rc<Cell<Option<LabeledControlColors>>>,
258
275
  changed: Rc<RefCell<Option<RadioButtonChangedCallback>>>,
@@ -263,7 +280,11 @@ struct RadioEventTarget {
263
280
  impl RadioEventTarget {
264
281
  fn from_radio(radio: &RadioButton) -> Self {
265
282
  Self {
283
+ base: radio.base.downgrade(),
266
284
  presenter: radio.indicator_presenter.clone(),
285
+ template_override: radio.template_override.clone(),
286
+ sizing_value: radio.sizing_value.clone(),
287
+ value_text: radio.value_text.clone(),
267
288
  checked: radio.checked.clone(),
268
289
  colors_value: radio.colors_value.clone(),
269
290
  changed: radio.changed.clone(),
@@ -272,6 +293,23 @@ impl RadioEventTarget {
272
293
  }
273
294
  }
274
295
 
296
+ fn upgrade(&self) -> Option<RadioButton> {
297
+ let base = self.base.upgrade()?;
298
+ Some(RadioButton {
299
+ root: base.root(),
300
+ base,
301
+ indicator_presenter: self.presenter.clone(),
302
+ template_override: self.template_override.clone(),
303
+ sizing_value: self.sizing_value.clone(),
304
+ colors_value: self.colors_value.clone(),
305
+ value_text: self.value_text.clone(),
306
+ checked: self.checked.clone(),
307
+ changed: self.changed.clone(),
308
+ owner_group: self.owner_group.clone(),
309
+ weak_root: self.weak_root.clone(),
310
+ })
311
+ }
312
+
275
313
  fn activate(&self, base_state: PressableLabeledControlState) {
276
314
  if let Some(group) = self.owner_group() {
277
315
  if let Some(handle) = upgraded_handle(&self.weak_root) {
@@ -80,3 +80,20 @@ impl HasFlexBoxRoot for SelectionArea {
80
80
  &self.root
81
81
  }
82
82
  }
83
+
84
+ impl ThemeBindable for SelectionArea {
85
+ fn theme_binding_node(&self) -> NodeRef {
86
+ self.root.retained_node_ref()
87
+ }
88
+
89
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
90
+ let weak_root = self.root.downgrade();
91
+ let signal = self.selected_text_signal.clone();
92
+ Box::new(move || {
93
+ Some(SelectionArea {
94
+ root: weak_root.upgrade()?,
95
+ selected_text_signal: signal.clone(),
96
+ })
97
+ })
98
+ }
99
+ }
@@ -415,11 +415,24 @@ impl HasFlexBoxRoot for Slider {
415
415
  }
416
416
  }
417
417
 
418
+ impl ThemeBindable for Slider {
419
+ fn theme_binding_node(&self) -> NodeRef {
420
+ self.root.retained_node_ref()
421
+ }
422
+
423
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
424
+ let target = SliderEventTarget::from_slider(self);
425
+ Box::new(move || target.upgrade())
426
+ }
427
+ }
428
+
418
429
  #[derive(Clone)]
419
430
  struct SliderEventTarget {
420
431
  weak_root: Rc<WeakNodeRef>,
421
432
  weak_flex_root: WeakFlexBox,
422
433
  slider_presenter: Rc<RefCell<Rc<dyn SliderPresenter>>>,
434
+ template_override: Rc<RefCell<Option<Rc<dyn SliderTemplate>>>>,
435
+ sizing_value: Rc<Cell<Option<SliderSizing>>>,
423
436
  min: Rc<Cell<f32>>,
424
437
  max: Rc<Cell<f32>>,
425
438
  step: Rc<Cell<f32>>,
@@ -439,6 +452,8 @@ impl SliderEventTarget {
439
452
  weak_root: slider.weak_root.clone(),
440
453
  weak_flex_root: slider.weak_flex_root.clone(),
441
454
  slider_presenter: slider.slider_presenter.clone(),
455
+ template_override: slider.template_override.clone(),
456
+ sizing_value: slider.sizing_value.clone(),
442
457
  min: slider.min.clone(),
443
458
  max: slider.max.clone(),
444
459
  step: slider.step.clone(),
@@ -453,6 +468,28 @@ impl SliderEventTarget {
453
468
  }
454
469
  }
455
470
 
471
+ fn upgrade(&self) -> Option<Slider> {
472
+ Some(Slider {
473
+ root: self.weak_flex_root.upgrade()?,
474
+ slider_presenter: self.slider_presenter.clone(),
475
+ template_override: self.template_override.clone(),
476
+ sizing_value: self.sizing_value.clone(),
477
+ colors_value: self.colors_value.clone(),
478
+ min: self.min.clone(),
479
+ max: self.max.clone(),
480
+ step: self.step.clone(),
481
+ value: self.value.clone(),
482
+ length: self.length.clone(),
483
+ orientation: self.orientation.clone(),
484
+ changed: self.changed.clone(),
485
+ hovered_state: self.hovered_state.clone(),
486
+ dragging_state: self.dragging_state.clone(),
487
+ focused_state: self.focused_state.clone(),
488
+ weak_root: self.weak_root.clone(),
489
+ weak_flex_root: self.weak_flex_root.clone(),
490
+ })
491
+ }
492
+
456
493
  fn is_enabled(&self) -> bool {
457
494
  self.weak_flex_root
458
495
  .upgrade()
@@ -1,4 +1,6 @@
1
- use super::internal::pressable_labeled_control::PressableLabeledControlState;
1
+ use super::internal::pressable_labeled_control::{
2
+ PressableLabeledControlState, WeakPressableLabeledControl,
3
+ };
2
4
  use super::internal::switch_indicator_presenter::{
3
5
  create_default_switch_indicator_presenter, SwitchIndicatorPresenter, SwitchIndicatorTemplate,
4
6
  SwitchIndicatorVisualState,
@@ -241,9 +243,23 @@ impl HasFlexBoxRoot for Switch {
241
243
  }
242
244
  }
243
245
 
246
+ impl ThemeBindable for Switch {
247
+ fn theme_binding_node(&self) -> NodeRef {
248
+ self.root.retained_node_ref()
249
+ }
250
+
251
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
252
+ let target = SwitchEventTarget::from_switch(self);
253
+ Box::new(move || target.upgrade())
254
+ }
255
+ }
256
+
244
257
  #[derive(Clone)]
245
258
  struct SwitchEventTarget {
259
+ base: WeakPressableLabeledControl,
246
260
  presenter: Rc<RefCell<Rc<dyn SwitchIndicatorPresenter>>>,
261
+ template_override: Rc<RefCell<Option<Rc<dyn SwitchIndicatorTemplate>>>>,
262
+ sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
247
263
  checked: Rc<Cell<bool>>,
248
264
  colors_value: Rc<Cell<Option<LabeledControlColors>>>,
249
265
  changed: Rc<RefCell<Option<SwitchChangedCallback>>>,
@@ -253,7 +269,10 @@ struct SwitchEventTarget {
253
269
  impl SwitchEventTarget {
254
270
  fn from_switch(switch: &Switch) -> Self {
255
271
  Self {
272
+ base: switch.base.downgrade(),
256
273
  presenter: switch.indicator_presenter.clone(),
274
+ template_override: switch.template_override.clone(),
275
+ sizing_value: switch.sizing_value.clone(),
257
276
  checked: switch.checked.clone(),
258
277
  colors_value: switch.colors_value.clone(),
259
278
  changed: switch.changed.clone(),
@@ -261,6 +280,21 @@ impl SwitchEventTarget {
261
280
  }
262
281
  }
263
282
 
283
+ fn upgrade(&self) -> Option<Switch> {
284
+ let base = self.base.upgrade()?;
285
+ Some(Switch {
286
+ root: base.root(),
287
+ base,
288
+ indicator_presenter: self.presenter.clone(),
289
+ template_override: self.template_override.clone(),
290
+ sizing_value: self.sizing_value.clone(),
291
+ colors_value: self.colors_value.clone(),
292
+ checked: self.checked.clone(),
293
+ changed: self.changed.clone(),
294
+ weak_root: self.weak_root.clone(),
295
+ })
296
+ }
297
+
264
298
  fn activate(&self, base_state: PressableLabeledControlState) {
265
299
  apply_switch_checked(
266
300
  &self.presenter,
@@ -1185,6 +1185,149 @@ fn nav_link_bind_theme_preserves_concrete_control_dx_and_retained_lifetime() {
1185
1185
  use_custom_theme(previous_theme);
1186
1186
  }
1187
1187
 
1188
+ #[test]
1189
+ fn button_bind_theme_preserves_concrete_control_dx_and_retained_lifetime() {
1190
+ ffi::test::reset();
1191
+ let previous_theme = current_theme();
1192
+ let parent = column();
1193
+ let control = button("Themed action");
1194
+ control.bind_theme(|button, theme| {
1195
+ button.colors(
1196
+ ButtonColors::new()
1197
+ .background(theme.colors.accent)
1198
+ .text_primary(theme.colors.text_on_accent),
1199
+ );
1200
+ });
1201
+ parent.child(&control);
1202
+ Application::mount(parent);
1203
+ let handle = control.handle().raw();
1204
+ drop(control);
1205
+ ffi::test::take_calls();
1206
+
1207
+ let changed = generate_theme(false, 0x2468ACFF);
1208
+ use_custom_theme(changed.clone());
1209
+ let calls = ffi::test::take_calls();
1210
+ assert!(calls.iter().any(|call| matches!(
1211
+ call,
1212
+ Call::SetBgColor { handle: actual, color }
1213
+ if *actual == handle && *color == changed.colors.accent
1214
+ )));
1215
+
1216
+ use_custom_theme(previous_theme);
1217
+ }
1218
+
1219
+ #[test]
1220
+ fn flex_box_backed_controls_preserve_concrete_bind_theme_dx() {
1221
+ ffi::test::reset();
1222
+ let invocations = Rc::new(Cell::new(0));
1223
+ let count = |invocations: &Rc<Cell<i32>>| {
1224
+ let invocations = invocations.clone();
1225
+ move || invocations.set(invocations.get() + 1)
1226
+ };
1227
+
1228
+ let checkbox = checkbox("Checkbox");
1229
+ checkbox.bind_theme({
1230
+ let count = count(&invocations);
1231
+ move |control, theme| {
1232
+ count();
1233
+ control.colors(LabeledControlColors::new().text_primary(theme.colors.text_primary));
1234
+ }
1235
+ });
1236
+ let switch = switch("Switch");
1237
+ switch.bind_theme({
1238
+ let count = count(&invocations);
1239
+ move |control, theme| {
1240
+ count();
1241
+ control.colors(LabeledControlColors::new().text_primary(theme.colors.text_primary));
1242
+ }
1243
+ });
1244
+ let radio = radio_button("Radio");
1245
+ radio.bind_theme({
1246
+ let count = count(&invocations);
1247
+ move |control, theme| {
1248
+ count();
1249
+ control.colors(LabeledControlColors::new().text_primary(theme.colors.text_primary));
1250
+ }
1251
+ });
1252
+ let slider = slider();
1253
+ slider.bind_theme({
1254
+ let count = count(&invocations);
1255
+ move |control, theme| {
1256
+ count();
1257
+ control.colors(SliderColors::new().fill(theme.colors.accent));
1258
+ }
1259
+ });
1260
+ let progress = progress_bar();
1261
+ progress.bind_theme({
1262
+ let count = count(&invocations);
1263
+ move |control, theme| {
1264
+ count();
1265
+ control.colors(ProgressBarColors::new().fill(theme.colors.accent));
1266
+ }
1267
+ });
1268
+ let dropdown = dropdown();
1269
+ dropdown.bind_theme({
1270
+ let count = count(&invocations);
1271
+ move |control, theme| {
1272
+ count();
1273
+ control.colors(DropdownColors::new().text_primary(theme.colors.text_primary));
1274
+ }
1275
+ });
1276
+ let combo = combo_box();
1277
+ combo.bind_theme({
1278
+ let count = count(&invocations);
1279
+ move |control, theme| {
1280
+ count();
1281
+ control.colors(DropdownColors::new().text_primary(theme.colors.text_primary));
1282
+ }
1283
+ });
1284
+ let input = text_input();
1285
+ input.bind_theme({
1286
+ let count = count(&invocations);
1287
+ move |control, theme| {
1288
+ count();
1289
+ control.colors(TextInputColors::new().text_primary(theme.colors.text_primary));
1290
+ }
1291
+ });
1292
+ let area = text_area();
1293
+ area.bind_theme({
1294
+ let count = count(&invocations);
1295
+ move |control, theme| {
1296
+ count();
1297
+ control.colors(TextInputColors::new().text_primary(theme.colors.text_primary));
1298
+ }
1299
+ });
1300
+ let selection = selection_area();
1301
+ selection.bind_theme({
1302
+ let count = count(&invocations);
1303
+ move |control, _theme| {
1304
+ count();
1305
+ let _ = control.selected_text();
1306
+ }
1307
+ });
1308
+ let barrier = anti_selection_area();
1309
+ barrier.bind_theme({
1310
+ let count = count(&invocations);
1311
+ move |control, _theme| {
1312
+ count();
1313
+ control.children(Vec::<Child>::new());
1314
+ }
1315
+ });
1316
+
1317
+ let scroll = crate::scroll_box();
1318
+ scroll.bind_theme({
1319
+ let count = count(&invocations);
1320
+ move |_control: &crate::ScrollBox, _theme| count()
1321
+ });
1322
+ let list = crate::virtual_list(10, 32.0);
1323
+ list.bind_theme({
1324
+ let count = count(&invocations);
1325
+ move |_control: &crate::VirtualList, _theme| count()
1326
+ });
1327
+
1328
+ assert_eq!(invocations.get(), 13);
1329
+ }
1330
+
1188
1331
  #[test]
1189
1332
  fn retained_pressable_and_slider_keep_theme_subscriptions_after_wrappers_drop() {
1190
1333
  ffi::test::reset();
@@ -3214,6 +3357,100 @@ fn progress_bar_invalid_sizing_warns_and_falls_back_to_defaults() {
3214
3357
  )));
3215
3358
  }
3216
3359
 
3360
+ #[test]
3361
+ fn progress_bar_direct_length_and_thickness_match_fui_as() {
3362
+ ffi::test::reset();
3363
+ let progress = progress_bar();
3364
+ progress.length(320.0).thickness(18.0);
3365
+ Application::mount(progress);
3366
+ let calls = ffi::test::take_calls();
3367
+ assert!(calls.iter().any(|call| matches!(
3368
+ call,
3369
+ Call::SetWidth { value, unit_enum, .. }
3370
+ if *value == 320.0 && *unit_enum == Unit::Pixel as u32
3371
+ )));
3372
+ assert!(calls.iter().any(|call| matches!(
3373
+ call,
3374
+ Call::SetHeight { value, unit_enum, .. }
3375
+ if *value == 18.0 && *unit_enum == Unit::Pixel as u32
3376
+ )));
3377
+
3378
+ ffi::test::reset();
3379
+ let progress = progress_bar();
3380
+ progress.length(0.0).thickness(-2.0);
3381
+ let calls = ffi::test::take_calls();
3382
+ assert!(calls.iter().any(|call| matches!(
3383
+ call,
3384
+ Call::Log { category, message }
3385
+ if category == "Warning/Layout"
3386
+ && message == "ProgressBar.length() received 0; clamping to 1.0."
3387
+ )));
3388
+ assert!(calls.iter().any(|call| matches!(
3389
+ call,
3390
+ Call::Log { category, message }
3391
+ if category == "Warning/Layout"
3392
+ && message == "ProgressBar.thickness() received -2; clamping to 1.0."
3393
+ )));
3394
+ }
3395
+
3396
+ #[test]
3397
+ fn progress_bar_maps_length_and_thickness_to_orientation() {
3398
+ ffi::test::reset();
3399
+ let progress = progress_bar();
3400
+ progress
3401
+ .value(25.0)
3402
+ .length(300.0)
3403
+ .thickness(18.0)
3404
+ .orientation(Orientation::Vertical);
3405
+ Application::mount(progress.clone());
3406
+ let calls = ffi::test::take_calls();
3407
+ let root_handle = progress.retained_node_ref().handle().raw();
3408
+ let fill_handle = child_handles_for_parent(&calls, root_handle)
3409
+ .into_iter()
3410
+ .next()
3411
+ .expect("fill handle");
3412
+
3413
+ assert!(calls.iter().any(|call| matches!(
3414
+ call,
3415
+ Call::SetWidth { handle, value, unit_enum }
3416
+ if *handle == root_handle && *value == 18.0 && *unit_enum == Unit::Pixel as u32
3417
+ )));
3418
+ assert!(calls.iter().any(|call| matches!(
3419
+ call,
3420
+ Call::SetHeight { handle, value, unit_enum }
3421
+ if *handle == root_handle && *value == 300.0 && *unit_enum == Unit::Pixel as u32
3422
+ )));
3423
+ assert!(calls.iter().any(|call| matches!(
3424
+ call,
3425
+ Call::SetWidth { handle, value, unit_enum }
3426
+ if *handle == fill_handle && *value == 18.0 && *unit_enum == Unit::Pixel as u32
3427
+ )));
3428
+ assert!(calls.iter().any(|call| matches!(
3429
+ call,
3430
+ Call::SetHeight { handle, value, unit_enum }
3431
+ if *handle == fill_handle && *value == 75.0 && *unit_enum == Unit::Pixel as u32
3432
+ )));
3433
+ assert!(calls.iter().any(|call| matches!(
3434
+ call,
3435
+ Call::SetSemanticOrientation { handle, orientation_enum }
3436
+ if *handle == root_handle && *orientation_enum == Orientation::Vertical as u32
3437
+ )));
3438
+
3439
+ ffi::test::reset();
3440
+ progress.orientation(Orientation::Horizontal);
3441
+ let calls = ffi::test::take_calls();
3442
+ assert!(calls.iter().any(|call| matches!(
3443
+ call,
3444
+ Call::SetWidth { handle, value, unit_enum }
3445
+ if *handle == root_handle && *value == 300.0 && *unit_enum == Unit::Pixel as u32
3446
+ )));
3447
+ assert!(calls.iter().any(|call| matches!(
3448
+ call,
3449
+ Call::SetHeight { handle, value, unit_enum }
3450
+ if *handle == root_handle && *value == 18.0 && *unit_enum == Unit::Pixel as u32
3451
+ )));
3452
+ }
3453
+
3217
3454
  #[test]
3218
3455
  fn progress_bar_theme_changes_update_default_colors() {
3219
3456
  ffi::test::reset();
@@ -198,6 +198,21 @@ impl HasFlexBoxRoot for TextArea {
198
198
  }
199
199
  }
200
200
 
201
+ impl crate::ThemeBindable for TextArea {
202
+ fn theme_binding_node(&self) -> NodeRef {
203
+ self.core.flex_box_root().retained_node_ref()
204
+ }
205
+
206
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
207
+ let weak_core = Rc::downgrade(&self.core);
208
+ Box::new(move || {
209
+ Some(TextArea {
210
+ core: weak_core.upgrade()?,
211
+ })
212
+ })
213
+ }
214
+ }
215
+
201
216
  impl Node for TextArea {
202
217
  fn retained_node_ref(&self) -> NodeRef {
203
218
  let core = self.core.clone();
@@ -189,6 +189,21 @@ impl HasFlexBoxRoot for TextInput {
189
189
  }
190
190
  }
191
191
 
192
+ impl crate::ThemeBindable for TextInput {
193
+ fn theme_binding_node(&self) -> NodeRef {
194
+ self.core.flex_box_root().retained_node_ref()
195
+ }
196
+
197
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
198
+ let weak_core = Rc::downgrade(&self.core);
199
+ Box::new(move || {
200
+ Some(TextInput {
201
+ core: weak_core.upgrade()?,
202
+ })
203
+ })
204
+ }
205
+ }
206
+
192
207
  impl Node for TextInput {
193
208
  fn retained_node_ref(&self) -> NodeRef {
194
209
  let core = self.core.clone();
@@ -19,56 +19,56 @@ unsafe extern "C" {
19
19
  }
20
20
 
21
21
  pub fn fui_get_accent_color() -> u32 {
22
- #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
23
- {
24
- unsafe { __host_fui_get_accent_color() }
25
- }
26
- #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
27
- {
28
- unsafe { crate::ffi::fui_get_accent_color() }
29
- }
22
+ #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
23
+ {
24
+ unsafe { __host_fui_get_accent_color() }
25
+ }
26
+ #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
27
+ {
28
+ unsafe { crate::ffi::fui_get_accent_color() }
29
+ }
30
30
  }
31
31
 
32
32
  pub fn fui_get_platform_family() -> u32 {
33
- #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
34
- {
35
- unsafe { __host_fui_get_platform_family() }
36
- }
37
- #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
38
- {
39
- unsafe { crate::ffi::fui_get_platform_family() }
40
- }
33
+ #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
34
+ {
35
+ unsafe { __host_fui_get_platform_family() }
36
+ }
37
+ #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
38
+ {
39
+ unsafe { crate::ffi::fui_get_platform_family() }
40
+ }
41
41
  }
42
42
 
43
43
  pub fn fui_is_coarse_pointer() -> bool {
44
- #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
45
- {
46
- unsafe { __host_fui_is_coarse_pointer() }
47
- }
48
- #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
49
- {
50
- unsafe { crate::ffi::fui_is_coarse_pointer() }
51
- }
44
+ #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
45
+ {
46
+ unsafe { __host_fui_is_coarse_pointer() }
47
+ }
48
+ #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
49
+ {
50
+ unsafe { crate::ffi::fui_is_coarse_pointer() }
51
+ }
52
52
  }
53
53
 
54
54
  pub fn fui_is_dark_mode() -> bool {
55
- #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
56
- {
57
- unsafe { __host_fui_is_dark_mode() }
58
- }
59
- #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
60
- {
61
- unsafe { crate::ffi::fui_is_dark_mode() }
62
- }
55
+ #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
56
+ {
57
+ unsafe { __host_fui_is_dark_mode() }
58
+ }
59
+ #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
60
+ {
61
+ unsafe { crate::ffi::fui_is_dark_mode() }
62
+ }
63
63
  }
64
64
 
65
65
  pub fn fui_now_ms() -> f64 {
66
- #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
67
- {
68
- unsafe { __host_fui_now_ms() }
69
- }
70
- #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
71
- {
72
- crate::ffi::test::host_now_ms()
73
- }
66
+ #[cfg(any(target_family = "wasm", feature = "native-runtime"))]
67
+ {
68
+ unsafe { __host_fui_now_ms() }
69
+ }
70
+ #[cfg(all(not(target_family = "wasm"), not(feature = "native-runtime")))]
71
+ {
72
+ crate::ffi::test::host_now_ms()
73
+ }
74
74
  }
package/src/lib.rs CHANGED
@@ -424,7 +424,7 @@ pub mod prelude {
424
424
  Child, ContextMenuEventArgs, Corners, CustomDrawable, EdgeInsets, FlexBox, FlexBoxSurface,
425
425
  GradientStop, Grid, GridTrack, HasFlexBoxRoot, Image, ImageNode, Length, Node,
426
426
  PresenterHostStyle, ScrollBar, ScrollBarVisibility, ScrollBox, ScrollState, ScrollView,
427
- Shadow, Svg, SvgNode, Text, TextCore, TextNode, VirtualList,
427
+ Shadow, Svg, SvgNode, Text, TextCore, TextNode, ThemeBindable, VirtualList,
428
428
  };
429
429
  pub use crate::persisted;
430
430
  pub use crate::platform;
@@ -542,7 +542,7 @@ pub use node::{
542
542
  ContextMenuEventArgs, Corners, CustomDrawable, EdgeInsets, FlexBox, FlexBoxSurface,
543
543
  GradientStop, Grid, GridTrack, HasFlexBoxRoot, Image, ImageNode, Length, Node,
544
544
  PresenterHostStyle, ScrollBar, ScrollBarVisibility, ScrollBox, ScrollState, ScrollView, Shadow,
545
- Svg, SvgNode, Text, TextCore, TextNode, VirtualList,
545
+ Svg, SvgNode, Text, TextCore, TextNode, ThemeBindable, VirtualList,
546
546
  };
547
547
  pub use persisted::*;
548
548
  pub use platform::*;
package/src/node/mod.rs CHANGED
@@ -64,25 +64,43 @@ pub trait HasFlexBoxRoot {
64
64
  fn flex_box_root(&self) -> &FlexBox;
65
65
  }
66
66
 
67
+ pub trait ThemeBindable: Sized + 'static {
68
+ #[doc(hidden)]
69
+ fn theme_binding_node(&self) -> NodeRef;
70
+
71
+ #[doc(hidden)]
72
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>>;
73
+
74
+ fn bind_theme(&self, handler: impl Fn(&Self, crate::theme::Theme) + 'static) -> &Self {
75
+ let target = self.weak_theme_target();
76
+ let guard = crate::theme::subscribe(move |theme| {
77
+ if let Some(control) = target() {
78
+ handler(&control, theme);
79
+ }
80
+ });
81
+ self.theme_binding_node().retain_attachment(Rc::new(guard));
82
+ self
83
+ }
84
+ }
85
+
67
86
  impl HasFlexBoxRoot for FlexBox {
68
87
  fn flex_box_root(&self) -> &FlexBox {
69
88
  self
70
89
  }
71
90
  }
72
91
 
73
- pub trait FlexBoxSurface: HasFlexBoxRoot {
74
- fn bind_theme(&self, handler: impl Fn(&FlexBox, crate::theme::Theme) + 'static) -> &Self {
75
- let root = self.flex_box_root();
76
- let weak_root = root.downgrade();
77
- let guard = crate::theme::subscribe(move |theme| {
78
- if let Some(root) = weak_root.upgrade() {
79
- handler(&root, theme);
80
- }
81
- });
82
- root.retained_node_ref().retain_attachment(Rc::new(guard));
83
- self
92
+ impl ThemeBindable for FlexBox {
93
+ fn theme_binding_node(&self) -> NodeRef {
94
+ self.retained_node_ref()
84
95
  }
85
96
 
97
+ fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
98
+ let weak = self.downgrade();
99
+ Box::new(move || weak.upgrade())
100
+ }
101
+ }
102
+
103
+ pub trait FlexBoxSurface: HasFlexBoxRoot {
86
104
  fn width(&self, width: f32, unit: Unit) -> &Self {
87
105
  self.flex_box_root().width(width, unit);
88
106
  self