1use alloc::string::String;
16use alloc::vec::Vec;
17
18use crate::consts::*;
19use crate::{Sp, Ui};
20
21#[derive(Clone)]
22struct Toast {
23 id: u32,
24 kind: u32,
25 title: String,
26 msg: String,
27 until: u32,
28 forever: bool,
29}
30
31#[derive(Default)]
33pub(crate) struct S {
34 toasts: Vec<Toast>,
35 next: u32,
36}
37
38const TOAST_MAX: usize = 5;
41
42impl Ui {
43 pub fn toast(&mut self, kind: u32, title: &str, msg: &str, ms: u32) -> u32 {
45 let n = &mut self.m.notify;
46 n.next = n.next.wrapping_add(1).max(1);
47 let id = n.next;
48 let forever = ms == u32::MAX;
49 let dur = if ms == 0 { 5000 } else { ms };
50 let until = self.now_ms.wrapping_add(if forever { 0 } else { dur });
51 let n = &mut self.m.notify;
52 n.toasts.push(Toast { id, kind: kind & 3, title: String::from(title), msg: String::from(msg), until, forever });
53 while n.toasts.len() > TOAST_MAX {
54 n.toasts.remove(0);
55 }
56 if !forever {
57 self.timer(UIT_TOAST, dur);
58 }
59 self.repass();
60 id
61 }
62
63 pub fn toast_dismiss(&mut self, id: u32) {
65 self.m.notify.toasts.retain(|t| t.id != id);
66 self.repass();
67 }
68
69 pub fn toast_clear(&mut self) {
71 self.m.notify.toasts.clear();
72 self.repass();
73 }
74
75 pub fn toasts(&self) -> Vec<(u32, u32, String, String)> {
77 self.m.notify.toasts.iter().map(|t| (t.id, t.kind, t.title.clone(), t.msg.clone())).collect()
78 }
79
80 #[cfg_attr(jfgui_dev, track_caller)]
82 pub fn toast_host(&mut self) {
83 let _src = self.src_scope();
84 let now = self.now_ms;
85 let before = self.m.notify.toasts.len();
87 self.m.notify.toasts.retain(|t| t.forever || (t.until.wrapping_sub(now) as i32) > 0);
88 if self.m.notify.toasts.len() != before {
89 self.repass();
90 }
91 if self.m.notify.toasts.is_empty() {
92 return;
93 }
94 let (ww, wh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
95 let w = self.dp(360).min(ww - self.dp(32));
96 let h = self.dp(64);
97 let gap = self.dp(10);
98 let m = self.dp(16);
99 let prev_layer = self.layer_set(L_TIP);
100 let clip = self.wp.ctx.uiclip;
101 let lr = self.wp.ctx.last_rect;
102 self.wp.ctx.uiclip = [0, 0, ww, wh];
103 let toasts = self.m.notify.toasts.clone();
104 let mut y = wh - m - h - self.dp(28); let mut next_due = u32::MAX;
106 let sh = self.style.shell();
107 for t in toasts.iter().rev() {
108 let r = [ww - m - w, y, w, h];
109 let tid = 0xFFFF_7500u32.wrapping_add(t.id & 0xFF);
110 let ev = self.hit_rect(tid, r[0], r[1], r[2], r[3], HB_BLOCK);
111 let _ = ev;
112 let hovered = self.wp.ctx.hot == tid || (self.mouse().0 >= r[0] && self.mouse().0 < r[0] + r[2] && self.mouse().1 >= r[1] && self.mouse().1 < r[1] + r[3]);
113 let mut sp = Sp { flags: t.kind, label: Some(&t.title), label2: Some(&t.msg), ..Sp::default() };
114 sp.place(r);
115 sh.toast(self, &mut sp);
116 let (cx, cs) = (sp.s[0], sp.s[1]);
117 let cid = 0xFFFF_7600u32.wrapping_add(t.id & 0xFF);
118 let cr = [cx, r[1] + self.dp(10), cs, cs];
119 let cev = self.hit_rect(cid, cr[0], cr[1], cr[2], cr[3], HB_FOCUS);
120 if cev & EV_HOVER != 0 {
121 let c = self.color(T_SUBTLE_HOVER);
122 if c & 0xFF00_0000 != 0 {
123 self.cmd_rrect(cr[0], cr[1], cr[2], cr[3], self.dp(4), 0, c, 0, 0);
124 }
125 }
126 let c = self.color(T_TEXT2);
127 let px = self.dp(12);
128 crate::stock::icon_in(self, crate::keys::IC_CANCEL, cr, px, c);
129 self.sem(K_BUTTON, cid, Some(crate::i18n::tr("Schließen")), 0);
130 self.wp.ctx.last_rect = r;
131 self.wp.ctx.last_state = 0;
132 self.sem(K_BOX, tid, Some(&t.title), t.kind as i32);
133 if cev & EV_ACT != 0 {
134 let id = t.id;
135 self.m.notify.toasts.retain(|x| x.id != id);
136 self.repass();
137 } else if hovered && !t.forever {
138 let until = now.wrapping_add(2000);
140 if let Some(x) = self.m.notify.toasts.iter_mut().find(|x| x.id == t.id) {
141 if (x.until.wrapping_sub(until) as i32) < 0 {
142 x.until = until;
143 }
144 }
145 }
146 if !t.forever {
147 let left = t.until.wrapping_sub(now);
148 if (left as i32) > 0 && left < next_due {
149 next_due = left;
150 }
151 }
152 y -= h + gap;
153 if y < m {
154 break;
155 }
156 }
157 if next_due != u32::MAX {
158 self.timer(UIT_TOAST, next_due.max(16));
159 }
160 self.wp.ctx.uiclip = clip;
161 self.wp.ctx.last_rect = lr;
162 self.layer_set(prev_layer);
163 }
164
165 fn anim_shown(&self, r: [i32; 4]) -> bool {
169 let k = self.wp.ctx.uiclip;
170 r[2] <= 0 || r[3] <= 0 || (r[0] < k[2] && r[1] < k[3] && r[0] + r[2] > k[0] && r[1] + r[3] > k[1])
171 }
172
173 #[cfg_attr(jfgui_dev, track_caller)]
175 pub fn spinner(&mut self, id: u32, size: i32) {
176 let _src = self.src_scope();
177 let s = self.dp(if size > 0 { size } else { 20 });
178 let r = self.place(s, s);
179 let phase = if self.anim_off { 0 } else { ((self.now_ms as u64 * 1024 / 900) & 1023) as i32 };
180 if !self.anim_off && self.anim_shown(r) {
181 self.animating = true;
182 }
183 let sh = self.style.shell();
184 let mut sp = Sp { value: phase, ..Sp::default() };
185 sp.place([r[0] + ((r[2] - s) >> 1), r[1] + ((r[3] - s) >> 1), s, s]);
186 sh.spinner(self, &sp);
187 self.wp.ctx.last_state = 0;
188 self.sem(K_PROGRESS, id, None, -1);
189 }
190
191 #[cfg_attr(jfgui_dev, track_caller)]
193 pub fn progress_indeterminate(&mut self, id: u32) {
194 let _src = self.src_scope();
195 let r = self.place(self.dp(200), self.dp(4));
196 let phase = if self.anim_off { 300 } else { ((self.now_ms as u64 * 1024 / 1500) & 1023) as i32 };
197 if !self.anim_off && self.anim_shown(r) {
198 self.animating = true;
199 }
200 let sh = self.style.shell();
201 let mut sp = Sp { value: phase, ..Sp::default() };
202 sp.place(r);
203 sh.progress_indeterminate(self, &sp);
204 self.wp.ctx.last_state = 0;
205 self.sem(K_PROGRESS, id, None, -1);
206 }
207}