Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions masonry/src/tests/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,3 +805,54 @@ fn status_flag_update_order() {
assert!(!harness.get_widget(parent1_tag).ctx().is_focus_target());
assert!(!harness.get_widget(parent1_tag).ctx().has_focus_target());
}

/// `RenderRoot::set_default_properties` should re-fire `property_changed` on
/// every cached property across the tree, so a host can swap the default set at
/// runtime and have widgets react.
#[test]
fn set_default_properties_refires_property_changed() {
use std::any::TypeId;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;

use crate::peniko::color::AlphaColor;
use crate::properties::ContentColor;

let changed: Rc<RefCell<Vec<TypeId>>> = Rc::new(RefCell::new(Vec::new()));

let widget = {
let changed = changed.clone();
ModularWidget::new(())
// Read `ContentColor` during paint so it lands in the property cache.
.paint_fn(|_, ctx, props, _painter| {
let cache = ctx.property_cache();
let _ = props.get::<ContentColor>(cache);
})
.property_change_fn(move |_, _ctx, property_type| {
changed.borrow_mut().push(property_type);
})
};

// Seed a default `ContentColor` for this widget type and run a frame so the
// widget caches it.
let mut defaults = test_property_set();
defaults.insert::<ModularWidget<()>, ContentColor>(ContentColor::new(AlphaColor::WHITE));
let mut harness = TestHarness::create(defaults, NewWidget::new(widget));
let _ = harness.redraw();
changed.borrow_mut().clear();

// Swap in a different default and run another frame; the update-props pass
// should re-fire `property_changed` for every cached property, including
// `ContentColor`.
let mut new_defaults = test_property_set();
new_defaults.insert::<ModularWidget<()>, ContentColor>(ContentColor::new(AlphaColor::BLACK));
harness.set_default_properties(Arc::new(new_defaults));
let _ = harness.redraw();

assert!(
changed.borrow().contains(&TypeId::of::<ContentColor>()),
"expected property_changed to re-fire for ContentColor after the default swap, got {:?}",
changed.borrow(),
);
}
36 changes: 36 additions & 0 deletions masonry_core/src/app/render_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,42 @@ impl RenderRoot {
&mut self.property_arena
}

/// Replace the tree-wide default properties at runtime.
///
/// The default property set is normally fixed at construction. This lets
/// a host swap it mid-session, e.g. a light/dark theme toggle that
/// re-colors widgets relying on default `ContentColor` / `Background`.
///
Comment thread
mark-ik marked this conversation as resolved.
/// This invalidates the computed properties of the entire widget tree,
/// and calls [`Widget::property_changed`] for every property previously
/// resolved by each widget.
pub fn set_default_properties(&mut self, default_properties: Arc<DefaultProperties>) {
self.property_arena.default_properties = default_properties;

// Mark the whole tree for the update-properties pass: `needs_update_props`
// so the pass descends to every node, `request_update_props` to enter the
// cache-eviction branch, and `invalidated` so every cached entry re-fires
// `property_changed` regardless of its (unchanged) resolved index.
fn invalidate_props_all_in(node: ArenaMut<'_, WidgetArenaNode>) {
let children = node.children;
let widget = &mut *node.item.widget;
let state = &mut node.item.state;

state.request_update_props = true;
state.needs_update_props = true;
state.property_cache.invalidated = true;

let id = state.id;
recurse_on_children(id, widget, children, |node| {
invalidate_props_all_in(node);
});
}
let root_node = self.widget_arena.get_node_mut(self.root_id());
invalidate_props_all_in(root_node);

self.run_rewrite_passes();
}
Comment thread
mark-ik marked this conversation as resolved.

pub(crate) fn root_id(&self) -> WidgetId {
self.layer_stack.id()
}
Expand Down
7 changes: 7 additions & 0 deletions masonry_testing/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,13 @@ impl<W: Widget> TestHarness<W> {
self.render_root.get_layer_root(0).id()
}

/// Replace the tree-wide default properties at runtime.
///
/// Mirrors [`RenderRoot::set_default_properties`].
pub fn set_default_properties(&mut self, default_properties: Arc<DefaultProperties>) {
self.render_root.set_default_properties(default_properties);
}

/// Returns a [`WidgetRef`] to the widget with the given id.
///
/// # Panics
Expand Down
Loading