Skip to main content

kobo_core/rendering/
draw.rs

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