diff --git a/.changes/submenu-set-icon.md b/.changes/submenu-set-icon.md new file mode 100644 index 00000000..a26d5f6c --- /dev/null +++ b/.changes/submenu-set-icon.md @@ -0,0 +1,10 @@ +--- +"muda": "patch" +--- + +Add support for icons on `Submenu` so we added: + +- `Submenu::set_icon` +- `Submenu::set_native_icon` +- `SubmenuBuilder::icon` +- `SubmenuBuilder::native_icon` diff --git a/Cargo.lock b/Cargo.lock index 799b1220..8367ab16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3091,9 +3091,9 @@ dependencies = [ "ndk", "ndk-context", "ndk-sys", - "objc2 0.6.0", - "objc2-app-kit 0.3.0", - "objc2-foundation 0.3.0", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-foundation 0.3.1", "once_cell", "parking_lot", "raw-window-handle", diff --git a/examples/windows-common-controls-v6/src/main.rs b/examples/windows-common-controls-v6/src/main.rs index bb811b2c..21114605 100644 --- a/examples/windows-common-controls-v6/src/main.rs +++ b/examples/windows-common-controls-v6/src/main.rs @@ -10,7 +10,7 @@ use muda::{ PredefinedMenuItem, Submenu, }; #[cfg(target_os = "macos")] -use tao::platform::macos::{EventLoopBuilderExtMacOS, WindowExtMacOS}; +use tao::platform::macos::WindowExtMacOS; #[cfg(target_os = "windows")] use tao::platform::windows::{EventLoopBuilderExtWindows, WindowExtWindows}; use tao::{ @@ -36,8 +36,6 @@ fn main() { } }); } - #[cfg(target_os = "macos")] - event_loop_builder.with_default_menu(false); let event_loop = event_loop_builder.build(); @@ -79,8 +77,6 @@ fn main() { Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)), ); - let path = concat!(env!("CARGO_MANIFEST_DIR"), "../../icon.png"); - let icon = load_icon(std::path::Path::new(path)); let image_item = IconMenuItem::new("Image Custom 1", true, Some(icon), None); let check_custom_i_1 = CheckMenuItem::new("Check Custom 1", true, true, None); @@ -128,13 +124,13 @@ fn main() { edit_m.append_items(&[©_i, &PredefinedMenuItem::separator(), &paste_i]); #[cfg(target_os = "windows")] - { + unsafe { use tao::rwh_06::*; if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { - menu_bar.init_for_hwnd(handle.hwnd.get()); + menu_bar.init_for_hwnd(handle.hwnd.get()).unwrap(); } if let RawWindowHandle::Win32(handle) = window2.window_handle().unwrap().as_raw() { - menu_bar.init_for_hwnd(handle.hwnd.get()); + menu_bar.init_for_hwnd(handle.hwnd.get()).unwrap(); } } #[cfg(target_os = "macos")] @@ -206,9 +202,10 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option

muda::Icon { diff --git a/src/builders/submenu.rs b/src/builders/submenu.rs index 15a01ea6..cceabbc4 100644 --- a/src/builders/submenu.rs +++ b/src/builders/submenu.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{IsMenuItem, MenuId, Submenu}; +use crate::{Icon, IsMenuItem, MenuId, NativeIcon, Submenu}; /// A builder type for [`Submenu`] #[derive(Clone, Default)] @@ -11,6 +11,8 @@ pub struct SubmenuBuilder<'a> { enabled: bool, id: Option, items: Vec<&'a dyn IsMenuItem>, + icon: Option, + native_icon: Option, } impl std::fmt::Debug for SubmenuBuilder<'_> { @@ -59,12 +61,34 @@ impl<'a> SubmenuBuilder<'a> { self } + /// Set an icon for this submenu. + pub fn icon(mut self, icon: Icon) -> Self { + self.icon = Some(icon); + self + } + + /// Set a native icon for this submenu. + pub fn native_icon(mut self, icon: NativeIcon) -> Self { + self.native_icon = Some(icon); + self + } + /// Build this menu item. pub fn build(self) -> crate::Result { - if let Some(id) = self.id { - Submenu::with_id_and_items(id, self.text, self.enabled, &self.items) + let submenu = if let Some(id) = self.id { + Submenu::with_id_and_items(id, self.text, self.enabled, &self.items)? } else { - Submenu::with_items(self.text, self.enabled, &self.items) + Submenu::with_items(self.text, self.enabled, &self.items)? + }; + + if let Some(icon) = self.icon { + submenu.set_icon(Some(icon)); } + + if let Some(native_icon) = self.native_icon { + submenu.set_native_icon(Some(native_icon)); + } + + Ok(submenu) } } diff --git a/src/items/submenu.rs b/src/items/submenu.rs index 72542e17..a93b841c 100644 --- a/src/items/submenu.rs +++ b/src/items/submenu.rs @@ -5,8 +5,8 @@ use std::{cell::RefCell, mem, rc::Rc}; use crate::{ - dpi::Position, sealed::IsMenuItemBase, util::AddOp, ContextMenu, IsMenuItem, MenuId, - MenuItemKind, + dpi::Position, sealed::IsMenuItemBase, util::AddOp, ContextMenu, Icon, IsMenuItem, MenuId, + MenuItemKind, NativeIcon, }; /// A menu that can be added to a [`Menu`] or another [`Submenu`]. @@ -208,6 +208,21 @@ impl Submenu { self.id().clone() } } + + /// Change this menu item icon or remove it. + pub fn set_icon(&self, icon: Option) { + self.inner.borrow_mut().set_icon(icon) + } + + /// Change this menu item icon to a native image or remove it. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn set_native_icon(&self, _icon: Option) { + #[cfg(target_os = "macos")] + self.inner.borrow_mut().set_native_icon(_icon) + } } impl ContextMenu for Submenu { diff --git a/src/platform_impl/gtk/mod.rs b/src/platform_impl/gtk/mod.rs index 7a9875ab..18bce50c 100644 --- a/src/platform_impl/gtk/mod.rs +++ b/src/platform_impl/gtk/mod.rs @@ -107,16 +107,14 @@ impl Menu { gtk_item.show(); } - { - if let (menu_id, Some(menu)) = &self.gtk_menu { - let gtk_item = - item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?; - match op { - AddOp::Append => menu.append(>k_item), - AddOp::Insert(position) => menu.insert(>k_item, position as i32), - } - gtk_item.show(); + if let (menu_id, Some(menu)) = &self.gtk_menu { + let gtk_item = + item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?; + match op { + AddOp::Append => menu.append(>k_item), + AddOp::Insert(position) => menu.insert(>k_item, position as i32), } + gtk_item.show(); } } @@ -144,8 +142,7 @@ impl Menu { fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { return_if_item_not_supported!(item); - let (menu_id, menu) = &self.gtk_menu; - if let Some(menu) = menu { + if let (menu_id, Some(menu)) = &self.gtk_menu { let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?; menu.append(>k_item); @@ -793,16 +790,14 @@ impl MenuChild { } } - { - if let (menu_id, Some(menu)) = self.gtk_menu.as_ref().unwrap() { - let gtk_item = - item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?; - match op { - AddOp::Append => menu.append(>k_item), - AddOp::Insert(position) => menu.insert(>k_item, position as i32), - } - gtk_item.show(); + if let Some((menu_id, Some(menu))) = self.gtk_menu.as_ref() { + let gtk_item = + item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?; + match op { + AddOp::Append => menu.append(>k_item), + AddOp::Insert(position) => menu.insert(>k_item, position as i32), } + gtk_item.show(); } } @@ -836,8 +831,7 @@ impl MenuChild { fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { return_if_item_not_supported!(item); - let (menu_id, menu) = self.gtk_menu.as_ref().unwrap(); - if let Some(menu) = menu { + if let Some((menu_id, Some(menu))) = self.gtk_menu.as_ref() { let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?; menu.append(>k_item); @@ -1005,17 +999,45 @@ impl MenuChild { menu_id: u32, accel_group: Option<>k::AccelGroup>, add_to_cache: bool, + for_menu_bar: bool, ) -> crate::Result { let submenu = gtk::Menu::new(); - let item = gtk::MenuItem::builder() + + let image = self + .icon + .as_ref() + .map(|icon| gtk::Image::from_pixbuf(Some(&icon.inner.to_pixbuf_scale(16, 16)))) + .unwrap_or_default(); + + let label = gtk::AccelLabel::builder() .label(to_gtk_mnemonic(&self.text)) .use_underline(true) - .submenu(&submenu) + .xalign(0.0) + .build(); + + let box_container = gtk::Box::new(gtk::Orientation::Horizontal, 6); + if !for_menu_bar { + let style_context = box_container.style_context(); + let css_provider = gtk::CssProvider::new(); + let theme = r#" + box { + margin-left: -22px; + } + "#; + let _ = css_provider.load_from_data(theme.as_bytes()); + style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + } + box_container.pack_start(&image, false, false, 0); + box_container.pack_start(&label, true, true, 0); + box_container.show_all(); + + let item = gtk::MenuItem::builder() + .child(&box_container) .sensitive(self.enabled) .build(); - item.show(); item.set_submenu(Some(&submenu)); + item.show(); self.accel_group = accel_group.cloned(); @@ -1036,12 +1058,12 @@ impl MenuChild { .push((id, submenu.clone())); } - for item in self.items() { + for child_item in self.items() { if add_to_cache { - self.add_menu_item_with_id(item.as_ref(), id)?; + self.add_menu_item_with_id(child_item.as_ref(), id)?; } else { - let gtk_item = item.make_gtk_menu_item(0, None, false, false)?; - submenu.append(>k_item); + let gtk_child_item = child_item.make_gtk_menu_item(0, None, false, false)?; + submenu.append(>k_child_item); } } @@ -1326,7 +1348,7 @@ impl MenuItemKind { let mut child = self.child_mut(); match child.item_type() { MenuItemType::Submenu => { - child.create_gtk_item_for_submenu(menu_id, accel_group, add_to_cache) + child.create_gtk_item_for_submenu(menu_id, accel_group, add_to_cache, for_menu_bar) } MenuItemType::MenuItem => { child.create_gtk_item_for_menu_item(menu_id, accel_group, add_to_cache) @@ -1415,7 +1437,6 @@ fn show_context_menu( let tx_clone = tx.clone(); let id = gtk_menu.connect_cancel(move |_| tx_clone.send(false).unwrap_or(())); let id2 = gtk_menu.connect_selection_done(move |_| tx.send(true).unwrap_or(())); - gtk_menu.popup_at_rect( &window, &gdk::Rectangle::new(pos.0, pos.1, 0, 0), diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 494e2c48..141378ad 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -708,6 +708,14 @@ impl MenuChild { ns_submenu.setAutoenablesItems(false); ns_menu_item.setEnabled(self.enabled); + + if let Some(native_icon) = self.native_icon { + menuitem_set_native_icon(&ns_menu_item, Some(native_icon)); + } + + if let Some(icon) = self.icon.as_ref() { + menuitem_set_icon(&ns_menu_item, Some(icon)); + } } let id = COUNTER.next(); diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index b087d013..4e081005 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -233,8 +233,9 @@ impl Menu { { let child_ = child.borrow(); + let item_type = child_.item_type(); - if child_.item_type() == MenuItemType::Icon { + if matches!(item_type, MenuItemType::Icon | MenuItemType::Submenu) { let hbitmap = child_ .icon .as_ref() @@ -873,18 +874,18 @@ impl MenuChild { { let child_ = child.borrow(); + let item_type = child_.item_type(); - if child_.item_type() == MenuItemType::Icon { + if matches!(item_type, MenuItemType::Icon | MenuItemType::Submenu) { let hbitmap = child_ .icon .as_ref() .map(|i| unsafe { i.inner.to_hbitmap() }) .unwrap_or(std::ptr::null_mut()); let info = create_icon_item_info(hbitmap); - unsafe { - SetMenuItemInfoW(self.hmenu, child_.internal_id, FALSE, &info); - SetMenuItemInfoW(self.hpopupmenu, child_.internal_id, FALSE, &info); + SetMenuItemInfoW(self.hmenu, child_.internal_id(), FALSE, &info); + SetMenuItemInfoW(self.hpopupmenu, child_.internal_id(), FALSE, &info); }; } } @@ -1170,7 +1171,6 @@ unsafe extern "system" fn menu_subclass_proc( res } - _ => DefSubclassProc(hwnd as _, msg, wparam, lparam), } }