Skip to content

Commit 5ba1c3f

Browse files
s00dLegend-Master
andauthored
feat(menu): add icon support for Submenu in Rust and JS/TS APIs (#13722)
* feat(menu): add icon and nativeIcon support for Submenu in tauri and @tauri-apps/api * Merge branch 'dev' into dev * Update muda * feat(menu): add set_icon and set_native_icon methods to set submenu icons * feat(menu): unify icon handling by introducing MenuIcon type * chore: sync bundle.global.js * Make setIcon actually work * Regenerate `bundle.global.js` --------- Co-authored-by: Tony <[email protected]>
1 parent e27427f commit 5ba1c3f

File tree

11 files changed

+288
-42
lines changed

11 files changed

+288
-42
lines changed

.changes/submenu-icon-support.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@tauri-apps/api': 'minor:enhance'
3+
'tauri': 'minor:enhance'
4+
---
5+
6+
Added icon (icon and nativeIcon) support for Submenu:
7+
- In the Rust API (`tauri`), you can now set an icon for submenus via the builder and dedicated methods.
8+
- In the JS/TS API (`@tauri-apps/api`), `SubmenuOptions` now has an `icon` field, and the `Submenu` class provides `setIcon` and `setNativeIcon` methods.
9+
- Usage examples are added to the documentation and demo app.
10+
11+
This is a backwards-compatible feature. Submenus can now display icons just like regular menu items.

Cargo.lock

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tauri/scripts/bundle.global.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tauri/src/menu/builders/menu.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ pub struct SubmenuBuilder<'m, R: Runtime, M: Manager<R>> {
117117
pub(crate) text: String,
118118
pub(crate) enabled: bool,
119119
pub(crate) items: Vec<crate::Result<MenuItemKind<R>>>,
120+
pub(crate) icon: Option<crate::image::Image<'m>>,
121+
pub(crate) native_icon: Option<NativeIcon>,
120122
}
121123

122124
impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
@@ -131,6 +133,8 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
131133
text: text.as_ref().to_string(),
132134
enabled: true,
133135
manager,
136+
icon: None,
137+
native_icon: None,
134138
}
135139
}
136140

@@ -145,9 +149,27 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
145149
enabled: true,
146150
items: Vec::new(),
147151
manager,
152+
icon: None,
153+
native_icon: None,
148154
}
149155
}
150156

157+
/// Set an icon for the submenu.
158+
/// Calling this method resets the native_icon.
159+
pub fn submenu_icon(mut self, icon: crate::image::Image<'m>) -> Self {
160+
self.icon = Some(icon);
161+
self.native_icon = None;
162+
self
163+
}
164+
165+
/// Set a native icon for the submenu.
166+
/// Calling this method resets the icon.
167+
pub fn submenu_native_icon(mut self, icon: NativeIcon) -> Self {
168+
self.native_icon = Some(icon);
169+
self.icon = None;
170+
self
171+
}
172+
151173
/// Set the enabled state for the submenu.
152174
pub fn enabled(mut self, enabled: bool) -> Self {
153175
self.enabled = enabled;
@@ -157,7 +179,23 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
157179
/// Builds this submenu
158180
pub fn build(self) -> crate::Result<Submenu<R>> {
159181
let submenu = if let Some(id) = self.id {
160-
Submenu::with_id(self.manager, id, self.text, self.enabled)?
182+
if let Some(icon) = self.icon {
183+
Submenu::with_id_and_icon(self.manager, id, self.text, self.enabled, Some(icon))?
184+
} else if let Some(native_icon) = self.native_icon {
185+
Submenu::with_id_and_native_icon(
186+
self.manager,
187+
id,
188+
self.text,
189+
self.enabled,
190+
Some(native_icon),
191+
)?
192+
} else {
193+
Submenu::with_id(self.manager, id, self.text, self.enabled)?
194+
}
195+
} else if let Some(icon) = self.icon {
196+
Submenu::new_with_icon(self.manager, self.text, self.enabled, Some(icon))?
197+
} else if let Some(native_icon) = self.native_icon {
198+
Submenu::new_with_native_icon(self.manager, self.text, self.enabled, Some(native_icon))?
161199
} else {
162200
Submenu::new(self.manager, self.text, self.enabled)?
163201
};

crates/tauri/src/menu/plugin.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ struct SubmenuPayload {
9999
text: String,
100100
enabled: Option<bool>,
101101
items: Vec<MenuItemPayloadKind>,
102+
icon: Option<Icon>,
102103
}
103104

104105
impl SubmenuPayload {
@@ -115,6 +116,14 @@ impl SubmenuPayload {
115116
if let Some(enabled) = self.enabled {
116117
builder = builder.enabled(enabled);
117118
}
119+
if let Some(icon) = self.icon {
120+
builder = match icon {
121+
Icon::Native(native_icon) => builder.submenu_native_icon(native_icon),
122+
Icon::Icon(js_icon) => {
123+
builder.submenu_icon(js_icon.into_img(resources_table)?.as_ref().clone())
124+
}
125+
};
126+
}
118127
for item in self.items {
119128
builder = item.with_item(webview, resources_table, |i| Ok(builder.item(i)))?;
120129
}
@@ -380,6 +389,7 @@ fn new<R: Runtime>(
380389
text: options.text.unwrap_or_default(),
381390
enabled: options.enabled,
382391
items: options.items.unwrap_or_default(),
392+
icon: options.icon,
383393
}
384394
.create_item(&webview, &resources_table)?;
385395
let id = submenu.id().clone();
@@ -849,22 +859,27 @@ fn set_checked<R: Runtime>(
849859
fn set_icon<R: Runtime>(
850860
webview: Webview<R>,
851861
rid: ResourceId,
862+
kind: ItemKind,
852863
icon: Option<Icon>,
853864
) -> crate::Result<()> {
854865
let resources_table = webview.resources_table();
855-
let icon_item = resources_table.get::<IconMenuItem<R>>(rid)?;
856-
857-
match icon {
858-
Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),
859-
Some(Icon::Icon(icon)) => {
860-
icon_item.set_icon(Some(icon.into_img(&resources_table)?.as_ref().clone()))
861-
}
862-
None => {
863-
icon_item.set_icon(None)?;
864-
icon_item.set_native_icon(None)?;
865-
Ok(())
866-
}
867-
}
866+
do_menu_item!(
867+
resources_table,
868+
rid,
869+
kind,
870+
|icon_item| match icon {
871+
Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),
872+
Some(Icon::Icon(icon)) => {
873+
icon_item.set_icon(Some(icon.into_img(&resources_table)?.as_ref().clone()))
874+
}
875+
None => {
876+
icon_item.set_icon(None)?;
877+
icon_item.set_native_icon(None)?;
878+
Ok(())
879+
}
880+
},
881+
Icon | Submenu
882+
)
868883
}
869884

870885
struct MenuChannels(Mutex<HashMap<MenuId, Channel<MenuId>>>);

0 commit comments

Comments
 (0)