Skip to main content

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}