1use crate::{h, ActivationType, Crop, Dismissed, Duration, HashMap, Result, Scenario, TAG};
2use windows::{
3 core::{IInspectable, Interface, HRESULT, HSTRING},
4 Data::Xml::Dom::{IXmlNode, XmlDocument},
5 Foundation::TypedEventHandler,
6 UI::Notifications::{
7 NotificationData, ToastActivatedEventArgs, ToastDismissalReason, ToastDismissedEventArgs,
8 ToastFailedEventArgs, ToastNotification, ToastNotificationManager, ToastNotifier,
9 },
10};
11
12pub type OnActivated = Box<dyn FnMut(HashMap<String, String>) + Send + 'static>;
13pub type OnFailed = Box<dyn FnMut(HRESULT) + Send + 'static>;
14pub type OnDismissed = Box<dyn FnMut(Dismissed) + Send + 'static>;
15
16pub struct Toastify {
17 document: XmlDocument,
18 bind: IXmlNode,
19 bind_toast: IXmlNode,
20 notify_data: NotificationData,
21}
22
23impl Toastify {
24 pub fn create() -> Result<Self> {
25 let document = XmlDocument::new()?;
26 let notify_data = NotificationData::new()?;
27
28 notify_data.SetSequenceNumber(0)?;
29
30 let xml_default = include_str!("../assets/xml_default.txt");
31 document.LoadXml(&HSTRING::from(xml_default))?;
32
33 let bind = document.SelectSingleNode(h!("//binding"))?;
34
35 let toast = document.SelectSingleNode(h!("/toast"))?;
36 let bind_toast = document.SelectSingleNode(h!("/toast"))?;
37
38 let actions = document.CreateElement(h!("actions"))?;
39
40 toast.AppendChild(&actions)?;
41 Ok(Self {
42 document,
43 bind,
44 bind_toast,
45 notify_data,
46 })
47 }
48
49 pub fn add_text(&mut self, text: &str) -> Result<()> {
50 let text_elm = self.document.CreateElement(h!("text"))?;
51
52 text_elm.SetInnerText(h!(text))?;
53
54 self.bind.AppendChild(&text_elm)?;
55 Ok(())
56 }
57
58 pub fn add_icon(&mut self, icon: &str, crop: Crop) -> Result<()> {
59 let image_elm = self.document.CreateElement(h!("image"))?;
60
61 for (key, value) in [
62 ("placement", "appLogoOverride"),
63 ("hint-crop", crop.as_str()),
64 ("src", icon),
65 ] {
66 image_elm.SetAttribute(h!(key), h!(value))?;
67 }
68
69 self.bind.AppendChild(&image_elm)?;
70 Ok(())
71 }
72
73 pub fn add_image(&mut self, image: &str) -> Result<()> {
74 let image_elm = self.document.CreateElement(h!("image"))?;
75
76 image_elm.SetAttribute(h!("src"), h!(image))?;
77
78 self.bind.AppendChild(&image_elm)?;
79 Ok(())
80 }
81
82 pub fn add_hero(&mut self, hero: &str) -> Result<()> {
83 let hero_elm = self.document.CreateElement(h!("image"))?;
84
85 for (key, value) in [("placement", "hero"), ("src", hero)] {
86 hero_elm.SetAttribute(h!(key), h!(value))?;
87 }
88
89 self.bind.AppendChild(&hero_elm)?;
90 Ok(())
91 }
92
93 pub fn add_launch(&mut self, on_click: &str) -> Result<()> {
94 let launch_elm = self.document.CreateAttribute(h!("launch"))?;
95
96 let launch = IInspectable::try_from(on_click)?;
97 launch_elm.SetNodeValue(&launch)?;
98
99 self.bind_toast.Attributes()?.SetNamedItem(&launch_elm)?;
100 Ok(())
101 }
102
103 pub fn add_scenario(&mut self, scenario: Scenario) -> Result<()> {
104 let scenario_elm = self.document.CreateAttribute(h!("scenario"))?;
105
106 let launch = IInspectable::try_from(scenario.as_str())?;
107 scenario_elm.SetNodeValue(&launch)?;
108
109 self.bind_toast.Attributes()?.SetNamedItem(&scenario_elm)?;
110 Ok(())
111 }
112
113 pub fn add_audio(&mut self, audio: &str, silent: bool) -> Result<()> {
114 let audio_elm = self.document.CreateElement(h!("audio"))?;
115
116 audio_elm.SetAttribute(h!("src"), h!(audio))?;
117 audio_elm.SetAttribute(h!("silent"), h!(silent.to_string()))?;
118
119 self.bind_toast.AppendChild(&audio_elm)?;
120 Ok(())
121 }
122
123 pub fn add_button(
124 &mut self,
125 title: &str,
126 arg: &str,
127 activation_type: ActivationType,
128 image_uri: &str,
129 ) -> Result<()> {
130 let actions = self.document.SelectSingleNode(h!("//actions"))?;
131 let button_elm = self.document.CreateElement(h!("action"))?;
132
133 for (key, value) in [
134 ("activationType", activation_type.as_str()),
135 ("arguments", arg),
136 ("content", title),
137 ("imageUri", image_uri),
138 ] {
139 button_elm.SetAttribute(h!(key), h!(value))?;
140 }
141
142 actions.AppendChild(&button_elm)?;
143 Ok(())
144 }
145
146 pub fn add_context_button(&mut self, title: &str, arg: &str) -> Result<()> {
147 let actions = self.document.SelectSingleNode(h!("//actions"))?;
148 let context_button_elm = self.document.CreateElement(h!("action"))?;
149
150 for (key, value) in [
151 ("arguments", arg),
152 ("content", title),
153 ("placement", "contextMenu"),
154 ] {
155 context_button_elm.SetAttribute(h!(key), h!(value))?;
156 }
157
158 actions.AppendChild(&context_button_elm)?;
159 Ok(())
160 }
161
162 pub fn add_input(&mut self, input: &str, id: &str) -> Result<()> {
163 let actions = self.document.SelectSingleNode(h!("//actions"))?;
164 let input_elm = self.document.CreateElement(h!("input"))?;
165
166 for (key, value) in [("type", "text"), ("placeHolderContent", input), ("id", id)] {
167 input_elm.SetAttribute(crate::h!(key), h!(value))?;
168 }
169
170 actions.AppendChild(&input_elm)?;
171 Ok(())
172 }
173
174 pub fn add_textbox(
175 &mut self,
176 input: &str,
177 id: &str,
178 title_box: &str,
179 arg_box: &str,
180 image_uri_box: &str,
181 ) -> Result<()> {
182 self.add_input(input, id)?;
183
184 let actions = self.document.SelectSingleNode(h!("//actions"))?;
185 let button_elm = self.document.CreateElement(h!("action"))?;
186
187 for (key, value) in [
188 ("hint-inputId", id),
189 ("arguments", arg_box),
190 ("content", title_box),
191 ("imageUri", image_uri_box),
192 ] {
193 button_elm.SetAttribute(h!(key), h!(value))?;
194 }
195
196 actions.AppendChild(&button_elm)?;
197 Ok(())
198 }
199
200 pub fn add_selection(&mut self, items: Vec<&str>, id: &str) -> Result<()> {
201 let actions = self.document.SelectSingleNode(h!("//actions"))?;
202 let input_elm = self.document.CreateElement(h!("input"))?;
203
204 for (key, value) in [
205 ("id", id),
206 ("type", "selection"),
207 ("defaultInput", items[0]),
208 ] {
209 input_elm.SetAttribute(h!(key), h!(value))?;
210 }
211
212 for item in items {
213 let selection_elm = self.document.CreateElement(h!("selection"))?;
214 for (key, value) in [("id", item), ("content", item)] {
215 selection_elm.SetAttribute(h!(key), h!(value))?;
216
217 input_elm.AppendChild(&selection_elm)?;
218 }
219 }
220
221 actions.AppendChild(&input_elm)?;
222 Ok(())
223 }
224
225 pub fn add_progress(
226 &mut self,
227 title: &str,
228 status: &str,
229 value: &str,
230 value_string: &str,
231 ) -> Result<()> {
232 let progress_elm = self.document.CreateElement(h!("progress"))?;
233
234 for (key, value) in [
235 ("title", "{title}"),
236 ("status", "{status}"),
237 ("value", "{value}"),
238 ("valueStringOverride", "{value_string}"),
239 ] {
240 progress_elm.SetAttribute(h!(key), h!(value))?;
241 }
242
243 let values = self.notify_data.Values()?;
244
245 for (key, value) in [
246 ("title", title),
247 ("status", status),
248 ("value", value),
249 ("value_string", value_string),
250 ] {
251 values.Insert(h!(key), h!(value))?;
252 }
253
254 self.bind.AppendChild(&progress_elm)?;
255 Ok(())
256 }
257
258 pub fn add_data(&mut self, args: HashMap<String, String>) -> Result<()> {
259 let values = self.notify_data.Values()?;
260
261 for (key, value) in args.iter() {
262 values.Insert(h!(key), h!(value))?;
263 }
264
265 Ok(())
266 }
267
268 pub fn add_duration(&mut self, duration: Duration) -> Result<()> {
269 let duration_elm = self.document.CreateAttribute(h!("duration"))?;
270
271 let duration = IInspectable::try_from(duration.as_str())?;
272 duration_elm.SetNodeValue(&duration)?;
273
274 self.bind_toast.Attributes()?.SetNamedItem(&duration_elm)?;
275 Ok(())
276 }
277}
278
279impl Toastify {
280 pub fn show(
281 &self,
282 id: &str,
283 tag: Option<&str>,
284 group: Option<&str>,
285 remote_id: Option<&str>,
286 on_activated: Option<OnActivated>,
287 on_dismissed: Option<OnDismissed>,
288 on_failed: Option<OnFailed>,
289 ) -> Result<(ToastNotification, ToastNotifier)> {
290 let manager = ToastNotificationManager::CreateToastNotifierWithId(h!(id))?;
291 let toast = ToastNotification::CreateToastNotification(&self.document)?;
292
293 if let Some(tag) = tag {
294 toast.SetTag(h!(tag))?;
295 } else {
296 toast.SetTag(h!(TAG))?;
297 }
298 if let Some(group) = group {
299 toast.SetGroup(h!(group))?;
300 }
301 if let Some(remote_id) = remote_id {
302 toast.SetRemoteId(h!(remote_id))?;
303 }
304
305 toast.SetData(&self.notify_data)?;
306
307 if let Some(mut on_activated) = on_activated {
308 let handler = TypedEventHandler::new(move |_, args: &Option<IInspectable>| {
309 if let Some(args) = args {
310 let args = args.cast::<ToastActivatedEventArgs>()?;
311 let user_input = args.UserInput()?;
312
313 let mut hash_map: HashMap<String, String> = HashMap::new();
314
315 for keyvalue in user_input {
316 let key = keyvalue.Key()?;
317 let value: HSTRING = keyvalue.Value()?.try_into()?;
318
319 hash_map.insert(key.to_string(), value.to_string());
320 }
321
322 hash_map.insert("argument".to_string(), args.Arguments()?.to_string());
323 on_activated(hash_map);
324 }
325 Ok(())
326 });
327
328 toast.Activated(&handler)?;
329 }
330
331 if let Some(mut on_dismissed) = on_dismissed {
332 let handler =
333 TypedEventHandler::new(move |_, args: &Option<ToastDismissedEventArgs>| {
334 if let Some(args) = args {
335 let reason = match args.Reason()? {
336 ToastDismissalReason::UserCanceled => Dismissed::UserCanceled,
337 ToastDismissalReason::ApplicationHidden => Dismissed::ApplicationHidden,
338 ToastDismissalReason::TimedOut => Dismissed::TimedOut,
339 _ => Dismissed::None,
340 };
341
342 on_dismissed(reason);
343 }
344 Ok(())
345 });
346
347 toast.Dismissed(&handler)?;
348 }
349
350 if let Some(mut on_failed) = on_failed {
351 let handler = TypedEventHandler::new(move |_, args: &Option<ToastFailedEventArgs>| {
352 if let Some(args) = args {
353 let error_code = args.ErrorCode()?;
354 on_failed(error_code);
355 }
356 Ok(())
357 });
358
359 toast.Failed(&handler)?;
360 }
361
362 manager.Show(&toast)?;
363 Ok((toast, manager))
364 }
365
366 pub fn show_xml(&self, id: &str, xml: &str) -> Result<(ToastNotification, ToastNotifier)> {
367 let document = XmlDocument::new()?;
368
369 document.LoadXml(h!(xml))?;
370
371 let manager = ToastNotificationManager::CreateToastNotifierWithId(h!(id))?;
372 let toast = ToastNotification::CreateToastNotification(&document)?;
373
374 toast.SetTag(h!(TAG))?;
375 toast.SetData(&self.notify_data)?;
376
377 manager.Show(&toast)?;
378 Ok((toast, manager))
379 }
380}