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}