1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//!
//! Builder for the application system Tray menu.
//!
//! # Synopsis
//! ```rust
//! use workflow_nw::prelude::*;
//! use workflow_nw::result::Result;
//! use workflow_nw::tray::TrayMenuBuilder;
//! use workflow_dom::utils::window;
//! use wasm_bindgen::JsValue;
//!
//! # fn test()->Result<()>{
//! // create Tray icon menu without submenus
//! TrayMenuBuilder::new()
//!     .icon("resources/icons/tray-icon@2x.png")
//!     .icons_are_templates(false)
//!     .callback(|_|{
//!         window().alert_with_message("Tray menu click")?;
//!         Ok(())
//!     })
//!     .build()?;
//!
//! // create Tray menu icon with submenus
//! let submenu_1 = MenuItemBuilder::new()
//!     .label("Say hi")
//!     .key("6")
//!     .modifiers("ctrl")
//!     .callback(move |_|->std::result::Result<(), JsValue>{
//!         window().alert_with_message("hi")?;
//!         Ok(())
//!     }).build()?;
//!     
//! let exit_menu = MenuItemBuilder::new()
//!     .label("Exit")
//!     .callback(move |_|->std::result::Result<(), JsValue>{
//!         nw_sys::app::close_all_windows();
//!         Ok(())
//!     }).build()?;
//!     
//! let _tray = TrayMenuBuilder::new()
//!     .icon("resources/icons/tray-icon@2x.png")
//!     .icons_are_templates(false)
//!     .submenus(vec![submenu_1, menu_separator(), exit_menu])
//!     .build()?;
//!
//! # Ok(())
//! # }
//!
//! ```
//!

use crate::application::app;
use crate::result::Result;
use nw_sys::prelude::*;
use nw_sys::{menu_item::MenuItem, tray::Options, Menu, Tray};
use wasm_bindgen::prelude::*;
use web_sys::MouseEvent;
use workflow_wasm::prelude::*;

/// Provides a builder pattern for constructing a system tray menu
/// for the application.
///
/// For usage example please refer to [Examples](self)
pub struct TrayMenuBuilder {
    pub options: Options,
    pub menu: Option<Menu>,
    pub tooltip: Option<String>,
    pub callback: Option<Callback<CallbackClosure<MouseEvent>>>,
}

impl Default for TrayMenuBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl TrayMenuBuilder {
    pub fn new() -> Self {
        Self {
            options: Options::new(),
            menu: None,
            tooltip: None,
            callback: None,
        }
    }

    pub fn set(mut self, key: &str, value: JsValue) -> Self {
        self.options = self.options.set(key, value);
        self
    }

    /// Set the title of the tray.
    ///
    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traytitle)
    pub fn title(self, title: &str) -> Self {
        self.set("title", JsValue::from(title))
    }

    /// Set the tooltip of the tray. tooltip shows when you hover the Tray with mouse.
    ///
    /// Note: tooltip is showed on all three platforms.
    /// Should be set as Tray property rather from option object constructor.
    ///
    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traytooltip)
    pub fn tooltip(mut self, tooltip: &str) -> Self {
        self = self.set("tooltip", JsValue::from(tooltip));
        self.tooltip = Some(tooltip.to_string());

        self
    }

    /// Set the icon of the tray, icon must receive a path to your icon file.
    /// It can be a relative path which points to an icon in your app,
    /// or an absolute path pointing to a file in user’s system.
    ///
    /// Mac OS X caveat: when used in notification context,
    /// png icon is not sized down like in windows notification area,
    /// it is rather displayed in 1:1 ratio.
    ///
    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayicon)
    pub fn icon(self, icon: &str) -> Self {
        self.set("icon", JsValue::from(icon))
    }

    /// (Mac) Set the alternate (active) tray icon.
    ///
    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayalticon-mac)
    pub fn alticon(self, alticon: &str) -> Self {
        self.set("alticon", JsValue::from(alticon))
    }

    /// (Mac) Set whether icon and alticon images are treated as "templates" (true by default).
    /// When the property is set to true the images are treated as “templates”
    /// and the system automatically ensures proper styling according to the various
    /// states of the status item (e.g. dark menu, light menu, etc.).
    /// Template images should consist only of black and clear colours
    /// and can use the alpha channel in the image to adjust the opacity of black content.
    ///
    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#trayiconsaretemplates-mac)
    pub fn icons_are_templates(self, icons_are_templates: bool) -> Self {
        self.set("iconsAreTemplates", JsValue::from(icons_are_templates))
    }

    /// Set the menu of the tray, menu will be showed when you click on the tray icon.
    ///
    /// On Mac OS X the menu will be showed when you click on the
    /// tray (which is the only action available for tray icons on Mac OS X).
    /// On Windows and Linux, the menu will be showed when you single click on the
    /// tray with right mouse button, clicking with left mouse button sends the click
    /// event and does not show a menu.
    ///
    /// In order to reduce differences from different platforms, setting menu property
    /// is the only way to bind a menu to tray, there’s no way to popup a menu with
    /// left mouse button click on Linux and Windows.
    ///
    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traymenu)
    pub fn menu(mut self, menu: Menu) -> Self {
        self.menu = Some(menu);
        self
    }

    /// The callback function when tray icon is clicked.
    ///
    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#event-click)
    pub fn callback<F>(mut self, callback: F) -> Self
    where
        F: FnMut(MouseEvent) -> std::result::Result<(), JsValue> + 'static,
    {
        self.callback = Some(Callback::new(callback));

        self
    }

    /// A submenu
    ///
    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Tray/#traymenu)
    pub fn submenus(self, items: Vec<MenuItem>) -> Self {
        let submenu = nw_sys::Menu::new();
        for menu_item in items {
            submenu.append(&menu_item);
        }

        self.menu(submenu)
    }

    pub fn build_impl(self) -> Result<(Tray, Option<Callback<CallbackClosure<MouseEvent>>>)> {
        let tray = Tray::new(&self.options);

        if let Some(menu) = self.menu {
            tray.set_menu(&menu);
        }
        if let Some(tooltip) = self.tooltip {
            tray.set_tooltip(&tooltip);
        }

        if let Some(callback) = self.callback {
            tray.on("click", callback.as_ref());
            Ok((tray, Some(callback)))
        } else {
            Ok((tray, None))
        }
    }

    pub fn build(self) -> Result<Tray> {
        let (tray, callback) = self.build_impl()?;

        if let Some(callback) = callback {
            let app = match app() {
                Some(app) => app,
                None => return Err("app is not initialized".to_string().into()),
            };
            app.callbacks.retain(callback)?;
        }

        Ok(tray)
    }

    pub fn finalize(self) -> Result<(Tray, Option<Callback<CallbackClosure<MouseEvent>>>)> {
        let (tray, callback) = self.build_impl()?;

        Ok((tray, callback))
    }
}