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    pub mac_options: nw_sys::menu::MacOptions,
87    pub app_name: String,
88    pub menubar: nw_sys::Menu,
89    pub menu_items: Vec<nw_sys::MenuItem>,
90    pub is_macos: bool,
91}
92
93impl MenubarBuilder {
94    pub fn new(app_name: &str, is_macos: bool) -> Self {
95        Self {
96            mac_options: nw_sys::menu::MacOptions::new(),
97            app_name: app_name.to_string(),
98            menubar: nw_sys::Menu::new_with_options(&nw_sys::menu::Type::Menubar.into()),
99            menu_items: vec![],
100            is_macos,
101        }
102    }
103    /// (Mac) do not populate the Edit menu
104    ///
105    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Menu/#menucreatemacbuiltinappname-options-mac)
106    pub fn mac_hide_edit(mut self, hide: bool) -> Self {
107        if self.is_macos {
108            self.mac_options = self.mac_options.hide_edit(hide);
109        }
110        self
111    }
112    /// (Mac) do not populate the Window menu
113    ///
114    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Menu/#menucreatemacbuiltinappname-options-mac)
115    pub fn mac_hide_window(mut self, hide: bool) -> Self {
116        if self.is_macos {
117            self.mac_options = self.mac_options.hide_window(hide);
118        }
119        self
120    }
121
122    /// Append new child menu item
123    pub fn append(mut self, menu_item: nw_sys::MenuItem) -> Self {
124        self.menu_items.push(menu_item);
125        self
126    }
127
128    /// Build menubar
129    ///
130    /// optionally attach menubar to app/window
131    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/For%20Users/Advanced/Customize%20Menubar/#create-and-set-menubar)
132    pub fn build(self, attach: bool) -> Result<nw_sys::Menu> {
133        if self.is_macos {
134            self.menubar
135                .create_mac_builtin_with_options(&self.app_name, &self.mac_options);
136        }
137
138        for item in self.menu_items {
139            self.menubar.append(&item);
140        }
141        if attach {
142            nw_sys::window::get().set_menu(&self.menubar);
143        }
144        Ok(self.menubar)
145    }
146}
147
148/// MenuItem Builder
149///
150/// For usage example please refer to [Examples](self)
151pub struct MenuItemBuilder {
152    pub options: nw_sys::menu_item::Options,
153    pub callback: Option<Callback<CallbackClosure<JsValue>>>,
154}
155
156impl Default for MenuItemBuilder {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162impl MenuItemBuilder {
163    pub fn new() -> Self {
164        Self {
165            options: nw_sys::menu_item::Options::new(),
166            callback: None,
167        }
168    }
169
170    fn set(mut self, key: &str, value: JsValue) -> Self {
171        self.options = self.options.set(key, value);
172        self
173    }
174
175    /// Type of MenuItem
176    ///
177    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
178    pub fn set_type(self, t: MenuItemType) -> Self {
179        self.set("type", t.into())
180    }
181
182    /// Label for normal item or checkbox
183    ///
184    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
185    pub fn label(self, label: &str) -> Self {
186        self.set("label", JsValue::from(label))
187    }
188
189    /// Icon for normal item or checkbox
190    ///
191    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
192    pub fn icon(self, icon: &str) -> Self {
193        self.set("icon", JsValue::from(icon))
194    }
195
196    /// Tooltip for normal item or checkbox
197    ///
198    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
199    pub fn tooltip(self, tooltip: &str) -> Self {
200        self.set("tooltip", JsValue::from(tooltip))
201    }
202
203    /// The callback function when item is triggered by mouse click or keyboard shortcut
204    ///
205    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
206    pub fn callback<F>(mut self, callback: F) -> Self
207    where
208        F: FnMut(JsValue) -> std::result::Result<(), JsValue> + 'static,
209    {
210        let callback = Callback::new(callback);
211        let cb: &Function = callback.as_ref(); //.into_js();
212        self = self.set("click", JsValue::from(cb));
213        self.callback = Some(callback);
214
215        self
216    }
217
218    /// Whether the item is enabled or disabled. It’s set to true by default.
219    ///
220    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
221    pub fn enabled(self, enabled: bool) -> Self {
222        self.set("enabled", JsValue::from(enabled))
223    }
224
225    /// Whether the checkbox is checked or not. It’s set to false by default.
226    ///
227    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
228    pub fn checked(self, checked: bool) -> Self {
229        self.set("checked", JsValue::from(checked))
230    }
231
232    /// A submenu
233    ///
234    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
235    pub fn submenu(self, submenu: &Menu) -> Self {
236        self.set("submenu", JsValue::from(submenu))
237    }
238
239    /// Create submenu from menu items
240    ///
241    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
242    pub fn submenus(self, items: Vec<MenuItem>) -> Self {
243        let submenu = nw_sys::Menu::new();
244        for menu_item in items {
245            submenu.append(&menu_item);
246        }
247        self.set("submenu", JsValue::from(submenu))
248    }
249
250    /// The key of the shortcut
251    ///
252    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
253    pub fn key(self, key: &str) -> Self {
254        self.set("key", JsValue::from(key))
255    }
256
257    /// The modifiers of the shortcut
258    ///
259    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/MenuItem/#new-menuitemoption)
260    pub fn modifiers(self, modifiers: &str) -> Self {
261        self.set("modifiers", JsValue::from(modifiers))
262    }
263
264    pub fn build(self) -> Result<nw_sys::MenuItem> {
265        if let Some(callback) = self.callback {
266            let app = match app() {
267                Some(app) => app,
268                None => return Err("app is not initialized".to_string().into()),
269            };
270            app.callbacks.retain(callback)?;
271        }
272
273        let menu_item = nw_sys::MenuItem::new(&self.options);
274        Ok(menu_item)
275    }
276
277    pub fn finalize(
278        self,
279    ) -> Result<(nw_sys::MenuItem, Option<Callback<CallbackClosure<JsValue>>>)> {
280        let menu_item = nw_sys::MenuItem::new(&self.options);
281        Ok((menu_item, self.callback))
282    }
283}