1use crate::rendering::common::BODY_PX;
4use crate::rendering::layout::word_wrap_bytes;
5use crate::rendering::text_render;
6
7pub fn paint_placeholder_box(
8 buf_bytes: &mut [u8],
9 screen_w: usize,
10 screen_h: usize,
11 x: usize,
12 y: usize,
13 w: usize,
14 h: usize,
15) {
16 for ry in 0..h {
17 for rx in 0..w {
18 let px = x + rx;
19 let py = y + ry;
20 if px >= screen_w || py >= screen_h {
21 continue;
22 }
23 let off = (py * screen_w + px) * 2;
24 if off + 2 > buf_bytes.len() {
25 continue;
26 }
27 let border = rx == 0 || ry == 0 || rx == w - 1 || ry == h - 1;
28 let v = if border { 0x6B4D } else { 0xEF5D };
29 buf_bytes[off] = (v & 0xff) as u8;
30 buf_bytes[off + 1] = (v >> 8) as u8;
31 }
32 }
33}
34
35pub fn fill_rounded_rect(
36 buf_bytes: &mut [u8],
37 screen_w: usize,
38 screen_h: usize,
39 x: usize,
40 y: usize,
41 w: usize,
42 h: usize,
43 fill: u16,
44 border: u16,
45 radius: usize,
46) {
47 let r = radius.min(w / 2).min(h / 2);
48 let put = |buf: &mut [u8], px: usize, py: usize, v: u16| {
49 if px >= screen_w || py >= screen_h {
50 return;
51 }
52 let off = (py * screen_w + px) * 2;
53 if off + 2 > buf.len() {
54 return;
55 }
56 buf[off] = (v & 0xff) as u8;
57 buf[off + 1] = (v >> 8) as u8;
58 };
59 for ry in 0..h {
60 for rx in 0..w {
61 let cx_edge = if rx < r {
62 r - rx
63 } else if rx >= w - r {
64 rx - (w - 1 - r)
65 } else {
66 0
67 };
68 let cy_edge = if ry < r {
69 r - ry
70 } else if ry >= h - r {
71 ry - (h - 1 - r)
72 } else {
73 0
74 };
75 let in_corner = (rx < r || rx >= w - r) && (ry < r || ry >= h - r);
76 let v = if in_corner {
77 let dist2 =
78 (cx_edge as i32) * (cx_edge as i32) + (cy_edge as i32) * (cy_edge as i32);
79 if dist2 > (r as i32) * (r as i32) {
80 continue;
81 } else if dist2 >= ((r - 1) as i32) * ((r - 1) as i32) {
82 border
83 } else {
84 fill
85 }
86 } else {
87 let on_edge = rx == 0 || ry == 0 || rx == w - 1 || ry == h - 1;
88 if on_edge {
89 border
90 } else {
91 fill
92 }
93 };
94 put(buf_bytes, x + rx, y + ry, v);
95 }
96 }
97}
98
99pub fn paint_progress_bar(
100 buf_bytes: &mut [u8],
101 screen_w: usize,
102 screen_h: usize,
103 x: usize,
104 y: usize,
105 w: usize,
106 frac: f32,
107) {
108 let bar_h = 8usize;
109 for ry in 0..bar_h {
110 let py = y + ry;
111 if py >= screen_h {
112 break;
113 }
114 for rx in 0..w {
115 let px = x + rx;
116 if px >= screen_w {
117 break;
118 }
119 let off = (py * screen_w + px) * 2;
120 if off + 2 > buf_bytes.len() {
121 continue;
122 }
123 let filled = (rx as f32 / w as f32) < frac;
124 let v = if filled { 0x1082 } else { 0xD6BA };
125 buf_bytes[off] = (v & 0xff) as u8;
126 buf_bytes[off + 1] = (v >> 8) as u8;
127 }
128 }
129}
130
131pub fn measure_text(text: &str, px: f32) -> usize {
132 let space_w = text_render::word_width(" ", px);
133 let mut total = 0.0f32;
134 for (i, word) in text.split(' ').enumerate() {
135 if i > 0 {
136 total += space_w;
137 }
138 if !word.is_empty() {
139 total += text_render::word_width(word, px);
140 }
141 }
142 total as usize
143}
144
145pub fn truncate_to_width(s: &str, px: f32, max_w: usize) -> String {
146 if measure_text(s, px) <= max_w {
147 return s.to_string();
148 }
149 let mut out: String = s.chars().take(3).collect();
150 for ch in s.chars().skip(3) {
151 out.push(ch);
152 if measure_text(&(out.clone() + "..."), px) > max_w {
153 out.pop();
154 break;
155 }
156 }
157 out.push_str("...");
158 out
159}
160
161pub const ACTION_BTN_FILL: u16 = 0x0349;
162pub const ACTION_BTN_FG: u16 = 0xFFFF;
163pub const ACTION_BTN_RADIUS: usize = 10;
164
165pub fn paint_action_button(
166 buf_bytes: &mut [u8],
167 screen_w: usize,
168 screen_h: usize,
169 x: usize,
170 y: usize,
171 w: usize,
172 h: usize,
173 label: &str,
174 px: f32,
175) {
176 fill_rounded_rect(
177 buf_bytes,
178 screen_w,
179 screen_h,
180 x,
181 y,
182 w,
183 h,
184 ACTION_BTN_FILL,
185 ACTION_BTN_FILL,
186 ACTION_BTN_RADIUS,
187 );
188 let lw = measure_text(label, px);
189 let lh = text_render::line_height(px);
190 let tx = x + (w.saturating_sub(lw)) / 2;
191 let ty = y + (h.saturating_sub(lh)) / 2;
192 text_render::blit_rgb565_color(
193 buf_bytes,
194 screen_w,
195 label,
196 px,
197 tx,
198 ty,
199 ACTION_BTN_FG,
200 screen_w,
201 screen_h,
202 );
203}
204
205pub fn paint_nav_bar(
206 buf_bytes: &mut [u8],
207 screen_w: usize,
208 screen_h: usize,
209 nav_bar_h: usize,
210 y: usize,
211 center: &str,
212 clock: &str,
213 battery: i32,
214) {
215 let h = nav_bar_h;
216 let top = y;
217 for ry in 0..h {
218 let py = top + ry;
219 if py >= screen_h {
220 break;
221 }
222 let is_edge = ry == 0;
223 for rx in 0..screen_w {
224 let off = (py * screen_w + rx) * 2;
225 if off + 2 > buf_bytes.len() {
226 break;
227 }
228 let v = if is_edge { 0x1082 } else { 0xFFFF };
229 buf_bytes[off] = (v & 0xff) as u8;
230 buf_bytes[off + 1] = (v >> 8) as u8;
231 }
232 }
233 let label_px = BODY_PX * 0.8;
234 let small_px = BODY_PX * 0.66;
235 let lh = text_render::line_height(label_px);
236 let sh = text_render::line_height(small_px);
237 let cy = top + (h.saturating_sub(lh)) / 2;
238 let sy = top + (h.saturating_sub(sh)) / 2;
239
240 let exit_w = 150usize;
241 let exit_h = 56usize.min(h - 8);
242 let exit_x = 12usize;
243 let exit_y = top + (h.saturating_sub(exit_h)) / 2;
244 paint_action_button(
245 buf_bytes, screen_w, screen_h, exit_x, exit_y, exit_w, exit_h, "Exit", label_px,
246 );
247
248 let status = if battery > 0 {
249 format!("{} {}%", clock, battery)
250 } else {
251 clock.to_string()
252 };
253 if !status.is_empty() {
254 let sw = measure_text(&status, small_px);
255 let sx = screen_w.saturating_sub(sw + 24);
256 text_render::blit_rgb565(
257 buf_bytes, screen_w, &status, small_px, sx, sy, screen_w, screen_h,
258 );
259 }
260 if !center.is_empty() {
261 let label_w = measure_text(center, label_px);
262 let cx = (screen_w / 2).saturating_sub(label_w / 2);
263 text_render::blit_rgb565(
264 buf_bytes, screen_w, center, label_px, cx, cy, screen_w, screen_h,
265 );
266 }
267}
268
269pub fn paint_wrapped_text(
270 buf_bytes: &mut [u8],
271 screen_w: usize,
272 screen_h: usize,
273 text: &str,
274 x: usize,
275 y: usize,
276 max_w: usize,
277 px: f32,
278 max_lines: usize,
279) -> usize {
280 let lines = word_wrap_bytes(text, max_w, px);
281 let lh = text_render::line_height(px);
282 let mut cy = y;
283 for (i, line) in lines.iter().enumerate() {
284 if i >= max_lines {
285 break;
286 }
287 text_render::blit_rgb565(
288 buf_bytes, screen_w, &line.text, px, x, cy, screen_w, screen_h,
289 );
290 cy += lh;
291 }
292 cy - y
293}
294
295#[cfg(test)]
296mod tests;