1use crate::{fb::Framebuffer, palette::col};
9
10pub fn panel(fb: &mut Framebuffer, x0: i32, y0: i32, x1: i32, y1: i32, bg: u8, border: u8) {
12 fb.rectfill(x0, y0, x1, y1, bg);
13 fb.rect(x0, y0, x1, y1, border);
14}
15
16pub fn shadow_text(fb: &mut Framebuffer, s: &str, x: i32, y: i32, color: u8, shadow: u8) {
18 fb.print(s, x + 1, y + 1, shadow);
19 fb.print(s, x, y, color);
20}
21
22pub fn selection(fb: &mut Framebuffer, x0: i32, y0: i32, x1: i32, y1: i32, fg: u8, bg: u8) {
25 for y in y0..=y1 {
26 for x in x0..=x1 {
27 let c = fb.pget(x, y);
28 let n = if c == bg {
29 fg
30 } else if c == fg {
31 bg
32 } else {
33 c
34 };
35 fb.pset(x, y, n);
36 }
37 }
38}
39
40pub type Icon = [u8; 8];
43
44pub const ICON_CODE: Icon = [0x00, 0x7C, 0x42, 0x44, 0x78, 0x44, 0x42, 0x00];
46pub const ICON_SPRITE: Icon = [0x00, 0xA5, 0x42, 0x7E, 0xDB, 0x7E, 0x24, 0x00];
48pub const ICON_MAP: Icon = [0x00, 0xFF, 0x91, 0xFF, 0x91, 0x91, 0xFF, 0x00];
50pub const ICON_SFX: Icon = [0x00, 0x06, 0x0E, 0x7E, 0x7E, 0x0E, 0x06, 0x00];
52pub const ICON_MUSIC: Icon = [0x00, 0x3C, 0x24, 0x20, 0x20, 0xE0, 0xE0, 0x00];
54
55pub fn icon(fb: &mut Framebuffer, icon: &Icon, x: i32, y: i32, color: u8) {
57 for (ry, row) in icon.iter().enumerate() {
58 for rx in 0..8 {
59 if row & (0x80 >> rx) != 0 {
60 fb.pset(x + rx, y + ry as i32, color);
61 }
62 }
63 }
64}
65
66const CURSOR_FILL: Icon = [
69 0b00000000, 0b01000000, 0b01100000, 0b01110000, 0b01111000, 0b01100000, 0b00100000, 0b00000000,
70];
71const CURSOR_OUTLINE: Icon = [
72 0b11000000, 0b10100000, 0b10010000, 0b10001000, 0b10000100, 0b10011100, 0b11010000, 0b00110000,
73];
74
75pub fn error_screen(message: &str) -> Framebuffer {
79 use crate::fb::{HEIGHT, WIDTH};
80 let mut fb = Framebuffer::new();
81 fb.cls(col::BLACK);
82 fb.rectfill(0, 0, WIDTH - 1, 7, col::RED);
83 fb.print("Pixel8", 2, 1, col::WHITE);
84 fb.print("** Runtime error **", 2, 14, col::RED);
85 let mut y = 24;
86 for line in message.lines().take(12) {
87 let mut rest = line;
88 while !rest.is_empty() && y < HEIGHT - 8 {
89 let take = rest
90 .char_indices()
91 .nth(31)
92 .map(|(i, _)| i)
93 .unwrap_or(rest.len());
94 fb.print(&rest[..take], 2, y, col::ORANGE);
95 rest = &rest[take..];
96 y += 6;
97 }
98 }
99 fb
100}
101
102pub fn cursor(fb: &mut Framebuffer, x: i32, y: i32) {
104 for (icon, color) in [(&CURSOR_OUTLINE, col::BLACK), (&CURSOR_FILL, col::WHITE)] {
105 for (ry, row) in icon.iter().enumerate() {
106 for rx in 0..8 {
107 if row & (0x80 >> rx) != 0 {
108 fb.pset(x + rx, y + ry as i32, color);
109 }
110 }
111 }
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn tab_icons_share_a_uniform_envelope() {
121 for (name, icon) in [
122 ("code", &ICON_CODE),
123 ("sprite", &ICON_SPRITE),
124 ("map", &ICON_MAP),
125 ("sfx", &ICON_SFX),
126 ("music", &ICON_MUSIC),
127 ] {
128 assert_eq!(
129 icon[0], 0,
130 "{name} icon: row 0 must be blank for even top spacing"
131 );
132 assert_eq!(
133 icon[7], 0,
134 "{name} icon: row 7 must be blank for even bottom spacing"
135 );
136 }
137 }
138}