@effindomv2/fui-rs 0.1.9 → 0.1.11

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.
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fui-rs"
3
- version = "0.1.9"
3
+ version = "0.1.11"
4
4
  edition = "2021"
5
5
  license = "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial"
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effindomv2/fui-rs",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "private": false,
5
5
  "license": "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial",
6
6
  "description": "EffinDom v2 Rust frontend framework SDK and host generators",
@@ -59,7 +59,6 @@ impl NavLink {
59
59
  label_node
60
60
  .font_family(theme.fonts.body_family.clone())
61
61
  .font_size(15.0)
62
- .text_color(theme.colors.accent)
63
62
  .cursor(CursorStyle::Pointer);
64
63
  root.justify_content(JustifyContent::Start)
65
64
  .align_items(AlignItems::Center)
@@ -272,6 +271,19 @@ impl NavLink {
272
271
  self
273
272
  }
274
273
 
274
+ pub fn bind_theme(&self, handler: impl Fn(&NavLink, crate::theme::Theme) + 'static) -> &Self {
275
+ let target = self.event_target();
276
+ let guard = subscribe(move |theme| {
277
+ if let Some(link) = target.upgrade() {
278
+ handler(&link, theme);
279
+ }
280
+ });
281
+ self.root
282
+ .retained_node_ref()
283
+ .retain_attachment(Rc::new(guard));
284
+ self
285
+ }
286
+
275
287
  pub fn on_navigate(&self, handler: impl Fn(NavigateEventArgs) + 'static) -> &Self {
276
288
  *self.navigate.borrow_mut() = Some(Rc::new(handler));
277
289
  self
@@ -293,6 +305,7 @@ impl Node for NavLink {
293
305
 
294
306
  fn build_self(&self) {
295
307
  self.root.build_self();
308
+ self.sync_visual_state();
296
309
  }
297
310
  }
298
311
 
@@ -334,6 +347,24 @@ struct NavLinkEventTarget {
334
347
  }
335
348
 
336
349
  impl NavLinkEventTarget {
350
+ fn upgrade(&self) -> Option<NavLink> {
351
+ Some(NavLink {
352
+ root: self.weak_root.upgrade()?,
353
+ label: self.label.clone(),
354
+ href: self.href.clone(),
355
+ open_in_new_tab: self.open_in_new_tab.clone(),
356
+ navigate: self.navigate.clone(),
357
+ hovered: self.hovered.clone(),
358
+ focused: self.focused.clone(),
359
+ pointer_pressed: self.pointer_pressed.clone(),
360
+ pointer_pressed_open_in_new_tab: self.pointer_pressed_open_in_new_tab.clone(),
361
+ enter_pressed: self.enter_pressed.clone(),
362
+ enter_pressed_open_in_new_tab: self.enter_pressed_open_in_new_tab.clone(),
363
+ preview_pinned_for_context_menu: self.preview_pinned_for_context_menu.clone(),
364
+ text_color_override: self.text_color_override.clone(),
365
+ })
366
+ }
367
+
337
368
  fn is_enabled(&self) -> bool {
338
369
  self.weak_root
339
370
  .upgrade()
@@ -407,13 +438,15 @@ impl NavLinkEventTarget {
407
438
  }
408
439
 
409
440
  fn sync_visual_state(&self) {
410
- let color = self.text_color_override.get().unwrap_or_else(|| {
411
- if self.hovered.get() {
412
- current_theme().colors.accent_hovered
413
- } else {
414
- current_theme().colors.accent
415
- }
416
- });
441
+ let theme = current_theme();
442
+ let color = if self.hovered.get() {
443
+ theme.colors.accent_hovered
444
+ } else {
445
+ self.text_color_override
446
+ .get()
447
+ .or_else(|| self.label.configured_text_color())
448
+ .unwrap_or(theme.colors.accent)
449
+ };
417
450
  if self.label.handle() != NodeHandle::INVALID {
418
451
  ui::set_text_color(self.label.handle().raw(), color);
419
452
  }
@@ -1151,6 +1151,40 @@ fn retained_button_and_nav_link_keep_theme_subscriptions_after_wrappers_drop() {
1151
1151
  use_system_theme();
1152
1152
  }
1153
1153
 
1154
+ #[test]
1155
+ fn nav_link_bind_theme_preserves_concrete_control_dx_and_retained_lifetime() {
1156
+ ffi::test::reset();
1157
+ let previous_theme = current_theme();
1158
+ let parent = column();
1159
+ let link = NavLink::with_label("/docs", "Docs");
1160
+ link.bind_theme(|link, theme| {
1161
+ link.bg_color(theme.colors.surface)
1162
+ .text_color(theme.colors.text_muted);
1163
+ });
1164
+ parent.child(&link);
1165
+ Application::mount(parent);
1166
+ let link_handle = link.handle().raw();
1167
+ let label_handle = link.label_node().handle().raw();
1168
+ drop(link);
1169
+ ffi::test::take_calls();
1170
+
1171
+ let changed = generate_theme(false, 0x2468ACFF);
1172
+ use_custom_theme(changed.clone());
1173
+ let calls = ffi::test::take_calls();
1174
+ assert!(calls.iter().any(|call| matches!(
1175
+ call,
1176
+ Call::SetBgColor { handle, color }
1177
+ if *handle == link_handle && *color == changed.colors.surface
1178
+ )));
1179
+ assert!(calls.iter().any(|call| matches!(
1180
+ call,
1181
+ Call::SetTextColor { handle, color }
1182
+ if *handle == label_handle && *color == changed.colors.text_muted
1183
+ )));
1184
+
1185
+ use_custom_theme(previous_theme);
1186
+ }
1187
+
1154
1188
  #[test]
1155
1189
  fn retained_pressable_and_slider_keep_theme_subscriptions_after_wrappers_drop() {
1156
1190
  ffi::test::reset();
package/src/node/core.rs CHANGED
@@ -1791,6 +1791,9 @@ pub trait Node: Clone {
1791
1791
  Self: Sized,
1792
1792
  {
1793
1793
  let node_ref = self.node_ref();
1794
+ if enabled {
1795
+ node_ref.require_interactive();
1796
+ }
1794
1797
  let mut core = node_ref.inner.borrow_mut();
1795
1798
  core.behavior.focusable = Some((enabled, tab_index));
1796
1799
  let interactive = core.behavior.enabled && core.behavior.inherited_enabled;
@@ -476,6 +476,9 @@ impl FlexBox {
476
476
  }
477
477
 
478
478
  pub fn focusable(&self, enabled: bool, tab_index: i32) -> &Self {
479
+ if enabled {
480
+ self.retained_node_ref().require_interactive();
481
+ }
479
482
  let mut core = self.core.borrow_mut();
480
483
  core.behavior.focusable = Some((enabled, tab_index));
481
484
  let interactive = core.behavior.enabled && core.behavior.inherited_enabled;
@@ -249,6 +249,9 @@ impl ScrollView {
249
249
  }
250
250
 
251
251
  pub fn focusable(&self, enabled: bool, tab_index: i32) -> &Self {
252
+ if enabled {
253
+ self.retained_node_ref().require_interactive();
254
+ }
252
255
  let mut core = self.core.borrow_mut();
253
256
  core.behavior.focusable = Some((enabled, tab_index));
254
257
  let interactive = core.behavior.enabled && core.behavior.inherited_enabled;
@@ -177,6 +177,10 @@ impl TextNode {
177
177
  self
178
178
  }
179
179
 
180
+ pub(crate) fn configured_text_color(&self) -> Option<u32> {
181
+ self.props.borrow().text_color
182
+ }
183
+
180
184
  pub fn style_runs(&self, words: Vec<u32>) -> &Self {
181
185
  {
182
186
  let mut props = self.props.borrow_mut();
@@ -418,6 +422,9 @@ impl TextNode {
418
422
  }
419
423
 
420
424
  pub fn focusable(&self, enabled: bool, tab_index: i32) -> &Self {
425
+ if enabled {
426
+ self.retained_node_ref().require_interactive();
427
+ }
421
428
  let mut core = self.core.borrow_mut();
422
429
  core.behavior.focusable = Some((enabled, tab_index));
423
430
  let interactive = core.behavior.enabled && core.behavior.inherited_enabled;