1use crate::AppError;
7use crate::core::app::{App, AppCtx, AppResult};
8use alloc::string::{String, ToString as _};
9use alloc::vec::Vec;
10use embedded_graphics::{
11 mono_font::{MonoTextStyle, ascii::FONT_9X15},
12 pixelcolor::Rgb888,
13 prelude::*,
14 primitives::{PrimitiveStyleBuilder, Rectangle},
15 text::Text,
16};
17use uefi::proto::console::text::{Key, ScanCode};
18
19pub struct ErrorOverlay<'a> {
22 error: &'a AppError,
23}
24
25impl<'a> ErrorOverlay<'a> {
26 pub fn new(error: &'a AppError) -> Self {
28 Self { error }
29 }
30}
31
32impl<'a> App for ErrorOverlay<'a> {
33 fn run(&mut self, ctx: &mut AppCtx) -> AppResult {
34 let text = alloc::format!("{}", self.error);
35 draw_error_overlay(ctx, &text);
36
37 if let Err(err) = ctx.display.flush() {
38 return AppResult::Error(err.into());
39 }
40
41 loop {
42 let mut events = [unsafe { ctx.input.wait_for_key_event().unwrap_unchecked() }];
43
44 if uefi::boot::wait_for_event(&mut events).is_err() {
45 return AppResult::Error(uefi::Status::INVALID_PARAMETER.into());
46 }
47
48 if let Ok(Some(key)) = ctx.input.read_key() {
49 if matches!(key, Key::Printable(c) if c == '\r' || c == '\n') {
50 return AppResult::Done;
51 }
52 if matches!(key, Key::Special(ScanCode::END)) {
53 return AppResult::Done;
54 }
55 }
56 }
57 }
58}
59
60fn draw_error_overlay(ctx: &mut AppCtx, text: &str) {
61 let size = ctx.display.size();
62 let screen_w = size.width as i32;
63 let screen_h = size.height as i32;
64 let box_w = (screen_w * 2 / 3).max(280);
65 let box_h = (screen_h / 3).max(120);
66 let left = (screen_w - box_w) / 2;
67 let top = (screen_h - box_h) / 2;
68
69 let background = PrimitiveStyleBuilder::new()
70 .fill_color(Rgb888::new(20, 20, 20))
71 .stroke_color(Rgb888::new(220, 220, 220))
72 .stroke_width(2)
73 .build();
74 Rectangle::new(Point::new(left, top), Size::new(box_w as u32, box_h as u32))
75 .into_styled(background)
76 .draw(ctx.display)
77 .ok();
78
79 let title_style = MonoTextStyle::new(&FONT_9X15, Rgb888::new(255, 80, 80));
80 let body_style = MonoTextStyle::new(&FONT_9X15, Rgb888::WHITE);
81
82 let padding_x = 12;
83 let padding_y = 16;
84 let line_height = 18;
85 let max_chars = ((box_w - padding_x * 2) / 9).max(1) as usize;
86 let max_lines = ((box_h - padding_y * 2) / line_height).max(1) as usize;
87
88 Text::new(
89 "Error",
90 Point::new(left + padding_x, top + padding_y),
91 title_style,
92 )
93 .draw(ctx.display)
94 .ok();
95
96 let lines = wrap_lines(text, max_chars, max_lines.saturating_sub(1));
97 for (idx, line) in lines.iter().enumerate() {
98 let y = top + padding_y + line_height * (idx as i32 + 1);
99 Text::new(line.as_str(), Point::new(left + padding_x, y), body_style)
100 .draw(ctx.display)
101 .ok();
102 }
103}
104
105fn wrap_lines(text: &str, max_chars: usize, max_lines: usize) -> Vec<String> {
106 let mut lines = Vec::new();
107 for raw in text.lines() {
108 let mut start = 0;
109 let bytes = raw.as_bytes();
110 while start < bytes.len() {
111 let end = (start + max_chars).min(bytes.len());
112 let slice = &raw[start..end];
113 lines.push(slice.to_string());
114 start = end;
115 if lines.len() >= max_lines {
116 return lines;
117 }
118 }
119 if lines.len() >= max_lines {
120 return lines;
121 }
122 }
123 lines
124}