Skip to main content

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}