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),
}
}