1#![allow(dead_code)]
8
9use alloc::{string::String, vec::Vec};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum AppIcon {
18 Terminal,
19 FileManager,
20 TextEditor,
21 Settings,
22 ImageViewer,
23 Generic,
24}
25
26pub struct SwitcherEntry {
28 pub window_id: u32,
29 pub title: String,
30 pub app_icon: AppIcon,
31 pub thumbnail: Option<Vec<u32>>,
33 pub thumbnail_width: u32,
34 pub thumbnail_height: u32,
35}
36
37pub struct AppSwitcher {
47 entries: Vec<SwitcherEntry>,
48 selected_index: usize,
49 visible: bool,
50 overlay_x: u32,
52 overlay_y: u32,
53 overlay_width: u32,
54 overlay_height: u32,
55 entry_width: u32,
57 entry_height: u32,
58 padding: u32,
59}
60
61impl AppSwitcher {
62 pub fn new() -> Self {
64 Self {
65 entries: Vec::new(),
66 selected_index: 0,
67 visible: false,
68 overlay_x: 0,
69 overlay_y: 0,
70 overlay_width: 0,
71 overlay_height: 0,
72 entry_width: 120,
73 entry_height: 100,
74 padding: 12,
75 }
76 }
77
78 pub fn show(&mut self, windows: Vec<(u32, String)>) {
83 self.entries.clear();
84 self.selected_index = 0;
85
86 for (wid, title) in windows {
87 let icon = guess_icon(&title);
88 self.entries.push(SwitcherEntry {
89 window_id: wid,
90 title,
91 app_icon: icon,
92 thumbnail: None,
93 thumbnail_width: 0,
94 thumbnail_height: 0,
95 });
96 }
97
98 if !self.entries.is_empty() {
99 if self.entries.len() > 1 {
101 self.selected_index = 1;
102 }
103 self.visible = true;
104 }
105 }
106
107 pub fn hide(&mut self) -> Option<u32> {
109 self.visible = false;
110 let wid = self.entries.get(self.selected_index).map(|e| e.window_id);
111 self.entries.clear();
112 wid
113 }
114
115 pub fn next(&mut self) {
117 if self.entries.is_empty() {
118 return;
119 }
120 self.selected_index = (self.selected_index + 1) % self.entries.len();
121 }
122
123 pub fn previous(&mut self) {
125 if self.entries.is_empty() {
126 return;
127 }
128 if self.selected_index == 0 {
129 self.selected_index = self.entries.len() - 1;
130 } else {
131 self.selected_index -= 1;
132 }
133 }
134
135 pub fn is_visible(&self) -> bool {
137 self.visible
138 }
139
140 pub fn selected_window_id(&self) -> Option<u32> {
142 self.entries.get(self.selected_index).map(|e| e.window_id)
143 }
144
145 pub fn entry_count(&self) -> usize {
147 self.entries.len()
148 }
149
150 pub fn render(&self, buffer: &mut [u32], buf_width: u32, buf_height: u32) {
155 if !self.visible || self.entries.is_empty() {
156 return;
157 }
158
159 let count = self.entries.len() as u32;
160 let ew = self.entry_width;
161 let eh = self.entry_height;
162 let pad = self.padding;
163
164 let total_width = count * ew + (count + 1) * pad;
166 let total_height = eh + pad * 2;
167
168 let ox = if total_width < buf_width {
170 (buf_width - total_width) / 2
171 } else {
172 0
173 };
174 let oy = if total_height < buf_height {
175 (buf_height - total_height) / 2
176 } else {
177 0
178 };
179
180 let bw = buf_width as usize;
181
182 let bg_color: u32 = 0xE0202020; for y in 0..total_height {
185 for x in 0..total_width {
186 let px = (ox + x) as usize;
187 let py = (oy + y) as usize;
188 if px < buf_width as usize && py < buf_height as usize {
189 let idx = py * bw + px;
190 if idx < buffer.len() {
191 let src_a = (bg_color >> 24) & 0xFF;
193 let src_r = (bg_color >> 16) & 0xFF;
194 let src_g = (bg_color >> 8) & 0xFF;
195 let src_b = bg_color & 0xFF;
196
197 let dst = buffer[idx];
198 let dst_r = (dst >> 16) & 0xFF;
199 let dst_g = (dst >> 8) & 0xFF;
200 let dst_b = dst & 0xFF;
201
202 let inv_a = 255 - src_a;
203 let r = (src_r * src_a + dst_r * inv_a) / 255;
204 let g = (src_g * src_a + dst_g * inv_a) / 255;
205 let b = (src_b * src_a + dst_b * inv_a) / 255;
206
207 buffer[idx] = 0xFF00_0000 | (r << 16) | (g << 8) | b;
208 }
209 }
210 }
211 }
212
213 let border_color: u32 = 0xFF5294E2; for x in 0..total_width {
217 let px = (ox + x) as usize;
218 let py_top = oy as usize;
219 let py_bot = (oy + total_height).saturating_sub(1) as usize;
220 if px < buf_width as usize {
221 let idx_t = py_top * bw + px;
222 let idx_b = py_bot * bw + px;
223 if idx_t < buffer.len() {
224 buffer[idx_t] = border_color;
225 }
226 if idx_b < buffer.len() {
227 buffer[idx_b] = border_color;
228 }
229 }
230 }
231 for y in 0..total_height {
233 let py = (oy + y) as usize;
234 let px_left = ox as usize;
235 let px_right = (ox + total_width).saturating_sub(1) as usize;
236 if py < buf_height as usize {
237 let idx_l = py * bw + px_left;
238 let idx_r = py * bw + px_right;
239 if idx_l < buffer.len() {
240 buffer[idx_l] = border_color;
241 }
242 if idx_r < buffer.len() {
243 buffer[idx_r] = border_color;
244 }
245 }
246 }
247
248 for (i, entry) in self.entries.iter().enumerate() {
250 let entry_x = ox + pad + (i as u32) * (ew + pad);
251 let entry_y = oy + pad;
252 let selected = i == self.selected_index;
253
254 self.render_entry(buffer, buf_width, entry_x, entry_y, entry, selected);
255 }
256 }
257
258 fn render_entry(
260 &self,
261 buffer: &mut [u32],
262 buf_width: u32,
263 x: u32,
264 y: u32,
265 entry: &SwitcherEntry,
266 selected: bool,
267 ) {
268 let bw = buf_width as usize;
269 let ew = self.entry_width as usize;
270 let eh = self.entry_height as usize;
271
272 let entry_bg = if selected {
274 0xFF3A5F8A } else {
276 0xFF2A2A2A };
278
279 for dy in 0..eh {
280 for dx in 0..ew {
281 let px = x as usize + dx;
282 let py = y as usize + dy;
283 if px < buf_width as usize {
284 let idx = py * bw + px;
285 if idx < buffer.len() {
286 buffer[idx] = entry_bg;
287 }
288 }
289 }
290 }
291
292 if selected {
294 let sel_border: u32 = 0xFF7AB4FF; for dx in 0..ew {
296 let px = x as usize + dx;
297 let idx_t = y as usize * bw + px;
299 if idx_t < buffer.len() {
300 buffer[idx_t] = sel_border;
301 }
302 let idx_b = (y as usize + eh - 1) * bw + px;
304 if idx_b < buffer.len() {
305 buffer[idx_b] = sel_border;
306 }
307 }
308 for dy in 0..eh {
309 let py = y as usize + dy;
310 let idx_l = py * bw + x as usize;
312 if idx_l < buffer.len() {
313 buffer[idx_l] = sel_border;
314 }
315 let idx_r = py * bw + x as usize + ew - 1;
317 if idx_r < buffer.len() {
318 buffer[idx_r] = sel_border;
319 }
320 }
321 }
322
323 let icon_size: u32 = 32;
325 let icon_x = x + (self.entry_width.saturating_sub(icon_size)) / 2;
326 let icon_y = y + 8;
327 render_icon(buffer, buf_width, icon_x, icon_y, icon_size, entry.app_icon);
328
329 let title_y = y + 8 + icon_size + 8;
331 let max_chars = (self.entry_width / 8) as usize;
332 let title_bytes = entry.title.as_bytes();
333 let display_len = title_bytes.len().min(max_chars);
334 let text_pixel_w = display_len * 8;
335 let title_x = x + (self.entry_width.saturating_sub(text_pixel_w as u32)) / 2;
336
337 let text_color: u32 = 0xFFFFFFFF;
338 let r = (text_color >> 16) & 0xFF;
339 let g = (text_color >> 8) & 0xFF;
340 let b = text_color & 0xFF;
341 let pixel = 0xFF00_0000 | (r << 16) | (g << 8) | b;
342
343 for (ci, &ch) in title_bytes[..display_len].iter().enumerate() {
344 let glyph = crate::graphics::font8x16::glyph(ch);
345 for (row, &bits) in glyph.iter().enumerate() {
346 for col in 0..8 {
347 if (bits >> (7 - col)) & 1 != 0 {
348 let px = title_x as usize + ci * 8 + col;
349 let py = title_y as usize + row;
350 if px < buf_width as usize {
351 let idx = py * bw + px;
352 if idx < buffer.len() {
353 buffer[idx] = pixel;
354 }
355 }
356 }
357 }
358 }
359 }
360 }
361}
362
363impl Default for AppSwitcher {
364 fn default() -> Self {
365 Self::new()
366 }
367}
368
369pub fn render_icon(buffer: &mut [u32], buf_width: u32, x: u32, y: u32, size: u32, icon: AppIcon) {
377 let bw = buf_width as usize;
378 let sz = size as usize;
379 let bx = x as usize;
380 let by = y as usize;
381
382 match icon {
383 AppIcon::Terminal => {
384 let fg: u32 = 0xFF00CC66; let bg: u32 = 0xFF1A1A1A; fill_rect(buffer, bw, bx, by, sz, sz, bg);
390
391 let m = sz / 6; let mid_y = sz / 2;
394 for i in 0..(mid_y - m) {
396 let px = bx + m + i;
397 let py = by + m + i;
398 if px < buf_width as usize {
399 let idx = py * bw + px;
400 if idx < buffer.len() {
401 buffer[idx] = fg;
402 }
403 }
404 }
405 for i in 0..(mid_y - m) {
407 let px = bx + m + i;
408 let py = by + mid_y + (mid_y - m).saturating_sub(1 + i);
409 if px < buf_width as usize {
410 let idx = py * bw + px;
411 if idx < buffer.len() {
412 buffer[idx] = fg;
413 }
414 }
415 }
416 let uy = by + mid_y + m;
418 let ux_start = bx + mid_y;
419 let ux_end = bx + sz - m;
420 for px in ux_start..ux_end {
421 if px < buf_width as usize {
422 let idx = uy * bw + px;
423 if idx < buffer.len() {
424 buffer[idx] = fg;
425 }
426 }
427 }
428 }
429 AppIcon::FileManager => {
430 let fg: u32 = 0xFFDDAA22; let tab_h = sz / 4;
435 let tab_w = sz / 2;
436 fill_rect(buffer, bw, bx + 2, by + 2, tab_w, tab_h, fg);
438 fill_rect(
440 buffer,
441 bw,
442 bx + 2,
443 by + 2 + tab_h,
444 sz - 4,
445 sz - tab_h - 4,
446 fg,
447 );
448 }
449 AppIcon::TextEditor => {
450 let bg: u32 = 0xFFEEEEEE; let fg: u32 = 0xFF333333; fill_rect(buffer, bw, bx + 4, by + 2, sz - 8, sz - 4, bg);
456
457 let line_gap = sz / 6;
459 for i in 1..5 {
460 let ly = by + 4 + i * line_gap;
461 let lx_start = bx + 8;
462 let lx_end = bx + sz - 8;
463 for px in lx_start..lx_end.min(bx + sz) {
464 if px < buf_width as usize && ly < by + sz {
465 let idx = ly * bw + px;
466 if idx < buffer.len() {
467 buffer[idx] = fg;
468 }
469 }
470 }
471 }
472 }
473 AppIcon::Settings => {
474 let fg: u32 = 0xFF888888; let center = sz / 2;
478 let outer_r = (sz / 2).saturating_sub(2);
479 let inner_r = outer_r / 2;
480 let outer_sq = (outer_r * outer_r) as i32;
481 let inner_sq = (inner_r * inner_r) as i32;
482
483 for dy in 0..sz {
484 for dx in 0..sz {
485 let cx = dx as i32 - center as i32;
486 let cy = dy as i32 - center as i32;
487 let dist_sq = cx * cx + cy * cy;
488 if dist_sq <= outer_sq && dist_sq >= inner_sq {
489 let px = bx + dx;
490 let py = by + dy;
491 if px < buf_width as usize {
492 let idx = py * bw + px;
493 if idx < buffer.len() {
494 buffer[idx] = fg;
495 }
496 }
497 }
498 }
499 }
500 }
501 AppIcon::ImageViewer => {
502 let sky: u32 = 0xFF6699CC; let mtn: u32 = 0xFF336633; fill_rect(buffer, bw, bx + 2, by + 2, sz - 4, sz - 4, sky);
508
509 let base_y = by + sz - 4;
511 let peak_x = bx + sz / 2;
512 let peak_y = by + sz / 4;
513 let half_base = sz / 3;
514
515 for row_y in peak_y..base_y {
516 let progress = row_y - peak_y;
517 let total = base_y - peak_y;
518 if total == 0 {
519 continue;
520 }
521 let half_w = (progress * half_base) / total;
522 let start_x = peak_x.saturating_sub(half_w);
523 let end_x = peak_x + half_w;
524 for px in start_x..end_x.min(bx + sz - 2) {
525 if px < buf_width as usize {
526 let idx = row_y * bw + px;
527 if idx < buffer.len() {
528 buffer[idx] = mtn;
529 }
530 }
531 }
532 }
533 }
534 AppIcon::Generic => {
535 let fg: u32 = 0xFF6688AA;
537 let bg: u32 = 0xFF334455;
538
539 fill_rect(buffer, bw, bx + 2, by + 2, sz - 4, sz - 4, bg);
540
541 for i in 0..sz {
543 set_pixel(buffer, bw, bx + i, by + 2, fg);
545 set_pixel(buffer, bw, bx + i, by + sz - 3, fg);
547 set_pixel(buffer, bw, bx + 2, by + i, fg);
549 set_pixel(buffer, bw, bx + sz - 3, by + i, fg);
551 }
552 }
553 }
554}
555
556fn guess_icon(title: &str) -> AppIcon {
562 let lower: Vec<u8> = title.bytes().map(|b| b.to_ascii_lowercase()).collect();
563 let lower_str = core::str::from_utf8(&lower).unwrap_or("");
564
565 if lower_str.contains("terminal")
566 || lower_str.contains("shell")
567 || lower_str.contains("console")
568 {
569 AppIcon::Terminal
570 } else if lower_str.contains("file") || lower_str.contains("folder") {
571 AppIcon::FileManager
572 } else if lower_str.contains("editor")
573 || lower_str.contains("text")
574 || lower_str.contains("code")
575 {
576 AppIcon::TextEditor
577 } else if lower_str.contains("setting") || lower_str.contains("config") {
578 AppIcon::Settings
579 } else if lower_str.contains("image")
580 || lower_str.contains("photo")
581 || lower_str.contains("view")
582 {
583 AppIcon::ImageViewer
584 } else {
585 AppIcon::Generic
586 }
587}
588
589fn fill_rect(
595 buffer: &mut [u32],
596 buf_width: usize,
597 x: usize,
598 y: usize,
599 w: usize,
600 h: usize,
601 color: u32,
602) {
603 for dy in 0..h {
604 for dx in 0..w {
605 let idx = (y + dy) * buf_width + (x + dx);
606 if idx < buffer.len() {
607 buffer[idx] = color;
608 }
609 }
610 }
611}
612
613fn set_pixel(buffer: &mut [u32], buf_width: usize, x: usize, y: usize, color: u32) {
615 let idx = y * buf_width + x;
616 if idx < buffer.len() {
617 buffer[idx] = color;
618 }
619}