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, Tray, menu_item::MenuItem, tray::Options};
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 /// Accumulated NW.js tray options being configured.
66 pub options: Options,
67 /// Optional menu shown when the tray icon is clicked.
68 pub menu: Option<Menu>,
69 /// Optional tooltip shown when hovering the tray icon.
70 pub tooltip: Option<String>,
71 /// Optional callback invoked when the tray icon is clicked.
72 pub callback: Option<Callback<CallbackClosure<MouseEvent>>>,
73}
74
75impl Default for TrayMenuBuilder {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl TrayMenuBuilder {
82 /// Creates a new, empty tray menu builder.
83 pub fn new() -> Self {
84 Self {
85 options: Options::new(),
86 menu: None,
87 tooltip: None,
88 callback: None,
89 }
90 }
91
92 /// Sets a raw option `key` to `value` on the underlying tray options object.
93 pub fn set(mut self, key: &str, value: JsValue) -> Self {
94 self.options = self.options.set(key, value);
95 self
96 }
97
98 /// Set the title of the tray.
99 ///
100 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traytitle)
101 pub fn title(self, title: &str) -> Self {
102 self.set("title", JsValue::from(title))
103 }
104
105 /// Set the tooltip of the tray. tooltip shows when you hover the Tray with mouse.
106 ///
107 /// Note: tooltip is showed on all three platforms.
108 /// Should be set as Tray property rather from option object constructor.
109 ///
110 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traytooltip)
111 pub fn tooltip(mut self, tooltip: &str) -> Self {
112 self = self.set("tooltip", JsValue::from(tooltip));
113 self.tooltip = Some(tooltip.to_string());
114
115 self
116 }
117
118 /// Set the icon of the tray, icon must receive a path to your icon file.
119 /// It can be a relative path which points to an icon in your app,
120 /// or an absolute path pointing to a file in user’s system.
121 ///
122 /// Mac OS X caveat: when used in notification context,
123 /// png icon is not sized down like in windows notification area,
124 /// it is rather displayed in 1:1 ratio.
125 ///
126 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayicon)
127 pub fn icon(self, icon: &str) -> Self {
128 self.set("icon", JsValue::from(icon))
129 }
130
131 /// (Mac) Set the alternate (active) tray icon.
132 ///
133 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayalticon-mac)
134 pub fn alticon(self, alticon: &str) -> Self {
135 self.set("alticon", JsValue::from(alticon))
136 }
137
138 /// (Mac) Set whether icon and alticon images are treated as "templates" (true by default).
139 /// When the property is set to true the images are treated as “templates”
140 /// and the system automatically ensures proper styling according to the various
141 /// states of the status item (e.g. dark menu, light menu, etc.).
142 /// Template images should consist only of black and clear colours
143 /// and can use the alpha channel in the image to adjust the opacity of black content.
144 ///
145 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayiconsaretemplates-mac)
146 pub fn icons_are_templates(self, icons_are_templates: bool) -> Self {
147 self.set("iconsAreTemplates", JsValue::from(icons_are_templates))
148 }
149
150 /// Set the menu of the tray, menu will be showed when you click on the tray icon.
151 ///
152 /// On Mac OS X the menu will be showed when you click on the
153 /// tray (which is the only action available for tray icons on Mac OS X).
154 /// On Windows and Linux, the menu will be showed when you single click on the
155 /// tray with right mouse button, clicking with left mouse button sends the click
156 /// event and does not show a menu.
157 ///
158 /// In order to reduce differences from different platforms, setting menu property
159 /// is the only way to bind a menu to tray, there’s no way to popup a menu with
160 /// left mouse button click on Linux and Windows.
161 ///
162 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traymenu)
163 pub fn menu(mut self, menu: Menu) -> Self {
164 self.menu = Some(menu);
165 self
166 }
167
168 /// The callback function when tray icon is clicked.
169 ///
170 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#event-click)
171 pub fn callback<F>(mut self, callback: F) -> Self
172 where
173 F: FnMut(MouseEvent) -> std::result::Result<(), JsValue> + 'static,
174 {
175 self.callback = Some(Callback::new(callback));
176
177 self
178 }
179
180 /// A submenu
181 ///
182 /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traymenu)
183 pub fn submenus(self, items: Vec<MenuItem>) -> Self {
184 let submenu = nw_sys::Menu::new();
185 for menu_item in items {
186 submenu.append(&menu_item);
187 }
188
189 self.menu(submenu)
190 }
191
192 /// Constructs the [`Tray`] from the accumulated options, attaching the
193 /// menu, tooltip and click handler, and returns it with its callback (if any).
194 pub fn build_impl(self) -> Result<(Tray, Option<Callback<CallbackClosure<MouseEvent>>>)> {
195 let tray = Tray::new(&self.options);
196
197 if let Some(menu) = self.menu {
198 tray.set_menu(&menu);
199 }
200 if let Some(tooltip) = self.tooltip {
201 tray.set_tooltip(&tooltip);
202 }
203
204 if let Some(callback) = self.callback {
205 tray.on("click", callback.as_ref());
206 Ok((tray, Some(callback)))
207 } else {
208 Ok((tray, None))
209 }
210 }
211
212 /// Builds the configured [`Tray`], retaining any click callback in the
213 /// application so it remains valid.
214 pub fn build(self) -> Result<Tray> {
215 let (tray, callback) = self.build_impl()?;
216
217 if let Some(callback) = callback {
218 let app = match app() {
219 Some(app) => app,
220 None => return Err("app is not initialized".to_string().into()),
221 };
222 app.callbacks.retain(callback)?;
223 }
224
225 Ok(tray)
226 }
227
228 /// Builds the [`Tray`] and returns it together with its click callback (if
229 /// any), leaving the caller responsible for retaining the callback.
230 pub fn finalize(self) -> Result<(Tray, Option<Callback<CallbackClosure<MouseEvent>>>)> {
231 let (tray, callback) = self.build_impl()?;
232
233 Ok((tray, callback))
234 }
235}