workflow_nw/menu.rs
1//!
2//! Builder for application menus.
3//!
4//! ```rust
5//! use workflow_nw::prelude::*;
6//! use workflow_nw::result::Result;
7//! use workflow_dom::utils::window;
8//! use wasm_bindgen::JsValue;
9//!
10//! # fn test()->Result<()>{
11//!
12//! // create Application instance
13//! let app = Application::new()?;
14//!
15//! // create context menu
16//! let item_1 = MenuItemBuilder::new()
17//! .label("Sub Menu 1")
18//! .callback(move |_|->std::result::Result<(), JsValue>{
19//! window().alert_with_message("Context menu 1 clicked")?;
20//! Ok(())
21//! }).build()?;
22//!
23//! let item_2 = MenuItemBuilder::new()
24//! .label("Sub Menu 2")
25//! .callback(move |_|->std::result::Result<(), JsValue>{
26//! window().alert_with_message("Context menu 2 clicked")?;
27//! Ok(())
28//! }).build()?;
29//!
30//!
31//! app.create_context_menu(vec![item_1, item_2])?;
32//!
33//! // create menubar
34//! let submenu_1 = MenuItemBuilder::new()
35//! .label("Menu A")
36//! .key("8")
37//! .modifiers("ctrl")
38//! .callback(move |_|->std::result::Result<(), JsValue>{
39//! //log_trace!("Create window : menu clicked");
40//! window().alert_with_message("Menu A clicked")?;
41//! Ok(())
42//! }).build()?;
43//!
44//! let submenu_2 = MenuItemBuilder::new()
45//! .label("Say hello")
46//! .key("9")
47//! .modifiers("ctrl")
48//! .callback(move |_|->std::result::Result<(), JsValue>{
49//! window().alert_with_message("Hello")?;
50//! Ok(())
51//! }).build()?;
52//!
53//! let item = MenuItemBuilder::new()
54//! .label("Top Menu")
55//! .submenus(vec![submenu_1, menu_separator(), submenu_2])
56//! .build()?;
57//!
58//!
59//! MenubarBuilder::new("Example App", false)
60//! //.mac_hide_edit(true)
61//! .mac_hide_window(true)
62//! .append(item)
63//! .build(true)?;
64//!
65//! # Ok(())
66//! # }
67//! ```
68//!
69
70use crate::application::app;
71use crate::result::Result;
72use js_sys::Function;
73use nw_sys::prelude::*;
74use wasm_bindgen::prelude::*;
75use workflow_wasm::prelude::*;
76
77/// create a Separator [`MenuItem`](nw_sys::MenuItem)
78pub fn menu_separator() -> nw_sys::MenuItem {
79 nw_sys::MenuItem::new(&nw_sys::menu_item::Type::Separator.into())
80}
81
82/// Provides a builder pattern for building application menus.
83///
84/// For usage example please refer to [Examples](self)
85pub struct MenubarBuilder {
86 /// Mac-specific options applied when creating the built-in menubar.
87 pub mac_options: nw_sys::menu::MacOptions,
88 /// Application name used for the Mac built-in application menu.
89 pub app_name: String,
90 /// The underlying NW.js menubar being assembled.
91 pub menubar: nw_sys::Menu,
92 /// Top-level menu items appended to the menubar.
93 pub menu_items: Vec<nw_sys::MenuItem>,
94 /// Whether the host platform is macOS, enabling Mac-specific behavior.
95 pub is_macos: bool,
96}
97
98impl MenubarBuilder {
99 /// Creates a new menubar builder for the given application name, where
100 /// `is_macos` enables Mac-specific built-in menu handling.
101 pub fn new(app_name: &str, is_macos: bool) -> Self {
102 Self {
103 mac_options: nw_sys::menu::MacOptions::new(),
104 app_name: app_name.to_string(),
105 menubar: nw_sys::Menu::new_with_options(&nw_sys::menu::Type::Menubar.into()),
106 menu_items: vec![],
107 is_macos,
108 }
109 }
110 /// (Mac) do not populate the Edit menu
111 ///
112 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Menu/#menucreatemacbuiltinappname-options-mac)
113 pub fn mac_hide_edit(mut self, hide: bool) -> Self {
114 if self.is_macos {
115 self.mac_options = self.mac_options.hide_edit(hide);
116 }
117 self
118 }
119 /// (Mac) do not populate the Window menu
120 ///
121 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Menu/#menucreatemacbuiltinappname-options-mac)
122 pub fn mac_hide_window(mut self, hide: bool) -> Self {
123 if self.is_macos {
124 self.mac_options = self.mac_options.hide_window(hide);
125 }
126 self
127 }
128
129 /// Append new child menu item
130 pub fn append(mut self, menu_item: nw_sys::MenuItem) -> Self {
131 self.menu_items.push(menu_item);
132 self
133 }
134
135 /// Build menubar
136 ///
137 /// optionally attach menubar to app/window
138 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/For%20Users/Advanced/Customize%20Menubar/#create-and-set-menubar)
139 pub fn build(self, attach: bool) -> Result<nw_sys::Menu> {
140 if self.is_macos {
141 self.menubar
142 .create_mac_builtin_with_options(&self.app_name, &self.mac_options);
143 }
144
145 for item in self.menu_items {
146 self.menubar.append(&item);
147 }
148 if attach {
149 nw_sys::window::get().set_menu(&self.menubar);
150 }
151 Ok(self.menubar)
152 }
153}
154
155/// MenuItem Builder
156///
157/// For usage example please refer to [Examples](self)
158pub struct MenuItemBuilder {
159 /// Accumulated NW.js menu item options being configured.
160 pub options: nw_sys::menu_item::Options,
161 /// Optional callback invoked when the menu item is clicked.
162 pub callback: Option<Callback<CallbackClosure<JsValue>>>,
163}
164
165impl Default for MenuItemBuilder {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171impl MenuItemBuilder {
172 /// Creates a new, empty menu item builder.
173 pub fn new() -> Self {
174 Self {
175 options: nw_sys::menu_item::Options::new(),
176 callback: None,
177 }
178 }
179
180 fn set(mut self, key: &str, value: JsValue) -> Self {
181 self.options = self.options.set(key, value);
182 self
183 }
184
185 /// Type of MenuItem
186 ///
187 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
188 pub fn set_type(self, t: MenuItemType) -> Self {
189 self.set("type", t.into())
190 }
191
192 /// Label for normal item or checkbox
193 ///
194 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
195 pub fn label(self, label: &str) -> Self {
196 self.set("label", JsValue::from(label))
197 }
198
199 /// Icon for normal item or checkbox
200 ///
201 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
202 pub fn icon(self, icon: &str) -> Self {
203 self.set("icon", JsValue::from(icon))
204 }
205
206 /// Tooltip for normal item or checkbox
207 ///
208 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
209 pub fn tooltip(self, tooltip: &str) -> Self {
210 self.set("tooltip", JsValue::from(tooltip))
211 }
212
213 /// The callback function when item is triggered by mouse click or keyboard shortcut
214 ///
215 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
216 pub fn callback<F>(mut self, callback: F) -> Self
217 where
218 F: FnMut(JsValue) -> std::result::Result<(), JsValue> + 'static,
219 {
220 let callback = Callback::new(callback);
221 let cb: &Function = callback.as_ref(); //.into_js();
222 self = self.set("click", JsValue::from(cb));
223 self.callback = Some(callback);
224
225 self
226 }
227
228 /// Whether the item is enabled or disabled. It’s set to true by default.
229 ///
230 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
231 pub fn enabled(self, enabled: bool) -> Self {
232 self.set("enabled", JsValue::from(enabled))
233 }
234
235 /// Whether the checkbox is checked or not. It’s set to false by default.
236 ///
237 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
238 pub fn checked(self, checked: bool) -> Self {
239 self.set("checked", JsValue::from(checked))
240 }
241
242 /// A submenu
243 ///
244 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
245 pub fn submenu(self, submenu: &Menu) -> Self {
246 self.set("submenu", JsValue::from(submenu))
247 }
248
249 /// Create submenu from menu items
250 ///
251 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
252 pub fn submenus(self, items: Vec<MenuItem>) -> Self {
253 let submenu = nw_sys::Menu::new();
254 for menu_item in items {
255 submenu.append(&menu_item);
256 }
257 self.set("submenu", JsValue::from(submenu))
258 }
259
260 /// The key of the shortcut
261 ///
262 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
263 pub fn key(self, key: &str) -> Self {
264 self.set("key", JsValue::from(key))
265 }
266
267 /// The modifiers of the shortcut
268 ///
269 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
270 pub fn modifiers(self, modifiers: &str) -> Self {
271 self.set("modifiers", JsValue::from(modifiers))
272 }
273
274 /// Builds the configured [`MenuItem`](nw_sys::MenuItem), retaining any
275 /// click callback in the application so it remains valid.
276 pub fn build(self) -> Result<nw_sys::MenuItem> {
277 if let Some(callback) = self.callback {
278 let app = match app() {
279 Some(app) => app,
280 None => return Err("app is not initialized".to_string().into()),
281 };
282 app.callbacks.retain(callback)?;
283 }
284
285 let menu_item = nw_sys::MenuItem::new(&self.options);
286 Ok(menu_item)
287 }
288
289 /// Builds the [`MenuItem`](nw_sys::MenuItem) and returns it together with
290 /// its click callback (if any), leaving the caller responsible for
291 /// retaining the callback.
292 pub fn finalize(
293 self,
294 ) -> Result<(nw_sys::MenuItem, Option<Callback<CallbackClosure<JsValue>>>)> {
295 let menu_item = nw_sys::MenuItem::new(&self.options);
296 Ok((menu_item, self.callback))
297 }
298}