workflow_nw/tray.rs
1//!
2//! Builder for the application system Tray menu.
3//!
4//! # Synopsis
5//! ```rust
6//! use workflow_nw::prelude::*;
7//! use workflow_nw::result::Result;
8//! use workflow_nw::tray::TrayMenuBuilder;
9//! use workflow_dom::utils::window;
10//! use wasm_bindgen::JsValue;
11//!
12//! # fn test()->Result<()>{
13//! // create Tray icon menu without submenus
14//! TrayMenuBuilder::new()
15//! .icon("resources/icons/tray-icon@2x.png")
16//! .icons_are_templates(false)
17//! .callback(|_|{
18//! window().alert_with_message("Tray menu click")?;
19//! Ok(())
20//! })
21//! .build()?;
22//!
23//! // create Tray menu icon with submenus
24//! let submenu_1 = MenuItemBuilder::new()
25//! .label("Say hi")
26//! .key("6")
27//! .modifiers("ctrl")
28//! .callback(move |_|->std::result::Result<(), JsValue>{
29//! window().alert_with_message("hi")?;
30//! Ok(())
31//! }).build()?;
32//!
33//! let exit_menu = MenuItemBuilder::new()
34//! .label("Exit")
35//! .callback(move |_|->std::result::Result<(), JsValue>{
36//! nw_sys::app::close_all_windows();
37//! Ok(())
38//! }).build()?;
39//!
40//! let _tray = TrayMenuBuilder::new()
41//! .icon("resources/icons/tray-icon@2x.png")
42//! .icons_are_templates(false)
43//! .submenus(vec![submenu_1, menu_separator(), exit_menu])
44//! .build()?;
45//!
46//! # Ok(())
47//! # }
48//!
49//! ```
50//!
51
52use crate::application::app;
53use crate::result::Result;
54use nw_sys::prelude::*;
55use nw_sys::{menu_item::MenuItem, tray::Options, Menu, Tray};
56use wasm_bindgen::prelude::*;
57use web_sys::MouseEvent;
58use workflow_wasm::prelude::*;
59
60/// Provides a builder pattern for constructing a system tray menu
61/// for the application.
62///
63/// For usage example please refer to [Examples](self)
64pub struct TrayMenuBuilder {
65 pub options: Options,
66 pub menu: Option<Menu>,
67 pub tooltip: Option<String>,
68 pub callback: Option<Callback<CallbackClosure<MouseEvent>>>,
69}
70
71impl Default for TrayMenuBuilder {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77impl TrayMenuBuilder {
78 pub fn new() -> Self {
79 Self {
80 options: Options::new(),
81 menu: None,
82 tooltip: None,
83 callback: None,
84 }
85 }
86
87 pub fn set(mut self, key: &str, value: JsValue) -> Self {
88 self.options = self.options.set(key, value);
89 self
90 }
91
92 /// Set the title of the tray.
93 ///
94 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traytitle)
95 pub fn title(self, title: &str) -> Self {
96 self.set("title", JsValue::from(title))
97 }
98
99 /// Set the tooltip of the tray. tooltip shows when you hover the Tray with mouse.
100 ///
101 /// Note: tooltip is showed on all three platforms.
102 /// Should be set as Tray property rather from option object constructor.
103 ///
104 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traytooltip)
105 pub fn tooltip(mut self, tooltip: &str) -> Self {
106 self = self.set("tooltip", JsValue::from(tooltip));
107 self.tooltip = Some(tooltip.to_string());
108
109 self
110 }
111
112 /// Set the icon of the tray, icon must receive a path to your icon file.
113 /// It can be a relative path which points to an icon in your app,
114 /// or an absolute path pointing to a file in user’s system.
115 ///
116 /// Mac OS X caveat: when used in notification context,
117 /// png icon is not sized down like in windows notification area,
118 /// it is rather displayed in 1:1 ratio.
119 ///
120 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayicon)
121 pub fn icon(self, icon: &str) -> Self {
122 self.set("icon", JsValue::from(icon))
123 }
124
125 /// (Mac) Set the alternate (active) tray icon.
126 ///
127 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayalticon-mac)
128 pub fn alticon(self, alticon: &str) -> Self {
129 self.set("alticon", JsValue::from(alticon))
130 }
131
132 /// (Mac) Set whether icon and alticon images are treated as "templates" (true by default).
133 /// When the property is set to true the images are treated as “templates”
134 /// and the system automatically ensures proper styling according to the various
135 /// states of the status item (e.g. dark menu, light menu, etc.).
136 /// Template images should consist only of black and clear colours
137 /// and can use the alpha channel in the image to adjust the opacity of black content.
138 ///
139 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayiconsaretemplates-mac)
140 pub fn icons_are_templates(self, icons_are_templates: bool) -> Self {
141 self.set("iconsAreTemplates", JsValue::from(icons_are_templates))
142 }
143
144 /// Set the menu of the tray, menu will be showed when you click on the tray icon.
145 ///
146 /// On Mac OS X the menu will be showed when you click on the
147 /// tray (which is the only action available for tray icons on Mac OS X).
148 /// On Windows and Linux, the menu will be showed when you single click on the
149 /// tray with right mouse button, clicking with left mouse button sends the click
150 /// event and does not show a menu.
151 ///
152 /// In order to reduce differences from different platforms, setting menu property
153 /// is the only way to bind a menu to tray, there’s no way to popup a menu with
154 /// left mouse button click on Linux and Windows.
155 ///
156 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traymenu)
157 pub fn menu(mut self, menu: Menu) -> Self {
158 self.menu = Some(menu);
159 self
160 }
161
162 /// The callback function when tray icon is clicked.
163 ///
164 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#event-click)
165 pub fn callback<F>(mut self, callback: F) -> Self
166 where
167 F: FnMut(MouseEvent) -> std::result::Result<(), JsValue> + 'static,
168 {
169 self.callback = Some(Callback::new(callback));
170
171 self
172 }
173
174 /// A submenu
175 ///
176 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traymenu)
177 pub fn submenus(self, items: Vec<MenuItem>) -> Self {
178 let submenu = nw_sys::Menu::new();
179 for menu_item in items {
180 submenu.append(&menu_item);
181 }
182
183 self.menu(submenu)
184 }
185
186 pub fn build_impl(self) -> Result<(Tray, Option<Callback<CallbackClosure<MouseEvent>>>)> {
187 let tray = Tray::new(&self.options);
188
189 if let Some(menu) = self.menu {
190 tray.set_menu(&menu);
191 }
192 if let Some(tooltip) = self.tooltip {
193 tray.set_tooltip(&tooltip);
194 }
195
196 if let Some(callback) = self.callback {
197 tray.on("click", callback.as_ref());
198 Ok((tray, Some(callback)))
199 } else {
200 Ok((tray, None))
201 }
202 }
203
204 pub fn build(self) -> Result<Tray> {
205 let (tray, callback) = self.build_impl()?;
206
207 if let Some(callback) = callback {
208 let app = match app() {
209 Some(app) => app,
210 None => return Err("app is not initialized".to_string().into()),
211 };
212 app.callbacks.retain(callback)?;
213 }
214
215 Ok(tray)
216 }
217
218 pub fn finalize(self) -> Result<(Tray, Option<Callback<CallbackClosure<MouseEvent>>>)> {
219 let (tray, callback) = self.build_impl()?;
220
221 Ok((tray, callback))
222 }
223}