1use alloc::string::String;
8use alloc::vec::Vec;
9use core::cell::Cell;
10
11use rlvgl_core::bitmap_font::BitmapFont;
12use rlvgl_core::event::Event;
13use rlvgl_core::renderer::Renderer;
14use rlvgl_core::widget::{Color, Rect, Widget};
15
16use crate::draw_helpers::{draw_rounded_border, fill_rounded_rect};
17
18const DEFAULT_EXPIRE_TICKS: u32 = 60;
20
21const MAX_LINES: usize = 10;
23
24const CLEAR_FRAMES: u8 = 3;
26
27struct EventEntry {
29 text: String,
30 age: u32,
31}
32
33pub struct EventWindow {
35 bounds: Rect,
36 bg_color: Color,
37 border_color: Color,
38 border_width: u8,
39 radius: u8,
40 text_color: Color,
41 entries: Vec<EventEntry>,
42 visible: bool,
43 enabled: bool,
45 clear_countdown: u8,
47 padding: i32,
48 font: &'static BitmapFont,
49 expire_ticks: u32,
51 last_draw_lines: Cell<u8>,
53 draw_seq: Cell<u32>,
55 frozen: bool,
59 dma2d_mode: bool,
62}
63
64impl EventWindow {
65 pub fn is_visible(&self) -> bool {
67 self.visible
68 }
69
70 pub fn entry_count(&self) -> usize {
72 self.entries.len()
73 }
74
75 pub fn is_enabled(&self) -> bool {
77 self.enabled
78 }
79
80 pub fn set_enabled(&mut self, val: bool) {
82 self.enabled = val;
83 }
84
85 pub fn diag_state(&self) -> u32 {
87 ((self.last_draw_lines.get() as u32) << 24)
88 | ((self.clear_countdown as u32) << 16)
89 | ((self.entries.len().min(0xFF) as u32) << 8)
90 | ((self.visible as u32) << 1)
91 | (self.enabled as u32)
92 }
93
94 pub fn draw_seq(&self) -> u32 {
96 self.draw_seq.get()
97 }
98
99 pub fn set_frozen(&mut self, val: bool) {
102 self.frozen = val;
103 }
104
105 pub fn is_frozen(&self) -> bool {
107 self.frozen
108 }
109
110 pub fn set_dma2d_mode(&mut self, val: bool) {
113 self.dma2d_mode = val;
114 }
115
116 pub fn is_dma2d_mode(&self) -> bool {
118 self.dma2d_mode
119 }
120
121 pub fn for_each_visible<F: FnMut(usize, &str)>(&self, mut f: F) {
123 let max_lines = MAX_LINES.min(self.entries.len());
124 let start = self.entries.len().saturating_sub(MAX_LINES);
125 for (i, entry) in self.entries[start..].iter().enumerate() {
126 if i >= max_lines {
127 break;
128 }
129 f(i, &entry.text);
130 }
131 }
132
133 pub fn font(&self) -> &'static BitmapFont {
135 self.font
136 }
137
138 pub fn padding(&self) -> i32 {
140 self.padding
141 }
142
143 pub fn line_height(&self) -> i32 {
145 self.font.scaled_height() + 4
146 }
147
148 pub fn push_event(&mut self, text: String) {
150 if !self.enabled {
151 return;
152 }
153 self.entries.push(EventEntry { text, age: 0 });
154 if self.entries.len() > MAX_LINES * 2 {
156 self.entries.remove(0);
157 }
158 self.visible = true;
159 }
160}
161
162impl Widget for EventWindow {
163 fn bounds(&self) -> Rect {
164 self.bounds
165 }
166
167 fn draw(&self, renderer: &mut dyn Renderer) {
168 if !self.visible || self.dma2d_mode {
169 return;
170 }
171
172 fill_rounded_rect(renderer, self.bounds, self.bg_color, self.radius);
174 draw_rounded_border(
175 renderer,
176 self.bounds,
177 self.border_color,
178 self.border_width,
179 self.radius,
180 );
181
182 let line_h = self.font.scaled_height() + 4;
184 let max_lines = MAX_LINES.min(self.entries.len());
185 let start = self.entries.len().saturating_sub(MAX_LINES);
186 let inner_x = self.bounds.x + self.padding;
187 let inner_y = self.bounds.y + self.padding;
188 self.last_draw_lines.set(max_lines as u8);
189 self.draw_seq.set(self.draw_seq.get().wrapping_add(1));
190
191 for (i, entry) in self.entries[start..].iter().enumerate() {
192 if i >= max_lines {
193 break;
194 }
195 let y = inner_y + i as i32 * line_h;
196 self.font
197 .draw_str(renderer, inner_x, y, &entry.text, self.text_color);
198 }
199 }
200
201 fn handle_event(&mut self, event: &Event) -> bool {
202 if event == &Event::Tick {
203 if self.frozen {
205 return false;
206 }
207 for entry in &mut self.entries {
209 entry.age += 1;
210 }
211 self.entries.retain(|e| e.age < self.expire_ticks);
212 if self.entries.is_empty() && self.visible {
213 self.clear_countdown = CLEAR_FRAMES;
216 self.visible = false;
217 }
218 }
219 false }
223
224 fn clear_region(&mut self) -> Option<Rect> {
225 if self.clear_countdown > 0 && !self.visible {
226 self.clear_countdown -= 1;
227 Some(self.bounds)
228 } else {
229 None
230 }
231 }
232}
233
234pub struct EventWindowBuilder {
236 window_w: i32,
237 window_h: i32,
238 pos_x: Option<i32>,
239 pos_y: Option<i32>,
240 bg_color: Color,
241 border_color: Color,
242 border_width: u8,
243 radius: u8,
244 text_color: Color,
245 font: &'static BitmapFont,
246 expire_ticks: u32,
247}
248
249impl EventWindowBuilder {
250 pub fn new(font: &'static BitmapFont) -> Self {
252 let line_h = font.scaled_height() + 4;
254 let padding = 12;
255 let window_h = MAX_LINES as i32 * line_h + padding * 2;
256 let window_w = 380;
257 Self {
258 window_w,
259 window_h,
260 pos_x: None,
261 pos_y: None,
262 bg_color: Color(25, 25, 25, 255),
263 border_color: Color(80, 80, 80, 255),
264 border_width: 2,
265 radius: 8,
266 text_color: Color(220, 220, 220, 255),
267 font,
268 expire_ticks: DEFAULT_EXPIRE_TICKS,
269 }
270 }
271
272 pub fn expire_ticks(mut self, ticks: u32) -> Self {
276 self.expire_ticks = ticks;
277 self
278 }
279
280 pub fn bg_color(mut self, c: Color) -> Self {
282 self.bg_color = c;
283 self
284 }
285
286 pub fn border_color(mut self, c: Color) -> Self {
288 self.border_color = c;
289 self
290 }
291
292 pub fn radius(mut self, r: u8) -> Self {
294 self.radius = r;
295 self
296 }
297
298 pub fn width(mut self, w: i32) -> Self {
300 self.window_w = w;
301 self
302 }
303
304 pub fn center(mut self, screen_w: i32, screen_h: i32) -> Self {
306 self.pos_x = Some((screen_w - self.window_w) / 2);
307 self.pos_y = Some((screen_h - self.window_h) / 2);
308 self
309 }
310
311 pub fn build(self) -> EventWindow {
313 let margin = 10;
314 EventWindow {
315 bounds: Rect {
316 x: self.pos_x.unwrap_or(margin),
317 y: self.pos_y.unwrap_or(margin),
318 width: self.window_w,
319 height: self.window_h,
320 },
321 bg_color: self.bg_color,
322 border_color: self.border_color,
323 border_width: self.border_width,
324 radius: self.radius,
325 text_color: self.text_color,
326 entries: Vec::new(),
327 visible: false,
328 enabled: true,
329 clear_countdown: 0,
330 padding: 12,
331 font: self.font,
332 expire_ticks: self.expire_ticks,
333 last_draw_lines: Cell::new(0),
334 draw_seq: Cell::new(0),
335 frozen: false,
336 dma2d_mode: false,
337 }
338 }
339}