|
1 | 1 | use muda::Menu as MudaMenu; |
2 | 2 | use muda::accelerator::Accelerator; |
3 | | -use muda::{AboutMetadataBuilder, CheckMenuItem, IsMenuItem, MenuEvent, MenuId, MenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; |
| 3 | +use muda::{AboutMetadataBuilder, CheckMenuItem, IsMenuItem, MenuEvent, MenuId, MenuItem, MenuItemKind, PredefinedMenuItem, Result, Submenu}; |
4 | 4 |
|
5 | 5 | use crate::event::{AppEvent, AppEventScheduler}; |
6 | 6 | use crate::wrapper::messages::MenuItem as WrapperMenuItem; |
@@ -38,14 +38,26 @@ impl Menu { |
38 | 38 | } |
39 | 39 |
|
40 | 40 | pub(super) fn update(&self, entries: Vec<WrapperMenuItem>) { |
41 | | - // remove all items except the first (app menu) |
42 | | - self.inner.items().iter().skip(1).for_each(|item: &muda::MenuItemKind| { |
43 | | - self.inner.remove(menu_item_kind_to_dyn(item)).unwrap(); |
44 | | - }); |
45 | | - |
46 | | - let items = menu_items_from_wrapper(entries); |
47 | | - let items = items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>(); |
48 | | - self.inner.append_items(items.as_ref()).unwrap(); |
| 41 | + let new_entries = menu_items_from_wrapper(entries); |
| 42 | + let existing_entries = self.inner.items(); |
| 43 | + |
| 44 | + let mut new_entries_iter = new_entries.iter(); |
| 45 | + let mut existing_entries_iter = existing_entries.iter().skip(1); // Skip first menu (app menu) |
| 46 | + |
| 47 | + let incremental_update_ok = std::iter::from_fn(move || match (existing_entries_iter.next(), new_entries_iter.next()) { |
| 48 | + (Some(MenuItemKind::Submenu(old)), Some(MenuItemKind::Submenu(new))) if old.text() == new.text() => { |
| 49 | + replace_children(old, 0, new.items()); |
| 50 | + Some(true) |
| 51 | + } |
| 52 | + (None, None) => None, |
| 53 | + _ => Some(false), |
| 54 | + }) |
| 55 | + .all(|b| b); |
| 56 | + |
| 57 | + if !incremental_update_ok { |
| 58 | + // Fallback to full replace |
| 59 | + replace_children(&self.inner, 1, new_entries); // Skip first menu (app menu) |
| 60 | + } |
49 | 61 | } |
50 | 62 | } |
51 | 63 |
|
@@ -97,3 +109,48 @@ fn u64_to_menu_id(id: u64) -> String { |
97 | 109 | fn menu_id_to_u64(id: &MenuId) -> Option<u64> { |
98 | 110 | u64::from_str_radix(&id.0, 16).ok() |
99 | 111 | } |
| 112 | + |
| 113 | +fn replace_children<'a, T: Into<MenuContainer<'a>>>(menu: T, skip: usize, new_items: Vec<MenuItemKind>) { |
| 114 | + let menu: MenuContainer = menu.into(); |
| 115 | + let items = menu.items(); |
| 116 | + for item in items.iter().skip(skip) { |
| 117 | + menu.remove(menu_item_kind_to_dyn(item)).unwrap(); |
| 118 | + } |
| 119 | + let items = new_items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>(); |
| 120 | + menu.append_items(items.as_ref()).unwrap(); |
| 121 | +} |
| 122 | + |
| 123 | +enum MenuContainer<'a> { |
| 124 | + Menu(&'a MudaMenu), |
| 125 | + Submenu(&'a Submenu), |
| 126 | +} |
| 127 | +impl<'a> MenuContainer<'a> { |
| 128 | + fn items(&self) -> Vec<MenuItemKind> { |
| 129 | + match self { |
| 130 | + MenuContainer::Menu(menu) => menu.items(), |
| 131 | + MenuContainer::Submenu(submenu) => submenu.items(), |
| 132 | + } |
| 133 | + } |
| 134 | + fn remove(&self, item: &dyn IsMenuItem) -> Result<()> { |
| 135 | + match self { |
| 136 | + MenuContainer::Menu(menu) => menu.remove(item), |
| 137 | + MenuContainer::Submenu(submenu) => submenu.remove(item), |
| 138 | + } |
| 139 | + } |
| 140 | + fn append_items(&self, items: &[&dyn IsMenuItem]) -> Result<()> { |
| 141 | + match self { |
| 142 | + MenuContainer::Menu(menu) => menu.append_items(items), |
| 143 | + MenuContainer::Submenu(submenu) => submenu.append_items(items), |
| 144 | + } |
| 145 | + } |
| 146 | +} |
| 147 | +impl<'a> From<&'a MudaMenu> for MenuContainer<'a> { |
| 148 | + fn from(menu: &'a MudaMenu) -> Self { |
| 149 | + MenuContainer::Menu(menu) |
| 150 | + } |
| 151 | +} |
| 152 | +impl<'a> From<&'a Submenu> for MenuContainer<'a> { |
| 153 | + fn from(submenu: &'a Submenu) -> Self { |
| 154 | + MenuContainer::Submenu(submenu) |
| 155 | + } |
| 156 | +} |
0 commit comments