Refrersh the menubar when one of the property get changed

Install a PropertyTracker to update the shadow tree if something changes
This commit is contained in:
Olivier Goffart 2025-01-30 15:41:01 +01:00
parent a3d46a9e88
commit 094ff8f56b
5 changed files with 76 additions and 52 deletions

View file

@ -1269,11 +1269,13 @@ inline void setup_popup_menu_from_menu_item_tree(
using cbindgen_private::MenuVTable;
auto shared = std::make_shared<vtable::VBox<MenuVTable>>(nullptr, nullptr);
cbindgen_private::slint_menus_create_wrapper(&menu_item_tree, &*shared);
SharedVector<MenuEntry> entries_sv;
entries.set_binding([shared] {
vtable::VRefMut<MenuVTable> ref { shared->vtable, shared->instance };
SharedVector<MenuEntry> entries_sv;
shared->vtable->sub_menu(ref, nullptr, &entries_sv);
std::vector<MenuEntry> entries_vec(entries_sv.begin(), entries_sv.end());
entries.set(std::make_shared<VectorModel<MenuEntry>>(std::move(entries_vec)));
return std::make_shared<VectorModel<MenuEntry>>(std::move(entries_vec));
});
sub_menu.set_handler([shared](const auto &entry) {
vtable::VRefMut<MenuVTable> ref { shared->vtable, shared->instance };
SharedVector<MenuEntry> entries_sv;

View file

@ -3084,9 +3084,12 @@ fn compile_builtin_function_call(
sp::WindowInner::from_pub(#window_adapter_tokens.window()).setup_menubar(sp::VBox::new(menu_item_tree));
} else {
let menu_item_tree = sp::Rc::new(menu_item_tree);
let menu_item_tree_ = menu_item_tree.clone();
#access_entries.set_binding(move || {
let mut entries = sp::SharedVector::default();
sp::Menu::sub_menu(&*menu_item_tree, sp::Option::None, &mut entries);
#access_entries.set(sp::ModelRc::new(sp::SharedVectorModel::from(entries)));
sp::Menu::sub_menu(&*menu_item_tree_, sp::Option::None, &mut entries);
sp::ModelRc::new(sp::SharedVectorModel::from(entries))
});
let menu_item_tree_ = menu_item_tree.clone();
#access_sub_menu.set_handler(move |entry| {
let mut entries = sp::SharedVector::default();

View file

@ -7,6 +7,7 @@
use crate::item_rendering::CachedRenderingData;
use crate::item_tree::{ItemTreeRc, ItemWeak, VisitChildrenResult};
use crate::items::{ItemRc, ItemRef, MenuEntry, VoidArg};
use crate::properties::PropertyTracker;
#[cfg(feature = "rtti")]
use crate::rtti::*;
use crate::string::ToSharedString;
@ -15,6 +16,7 @@ use crate::{Callback, Property, SharedString, SharedVector};
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::rc::Rc;
use core::cell::{Cell, RefCell};
use core::pin::Pin;
use i_slint_core_macros::SlintElement;
use vtable::{VRef, VRefMut};
@ -38,39 +40,44 @@ struct ShadowTreeNode {
pub struct MenuFromItemTree {
item_tree: ItemTreeRc,
item_cache: BTreeMap<SharedString, ShadowTreeNode>,
root: SharedVector<MenuEntry>,
item_cache: RefCell<BTreeMap<SharedString, ShadowTreeNode>>,
root: RefCell<SharedVector<MenuEntry>>,
next_id: Cell<usize>,
tracker: Pin<Box<PropertyTracker>>,
}
impl MenuFromItemTree {
pub fn new(item_tree: ItemTreeRc) -> Self {
let mut this = Self { item_tree, item_cache: Default::default(), root: Default::default() };
this.update_shadow_tree();
this
Self {
item_tree,
item_cache: Default::default(),
root: Default::default(),
tracker: Box::pin(PropertyTracker::default()),
next_id: 0.into(),
}
pub fn update_shadow_tree(&mut self) {
self.root =
self.update_shadow_tree_recursive(&ItemRc::new(self.item_tree.clone(), 0), &mut 0);
}
fn update_shadow_tree_recursive(
&mut self,
parent: &ItemRc,
next_id: &mut usize,
) -> SharedVector<MenuEntry> {
fn update_shadow_tree(&self) {
self.tracker.as_ref().evaluate_if_dirty(|| {
self.item_cache.replace(Default::default());
self.root
.replace(self.update_shadow_tree_recursive(&ItemRc::new(self.item_tree.clone(), 0)))
});
}
fn update_shadow_tree_recursive(&self, parent: &ItemRc) -> SharedVector<MenuEntry> {
let mut result = SharedVector::default();
let mut actual_visitor = |item_tree: &ItemTreeRc,
index: u32,
item_pin: core::pin::Pin<ItemRef>|
-> VisitChildrenResult {
let mut actual_visitor =
|item_tree: &ItemTreeRc, index: u32, item_pin: Pin<ItemRef>| -> VisitChildrenResult {
if let Some(menu_item) = ItemRef::downcast_pin::<MenuItem>(item_pin) {
let next_id = self.next_id.get();
self.next_id.set(next_id + 1);
let id = next_id.to_shared_string();
*next_id += 1;
let item = ItemRc::new(item_tree.clone(), index);
let children = self.update_shadow_tree_recursive(&item, next_id);
let children = self.update_shadow_tree_recursive(&item);
let has_sub_menu = !children.is_empty();
self.item_cache.insert(
self.item_cache.borrow_mut().insert(
id.clone(),
ShadowTreeNode { item: ItemRc::downgrade(&item), children },
);
@ -91,21 +98,22 @@ impl MenuFromItemTree {
impl Menu for MenuFromItemTree {
fn sub_menu(&self, parent: Option<&MenuEntry>, result: &mut SharedVector<MenuEntry>) {
self.update_shadow_tree();
match parent {
Some(parent) => {
if let Some(r) = self.item_cache.get(parent.id.as_str()) {
if let Some(r) = self.item_cache.borrow().get(parent.id.as_str()) {
*result = r.children.clone();
}
}
None => {
*result = self.root.clone();
*result = self.root.borrow().clone();
}
}
}
fn activate(&self, entry: &MenuEntry) {
if let Some(menu_item) =
self.item_cache.get(entry.id.as_str()).and_then(|e| e.item.upgrade())
self.item_cache.borrow().get(entry.id.as_str()).and_then(|e| e.item.upgrade())
{
if let Some(menu_item) = menu_item.downcast::<MenuItem>() {
menu_item.activated.call(&());

View file

@ -550,7 +550,6 @@ impl<'id> ItemTreeDescription<'id> {
///
/// Returns an error if the instance does not corresponds to this ItemTreeDescription,
/// or if the property with this name does not exist in this component
#[allow(unused)]
pub fn set_binding(
&self,
component: ItemTreeRefPin,

View file

@ -722,7 +722,7 @@ fn call_builtin_function(
&menu_item_tree,
&enclosing_component,
));
compiled.set_property(inst_ref.borrow(), "entries", entries).unwrap();
compiled.set_binding(inst_ref.borrow(), "entries", entries).unwrap();
compiled.set_callback_handler(inst_ref.borrow(), "sub-menu", sub_menu).unwrap();
compiled.set_callback_handler(inst_ref.borrow(), "activated", activated).unwrap();
} else {
@ -1172,7 +1172,14 @@ fn call_builtin_function(
let (entries, sub_menu, activated) = menu_item_tree_properties(menu_item_tree);
store_property(component, &entries_nr.element(), entries_nr.name(), entries)
assert_eq!(
entries_nr.element().borrow().id,
component.description.original.root_element.borrow().id,
"entries need to be in the main element"
);
component
.description
.set_binding(component.borrow(), entries_nr.name(), entries)
.unwrap();
let i = &local_context.component_instance;
set_callback_handler(i, &sub_menu_nr.element(), sub_menu_nr.name(), sub_menu)
@ -2011,13 +2018,18 @@ impl Menu for MenuWrapper {
}
}
fn menu_item_tree_properties(menu: MenuFromItemTree) -> (Value, CallbackHandler, CallbackHandler) {
fn menu_item_tree_properties(
menu: MenuFromItemTree,
) -> (Box<dyn Fn() -> Value>, CallbackHandler, CallbackHandler) {
let context_menu_item_tree = Rc::new(menu);
let context_menu_item_tree_ = context_menu_item_tree.clone();
let entries = Box::new(move || {
let mut entries = SharedVector::default();
context_menu_item_tree.sub_menu(None, &mut entries);
let entries = Value::Model(ModelRc::new(VecModel::from(
context_menu_item_tree_.sub_menu(None, &mut entries);
Value::Model(ModelRc::new(VecModel::from(
entries.into_iter().map(|x| Value::from(x)).collect::<Vec<_>>(),
)));
)))
});
let context_menu_item_tree_ = context_menu_item_tree.clone();
let sub_menu = Box::new(move |args: &[Value]| -> Value {
let mut entries = SharedVector::default();