From 0d76ffd66dc46d7490de9a67a723108b19806092 Mon Sep 17 00:00:00 2001 From: Timon Date: Tue, 4 Nov 2025 09:26:08 +0000 Subject: [PATCH] Desktop: Mac remove menubar flicker (#3335) * remove unnecessary folders from bundling for mac * mac remove menu bar flicker * clean up implementation --- desktop/bundle/src/mac.rs | 5 --- desktop/src/window/mac/menu.rs | 73 ++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/desktop/bundle/src/mac.rs b/desktop/bundle/src/mac.rs index adad20ff..08a5279c 100644 --- a/desktop/bundle/src/mac.rs +++ b/desktop/bundle/src/mac.rs @@ -12,7 +12,6 @@ const HELPER_BIN: &str = "graphite-desktop-platform-mac-helper"; const EXEC_PATH: &str = "Contents/MacOS"; const FRAMEWORKS_PATH: &str = "Contents/Frameworks"; -const RESOURCES_PATH: &str = "Contents/Resources"; const FRAMEWORK: &str = "Chromium Embedded Framework.framework"; pub fn main() -> Result<(), Box> { @@ -56,10 +55,6 @@ fn create_app(app_dir: &Path, id: &str, name: &str, bin: &Path, is_helper: bool) fs::create_dir_all(app_dir.join(EXEC_PATH)).unwrap(); let app_contents_dir: &Path = &app_dir.join("Contents"); - for p in &[EXEC_PATH, RESOURCES_PATH, FRAMEWORKS_PATH] { - fs::create_dir_all(app_contents_dir.join(p)).unwrap(); - } - create_info_plist(app_contents_dir, id, name, is_helper).unwrap(); fs::copy(bin, app_dir.join(EXEC_PATH).join(name)).unwrap(); } diff --git a/desktop/src/window/mac/menu.rs b/desktop/src/window/mac/menu.rs index cc4ca073..e2b437a9 100644 --- a/desktop/src/window/mac/menu.rs +++ b/desktop/src/window/mac/menu.rs @@ -1,6 +1,6 @@ use muda::Menu as MudaMenu; use muda::accelerator::Accelerator; -use muda::{AboutMetadataBuilder, CheckMenuItem, IsMenuItem, MenuEvent, MenuId, MenuItem, MenuItemKind, PredefinedMenuItem, Submenu}; +use muda::{AboutMetadataBuilder, CheckMenuItem, IsMenuItem, MenuEvent, MenuId, MenuItem, MenuItemKind, PredefinedMenuItem, Result, Submenu}; use crate::event::{AppEvent, AppEventScheduler}; use crate::wrapper::messages::MenuItem as WrapperMenuItem; @@ -38,14 +38,26 @@ impl Menu { } pub(super) fn update(&self, entries: Vec) { - // remove all items except the first (app menu) - self.inner.items().iter().skip(1).for_each(|item: &muda::MenuItemKind| { - self.inner.remove(menu_item_kind_to_dyn(item)).unwrap(); - }); + let new_entries = menu_items_from_wrapper(entries); + let existing_entries = self.inner.items(); - let items = menu_items_from_wrapper(entries); - let items = items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::>(); - self.inner.append_items(items.as_ref()).unwrap(); + let mut new_entries_iter = new_entries.iter(); + let mut existing_entries_iter = existing_entries.iter().skip(1); // Skip first menu (app menu) + + let incremental_update_ok = std::iter::from_fn(move || match (existing_entries_iter.next(), new_entries_iter.next()) { + (Some(MenuItemKind::Submenu(old)), Some(MenuItemKind::Submenu(new))) if old.text() == new.text() => { + replace_children(old, 0, new.items()); + Some(true) + } + (None, None) => None, + _ => Some(false), + }) + .all(|b| b); + + if !incremental_update_ok { + // Fallback to full replace + replace_children(&self.inner, 1, new_entries); // Skip first menu (app menu) + } } } @@ -97,3 +109,48 @@ fn u64_to_menu_id(id: u64) -> String { fn menu_id_to_u64(id: &MenuId) -> Option { u64::from_str_radix(&id.0, 16).ok() } + +fn replace_children<'a, T: Into>>(menu: T, skip: usize, new_items: Vec) { + let menu: MenuContainer = menu.into(); + let items = menu.items(); + for item in items.iter().skip(skip) { + menu.remove(menu_item_kind_to_dyn(item)).unwrap(); + } + let items = new_items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::>(); + menu.append_items(items.as_ref()).unwrap(); +} + +enum MenuContainer<'a> { + Menu(&'a MudaMenu), + Submenu(&'a Submenu), +} +impl<'a> MenuContainer<'a> { + fn items(&self) -> Vec { + match self { + MenuContainer::Menu(menu) => menu.items(), + MenuContainer::Submenu(submenu) => submenu.items(), + } + } + fn remove(&self, item: &dyn IsMenuItem) -> Result<()> { + match self { + MenuContainer::Menu(menu) => menu.remove(item), + MenuContainer::Submenu(submenu) => submenu.remove(item), + } + } + fn append_items(&self, items: &[&dyn IsMenuItem]) -> Result<()> { + match self { + MenuContainer::Menu(menu) => menu.append_items(items), + MenuContainer::Submenu(submenu) => submenu.append_items(items), + } + } +} +impl<'a> From<&'a MudaMenu> for MenuContainer<'a> { + fn from(menu: &'a MudaMenu) -> Self { + MenuContainer::Menu(menu) + } +} +impl<'a> From<&'a Submenu> for MenuContainer<'a> { + fn from(submenu: &'a Submenu) -> Self { + MenuContainer::Submenu(submenu) + } +}