1use alloc::string::String;
19use alloc::vec::Vec;
20
21use gfx::{Surface, FMT_PARGB, FMT_XRGB};
22use plat::tray::{self, Note, Tray, TrayEvent, TrayIcon, TrayItem, TI_CHECKED, TI_DISABLED, TI_SEPARATOR};
23
24use super::consts::*;
25use super::popup::Mi;
26use super::Ui;
27
28pub(super) fn icon_of(sf: &Surface) -> Option<TrayIcon> {
30 if sf.w <= 0 || sf.h <= 0 || sf.w > 256 || sf.h > 256 {
31 return None;
32 }
33 let mut px = Vec::with_capacity((sf.w * sf.h) as usize);
34 for y in 0..sf.h {
35 for x in 0..sf.w {
36 let o = (y * sf.stride + x * 4) as usize;
37 let v = u32::from_le_bytes(sf.px.get(o..o + 4)?.try_into().ok()?);
38 px.push(match sf.fmt {
39 FMT_XRGB => v | 0xFF00_0000,
40 FMT_PARGB => {
41 let a = v >> 24;
42 if a == 0 {
43 0
44 } else {
45 let un = |c: u32| ((c * 255 + a / 2) / a).min(255);
46 (a << 24) | (un((v >> 16) & 255) << 16) | (un((v >> 8) & 255) << 8) | un(v & 255)
47 }
48 }
49 _ => v,
50 });
51 }
52 }
53 Some(TrayIcon { w: sf.w as u32, h: sf.h as u32, px })
54}
55
56impl Ui {
57 fn tray_items(&self, menu: &[Mi]) -> Vec<TrayItem> {
59 let mut out = Vec::new();
60 for m in menu {
61 match *m {
62 Mi::Cmd(c) => {
63 let f = self.cmd_flags(c);
64 if f & CF_HIDDEN != 0 {
65 continue;
66 }
67 let label = self.cmd_find(c).map_or(String::new(), |r| String::from(r.label));
68 let flags = if f & CF_DISABLED != 0 { TI_DISABLED } else { 0 } | if f & CF_CHECKED != 0 { TI_CHECKED } else { 0 };
69 out.push(TrayItem { id: c, label, flags, sub: Vec::new() });
70 }
71 Mi::Sep => out.push(TrayItem { flags: TI_SEPARATOR, ..TrayItem::default() }),
72 Mi::Sub(label, items) | Mi::Menu(label, items) => {
73 out.push(TrayItem { id: 0, label: String::from(label), flags: 0, sub: self.tray_items(items) })
74 }
75 Mi::Item(id, label, _) => out.push(TrayItem { id, label: String::from(label), flags: 0, sub: Vec::new() }),
76 Mi::List(base, max, placeholder) => {
77 let rows: Vec<String> = self.m.popup.lists.iter().find(|e| e.0 == base).map(|e| e.1.clone()).unwrap_or_default();
78 if rows.is_empty() && !placeholder.is_empty() {
79 out.push(TrayItem { id: 0, label: String::from(placeholder), flags: TI_DISABLED, sub: Vec::new() });
80 }
81 for (k, r) in rows.iter().take(max as usize).enumerate() {
82 out.push(TrayItem { id: base + k as u32, label: r.replace('&', "&&"), flags: 0, sub: Vec::new() });
83 }
84 }
85 }
86 }
87 out
88 }
89
90 pub fn tray_set(&mut self, icon: Option<&Surface>, tooltip: &str, menu: &[Mi]) -> bool {
92 tray::set_dry_run(self.headless);
93 let t = Tray { icon: icon.and_then(icon_of), tooltip: String::from(tooltip), menu: self.tray_items(menu) };
94 tray::tray_set(&t)
95 }
96
97 pub fn tray_remove(&mut self) {
99 tray::tray_remove();
100 }
101
102 pub fn notify(&mut self, title: &str, body: &str, icon: Option<&Surface>) -> u32 {
104 tray::set_dry_run(self.headless);
105 tray::notify(&Note { title: String::from(title), body: String::from(body), icon: icon.and_then(icon_of) })
106 }
107
108 pub fn notify_close(&mut self, id: u32) {
110 tray::notify_close(id);
111 }
112
113 pub fn tray_supported(&self) -> bool {
116 tray::tray_supported()
117 }
118
119 pub fn bring_to_front(&mut self) {
121 if self.headless {
122 return;
123 }
124 if plat::win_state(0) & plat::WST_MINIMIZED != 0 {
125 plat::win_cmd(0, plat::WCMD_RESTORE);
126 }
127 plat::win_activate(0);
128 }
129
130 pub(crate) fn tray_poll(&mut self) {
132 for e in tray::tray_take() {
133 match e {
134 TrayEvent::Activate => self.cmd_run(UIC_TRAY, TRAY_ACTIVATE),
135 TrayEvent::SecondaryActivate => self.cmd_run(UIC_TRAY, TRAY_SECONDARY),
136 TrayEvent::Menu(id) => self.cmd_run(id, 0),
137 TrayEvent::NoteClicked(id) => self.cmd_run(UIC_NOTE, id),
138 TrayEvent::NoteClosed(id) => self.cmd_run(UIC_NOTE_GONE, id),
139 }
140 }
141 }
142}