1use embassy_futures::select::{Either3, select3};
2use embassy_time::{Duration, Ticker};
3use embedded_graphics::{
4 mono_font::{MonoTextStyleBuilder, ascii::{FONT_6X10, FONT_8X13, FONT_9X15}},
5 pixelcolor::{Rgb565, Rgb888},
6 prelude::*,
7 primitives::{Rectangle, RoundedRectangle, CornerRadii, PrimitiveStyleBuilder, Circle, Line},
8 text::{Baseline, Text},
9};
10
11use crate::{
12 config::CONST_CONFIG,
13 drivers::interface::{display::DisplayDriver, reporter::Output},
14 utils::{Channel, Signal},
15};
16
17use super::{DisplayConfig, DisplayMessage};
18
19const SIN_TABLE: [i32; 32] = [
21 0, 19, 38, 55, 70, 83, 92, 98,
22 100, 98, 92, 83, 70, 55, 38, 19,
23 0, -19, -38, -55, -70, -83, -92, -98,
24 -100, -98, -92, -83, -70, -55, -38, -19
25];
26
27#[inline]
28fn get_sin(idx: u32) -> i32 {
29 SIN_TABLE[(idx % 32) as usize]
30}
31
32#[inline]
33fn rgb(r: u8, g: u8, b: u8) -> Rgb565 {
34 Rgb565::from(Rgb888::new(r, g, b))
35}
36
37fn draw_centered_text<D: DrawTarget<Color = Rgb565>>(
38 target: &mut D,
39 text: &str,
40 font: &embedded_graphics::mono_font::MonoFont,
41 rect: Rectangle,
42 text_color: Rgb565,
43) -> Result<(), D::Error> {
44 let text_width = text.len() as i32 * font.character_size.width as i32;
45 let text_height = font.character_size.height as i32;
46 let x = rect.top_left.x + (rect.size.width as i32 - text_width) / 2;
47 let y = rect.top_left.y + (rect.size.height as i32 - text_height) / 2;
48
49 let text_style = MonoTextStyleBuilder::new()
50 .font(font)
51 .text_color(text_color)
52 .build();
53
54 Text::with_baseline(text, Point::new(x, y), text_style, Baseline::Top).draw(target)?;
55 Ok(())
56}
57
58async fn draw_dashboard<D: DisplayDriver<Color = Rgb565>>(
59 display: &mut D,
60 layer_state: &[bool],
61 caps_lock: bool,
62 num_lock: bool,
63 output_mode: Output,
64 mouse_available: bool,
65 anim_tick: u32,
66) {
67 let target = display.draw_target();
68
69 let bg_style = PrimitiveStyleBuilder::new()
71 .fill_color(rgb(10, 10, 15))
72 .build();
73 let _ = Rectangle::new(Point::zero(), Size::new(284, 76)).into_styled(bg_style).draw(target);
74
75 let panel_border_style = PrimitiveStyleBuilder::new()
77 .stroke_color(rgb(0, 180, 216))
78 .stroke_width(1)
79 .build();
80
81 let _ = RoundedRectangle::new(
83 Rectangle::new(Point::new(6, 6), Size::new(66, 64)),
84 CornerRadii::new(Size::new(6, 6)),
85 )
86 .into_styled(panel_border_style)
87 .draw(target);
88
89 let brand_style = MonoTextStyleBuilder::new()
91 .font(&FONT_9X15)
92 .text_color(rgb(0, 180, 216))
93 .build();
94 let _ = Text::with_baseline("RKTK", Point::new(18, 12), brand_style, Baseline::Top).draw(target);
95
96 let line_style = PrimitiveStyleBuilder::new()
98 .stroke_color(rgb(0, 70, 90))
99 .stroke_width(1)
100 .build();
101 let _ = Line::new(Point::new(14, 29), Point::new(64, 29)).into_styled(line_style).draw(target);
102
103 let (conn_text, conn_bg, conn_fg) = match output_mode {
105 Output::Usb => ("USB", rgb(0, 60, 20), rgb(0, 255, 100)),
106 Output::Ble => ("BLE", rgb(0, 30, 80), rgb(0, 180, 255)),
107 };
108 let badge_rect = Rectangle::new(Point::new(12, 34), Size::new(54, 16));
109 let badge_style = PrimitiveStyleBuilder::new()
110 .fill_color(conn_bg)
111 .build();
112 let _ = RoundedRectangle::new(badge_rect, CornerRadii::new(Size::new(4, 4)))
113 .into_styled(badge_style)
114 .draw(target);
115 let _ = draw_centered_text(target, conn_text, &FONT_6X10, badge_rect, conn_fg);
116
117 let bat_border_style = PrimitiveStyleBuilder::new()
119 .stroke_color(rgb(60, 70, 80))
120 .stroke_width(1)
121 .build();
122 let _ = Rectangle::new(Point::new(14, 55), Size::new(32, 6))
123 .into_styled(bat_border_style)
124 .draw(target);
125
126 let bat_fill_style = PrimitiveStyleBuilder::new()
127 .fill_color(if mouse_available { rgb(0, 220, 100) } else { rgb(0, 200, 255) })
128 .build();
129 let _ = Rectangle::new(Point::new(15, 56), Size::new(26, 4))
130 .into_styled(bat_fill_style)
131 .draw(target);
132
133 let center_border_style = PrimitiveStyleBuilder::new()
135 .stroke_color(rgb(40, 50, 70))
136 .stroke_width(1)
137 .build();
138 let _ = RoundedRectangle::new(
139 Rectangle::new(Point::new(78, 6), Size::new(128, 64)),
140 CornerRadii::new(Size::new(6, 6)),
141 )
142 .into_styled(center_border_style)
143 .draw(target);
144
145 let label_style = MonoTextStyleBuilder::new()
146 .font(&FONT_6X10)
147 .text_color(rgb(120, 130, 150))
148 .build();
149 let _ = Text::with_baseline("ACTIVE LAYER", Point::new(86, 12), label_style, Baseline::Top).draw(target);
150
151 let active_layer = layer_state.iter().position(|&x| x).unwrap_or(0);
153 let (layer_name, layer_color) = match active_layer {
154 0 => ("BASE", rgb(0, 100, 150)),
155 1 => ("LOWER", rgb(180, 80, 0)),
156 2 => ("RAISE", rgb(130, 0, 180)),
157 3 => ("NAV", rgb(0, 120, 40)),
158 4 => ("MEDIA", rgb(180, 0, 80)),
159 _ => ("LAYER X", rgb(60, 60, 60)),
160 };
161
162 let layer_badge_rect = Rectangle::new(Point::new(86, 24), Size::new(112, 20));
163 let layer_badge_style = PrimitiveStyleBuilder::new()
164 .fill_color(layer_color)
165 .build();
166 let _ = RoundedRectangle::new(layer_badge_rect, CornerRadii::new(Size::new(4, 4)))
167 .into_styled(layer_badge_style)
168 .draw(target);
169 let _ = draw_centered_text(target, layer_name, &FONT_8X13, layer_badge_rect, rgb(255, 255, 255));
170
171 let caps_rect = Rectangle::new(Point::new(86, 49), Size::new(52, 14));
174 if caps_lock {
175 let caps_active_style = PrimitiveStyleBuilder::new()
176 .fill_color(rgb(220, 0, 100))
177 .build();
178 let _ = RoundedRectangle::new(caps_rect, CornerRadii::new(Size::new(3, 3)))
179 .into_styled(caps_active_style)
180 .draw(target);
181 let _ = draw_centered_text(target, "CAPS", &FONT_6X10, caps_rect, rgb(255, 255, 255));
182 } else {
183 let caps_inactive_style = PrimitiveStyleBuilder::new()
184 .stroke_color(rgb(40, 50, 60))
185 .stroke_width(1)
186 .build();
187 let _ = RoundedRectangle::new(caps_rect, CornerRadii::new(Size::new(3, 3)))
188 .into_styled(caps_inactive_style)
189 .draw(target);
190 let _ = draw_centered_text(target, "CAPS", &FONT_6X10, caps_rect, rgb(80, 90, 100));
191 }
192
193 let num_rect = Rectangle::new(Point::new(146, 49), Size::new(52, 14));
195 if num_lock {
196 let num_active_style = PrimitiveStyleBuilder::new()
197 .fill_color(rgb(0, 180, 100))
198 .build();
199 let _ = RoundedRectangle::new(num_rect, CornerRadii::new(Size::new(3, 3)))
200 .into_styled(num_active_style)
201 .draw(target);
202 let _ = draw_centered_text(target, "NUM", &FONT_6X10, num_rect, rgb(255, 255, 255));
203 } else {
204 let num_inactive_style = PrimitiveStyleBuilder::new()
205 .stroke_color(rgb(40, 50, 60))
206 .stroke_width(1)
207 .build();
208 let _ = RoundedRectangle::new(num_rect, CornerRadii::new(Size::new(3, 3)))
209 .into_styled(num_inactive_style)
210 .draw(target);
211 let _ = draw_centered_text(target, "NUM", &FONT_6X10, num_rect, rgb(80, 90, 100));
212 }
213
214 let _ = RoundedRectangle::new(
216 Rectangle::new(Point::new(212, 6), Size::new(66, 64)),
217 CornerRadii::new(Size::new(6, 6)),
218 )
219 .into_styled(panel_border_style)
220 .draw(target);
221
222 let _ = Text::with_baseline("LIVE", Point::new(220, 12), label_style, Baseline::Top).draw(target);
224
225 let dot_active = (anim_tick % 20) < 10;
227 let dot_style = PrimitiveStyleBuilder::new()
228 .fill_color(if dot_active { rgb(255, 0, 50) } else { rgb(80, 0, 10) })
229 .build();
230 let _ = Circle::new(Point::new(254, 14), 5).into_styled(dot_style).draw(target);
231
232 let bar_color = match active_layer {
234 0 => rgb(0, 200, 255),
235 1 => rgb(255, 120, 0),
236 2 => rgb(200, 0, 255),
237 3 => rgb(0, 255, 100),
238 4 => rgb(255, 0, 120),
239 _ => rgb(150, 150, 150),
240 };
241
242 let bar_fill_style = PrimitiveStyleBuilder::new()
243 .fill_color(bar_color)
244 .build();
245
246 for i in 0..8 {
249 let phase1 = (anim_tick.wrapping_mul(2).wrapping_add(i * 3)) % 32;
250 let phase2 = (anim_tick.wrapping_add(i * 7)) % 32;
251 let val = (get_sin(phase1) + get_sin(phase2)) / 2; let bar_height = 4 + ((val + 100) * 30) / 200;
255
256 let x = 222 + (i as i32 * 6);
257 let y = 60 - bar_height;
258
259 let _ = Rectangle::new(Point::new(x, y), Size::new(4, bar_height as u32))
260 .into_styled(bar_fill_style)
261 .draw(target);
262 }
263}
264
265pub struct ColorBarDisplayConfig;
266
267impl DisplayConfig for ColorBarDisplayConfig {
268 type Color = Rgb565;
269
270 async fn start<D: DisplayDriver<Color = Self::Color>, const N1: usize, const N2: usize>(
271 &mut self,
272 display: &mut D,
273 display_controller: &Channel<DisplayMessage, N1>,
274 display_dynamic_message_controller: &Signal<heapless::String<N2>>,
275 ) {
276 let mut layer_state = [false; CONST_CONFIG.key_manager.layer_count as usize];
277 layer_state[0] = true; let mut caps_lock = false;
279 let mut num_lock = false;
280 let mut output_mode = Output::Usb;
281 let mut mouse_available = false;
282 let mut anim_tick = 0u32;
283 let mut anim_ticker = Ticker::every(Duration::from_millis(50));
284
285 let _ = display.clear().await;
287 draw_dashboard(
288 display,
289 &layer_state,
290 caps_lock,
291 num_lock,
292 output_mode,
293 mouse_available,
294 anim_tick,
295 )
296 .await;
297 let _ = display.flush().await;
298
299 loop {
300 let select_res = select3(
302 display_controller.receive(),
303 display_dynamic_message_controller.wait(),
304 anim_ticker.next(),
305 )
306 .await;
307
308 let mut state_changed = false;
309
310 match select_res {
311 Either3::First(mes) => match mes {
312 DisplayMessage::Clear => {
313 let _ = display.clear().await;
314 state_changed = true;
315 }
316 DisplayMessage::Message(_msg) => {
317 }
319 DisplayMessage::Output(output) => {
320 output_mode = output;
321 state_changed = true;
322 }
323 DisplayMessage::LayerState(layers) => {
324 layer_state = layers;
325 state_changed = true;
326 }
327 DisplayMessage::MouseAvailable(mouse) => {
328 mouse_available = mouse;
329 state_changed = true;
330 }
331 DisplayMessage::NumLock(nl) => {
332 num_lock = nl;
333 state_changed = true;
334 }
335 DisplayMessage::CapsLock(cl) => {
336 caps_lock = cl;
337 state_changed = true;
338 }
339 DisplayMessage::Brightness(brightness) => {
340 let _ = display.set_brightness(brightness).await;
341 }
342 DisplayMessage::On(on) => {
343 let _ = display.set_display_on(on).await;
344 }
345 _ => {}
346 },
347 Either3::Second(_str) => {
348 }
350 Either3::Third(_) => {
351 anim_tick = anim_tick.wrapping_add(1);
353 state_changed = true;
354 }
355 }
356
357 if state_changed {
358 draw_dashboard(
359 display,
360 &layer_state,
361 caps_lock,
362 num_lock,
363 output_mode,
364 mouse_available,
365 anim_tick,
366 )
367 .await;
368 let _ = display.flush().await;
369 }
370 }
371 }
372}