fn reconcile_ns_menu_items()

in native/desktop-macos/src/macos/application_menu.rs [385:450]


fn reconcile_ns_menu_items(mtm: MainThreadMarker, menu: &NSMenu, is_top_level: bool, new_items: &[AppMenuItemSafe]) {
    let items_array = menu.itemArray();
    let menu_titles: Vec<_> = items_array.iter().map(|submenu| submenu.title()).collect();

    // sometimes macOS can surround our items with new ones,
    // but usually it just add items to the end
    let old_item_ids: Vec<ItemIdentity> = items_array
        .iter()
        .zip(menu_titles.iter())
        .enumerate()
        .map(|(i, (item, title))| {
            if is_top_level && i == 0 {
                // the first item in a top level menu is always the item with the app name
                ItemIdentity::AppNameSubMenu
            } else {
                item.representedObject()
                    .map(Retained::downcast::<MenuItemRepresenter>)
                    .map_or(ItemIdentity::MacOSProvided, |_rep_obj| {
                        if item.isSeparatorItem() {
                            ItemIdentity::Separator
                        } else if item.hasSubmenu() {
                            ItemIdentity::SubMenu { title }
                        } else {
                            ItemIdentity::Action { title }
                        }
                    })
            }
        })
        .collect();

    let new_item_ids: Vec<_> = new_items.iter().map(ItemIdentity::new).collect();

    let first_item = old_item_ids.iter().position(|it| *it != ItemIdentity::MacOSProvided);
    let last_item = old_item_ids.iter().rposition(|it| *it != ItemIdentity::MacOSProvided);
    let (old_item_ids, base_position) = match (first_item, last_item) {
        (Some(first_item), Some(last_item)) => (&old_item_ids[first_item..=last_item], first_item),
        // All items in the menu are macOS provided
        // Our items will be placed before them
        _ => ([].as_slice(), 0),
    };

    let operations = edit_operations(old_item_ids, &new_item_ids);
    let mut position_shift: isize = base_position as isize;
    for op in operations {
        match op {
            Operation::Insert { position, item_idx } => {
                let new_ns_menu_item = new_items[item_idx].create_ns_menu_item(mtm);
                menu.insertItem_atIndex(&new_ns_menu_item, position as isize + position_shift);
                position_shift += 1;
            }
            Operation::Reconcile { position, item_idx } => {
                let ns_menu_item = menu.itemAtIndex(position as isize + position_shift).unwrap();
                new_items[item_idx].reconcile_ns_menu_item(mtm, &ns_menu_item);
            }
            Operation::Remove { position } => {
                let ns_menu_item = menu.itemAtIndex(position as isize + position_shift).unwrap();
                let rep_obj = ns_menu_item.representedObject().map(Retained::downcast::<MenuItemRepresenter>);
                // Just skip remove commands for macOS provided items
                if rep_obj.is_some() {
                    menu.removeItemAtIndex(position as isize + position_shift);
                    position_shift -= 1;
                }
            }
        }
    }
}