Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/integration/overlay.rs
1use instant::Instant;
2 
3pub const OVERLAY_LOG_KEY: &str = "overlay_log";
4 
5#[cfg(feature = "integration_tests")]
6const CLICK_RING_DURATION: std::time::Duration = std::time::Duration::from_millis(900);
7#[cfg(feature = "integration_tests")]
8const KEY_DISPLAY_DURATION: std::time::Duration = std::time::Duration::from_millis(1500);
9#[cfg(feature = "integration_tests")]
10const DRAG_TRAIL_FADE_DURATION: std::time::Duration = std::time::Duration::from_millis(600);
11 
12#[cfg(feature = "integration_tests")]
13const MOUSE_DOWN_RADIUS: f32 = 16.0;
14#[cfg(feature = "integration_tests")]
15const CLICK_RING_MIN_RADIUS: f32 = 18.0;
16#[cfg(feature = "integration_tests")]
17const CLICK_RING_MAX_RADIUS: f32 = 36.0;
18#[cfg(feature = "integration_tests")]
19const CLICK_RING_THICKNESS: f32 = 4.0;
20#[cfg(feature = "integration_tests")]
21const DRAG_TRAIL_THICKNESS: f32 = 4.0;
22#[cfg(feature = "integration_tests")]
23const DRAG_ANCHOR_RADIUS: f32 = 10.0;
24 
25#[cfg(feature = "integration_tests")]
26const MOUSE_DOWN_COLOR: [u8; 4] = [255, 80, 40, 180];
27#[cfg(feature = "integration_tests")]
28const CLICK_RING_COLOR: [u8; 3] = [255, 80, 40];
29#[cfg(feature = "integration_tests")]
30const DRAG_TRAIL_COLOR: [u8; 4] = [255, 80, 40, 140];
31#[cfg(feature = "integration_tests")]
32const DRAG_ANCHOR_COLOR: [u8; 4] = [255, 80, 40, 120];
33#[cfg(feature = "integration_tests")]
34const KEY_BG_COLOR: [u8; 4] = [30, 30, 30, 200];
35#[cfg(feature = "integration_tests")]
36const KEY_TEXT_COLOR: [u8; 4] = [255, 255, 255, 255];
37 
38#[cfg(feature = "integration_tests")]
39const FONT_SOURCE_CHAR_W: u32 = 8;
40#[cfg(feature = "integration_tests")]
41const FONT_SOURCE_CHAR_H: u32 = 16;
42#[cfg(feature = "integration_tests")]
43const FONT_CHAR_W: u32 = 24;
44#[cfg(feature = "integration_tests")]
45const FONT_CHAR_H: u32 = 48;
46#[cfg(feature = "integration_tests")]
47const FONT_SUPERSAMPLE: u32 = 4;
48#[cfg(feature = "integration_tests")]
49const KEY_BOX_PADDING_X: u32 = 24;
50#[cfg(feature = "integration_tests")]
51const KEY_BOX_PADDING_Y: u32 = 12;
52#[cfg(feature = "integration_tests")]
53const KEY_BOX_MARGIN_BOTTOM: u32 = 48;
54#[cfg(feature = "integration_tests")]
55const KEY_BOX_SPACING_Y: u32 = 16;
56#[cfg(feature = "integration_tests")]
57const MAX_VISIBLE_KEY_EVENTS: usize = 4;
58 
59pub struct OverlayEvent {
60 pub recorded_at: Instant,
61 pub kind: OverlayKind,
62}
63 
64pub enum OverlayKind {
65 MouseDown { x: f32, y: f32 },
66 MouseMove { x: f32, y: f32 },
67 MouseUp { x: f32, y: f32 },
68 KeyPress { display_text: String },
69}
70 
71#[derive(Default)]
72pub struct OverlayLog {
73 events: Vec<OverlayEvent>,
74 scale_factor: f32,
75}
76 
77impl OverlayLog {
78 pub fn new() -> Self {
79 Self {
80 events: Vec::new(),
81 scale_factor: 2.0,
82 }
83 }
84 
85 pub fn set_scale_factor(&mut self, factor: f32) {
86 self.scale_factor = factor;
87 }
88 
89 pub fn record(&mut self, kind: OverlayKind) {
90 self.events.push(OverlayEvent {
91 recorded_at: Instant::now(),
92 kind,
93 });
94 }
95 
96 pub fn events(&self) -> &[OverlayEvent] {
97 &self.events
98 }
99 
100 pub fn scale_factor(&self) -> f32 {
101 self.scale_factor
102 }
103}
104 
105pub fn get_overlay_log_mut(
106 step_data_map: &mut super::step::StepDataMap,
107) -> Option<&mut OverlayLog> {
108 step_data_map.get_mut::<_, OverlayLog>(OVERLAY_LOG_KEY)
109}
110 
111pub fn get_overlay_log(step_data_map: &super::step::StepDataMap) -> Option<&OverlayLog> {
112 step_data_map.get::<_, OverlayLog>(OVERLAY_LOG_KEY)
113}
114 
115pub fn keystroke_display_text(keystroke: &crate::keymap::Keystroke) -> String {
116 let mut s = String::new();
117 if keystroke.ctrl {
118 s.push('\u{2303}');
119 }
120 if keystroke.alt {
121 s.push('\u{2325}');
122 }
123 if keystroke.shift {
124 s.push('\u{21e7}');
125 }
126 if keystroke.cmd {
127 s.push('\u{2318}');
128 }
129 let key = &keystroke.key;
130 match key.as_str() {
131 "enter" | "numpadenter" => s.push_str("Enter"),
132 "tab" => s.push_str("Tab"),
133 "escape" => s.push_str("Esc"),
134 "backspace" => s.push_str("Backspace"),
135 "delete" => s.push_str("Delete"),
136 " " => s.push_str("Space"),
137 "up" => s.push('\u{2191}'),
138 "down" => s.push('\u{2193}'),
139 "left" => s.push('\u{2190}'),
140 "right" => s.push('\u{2192}'),
141 other => {
142 if other.len() == 1 {
143 s.push_str(&other.to_uppercase());
144 } else {
145 s.push_str(other);
146 }
147 }
148 }
149 s
150}
151 
152#[cfg(feature = "integration_tests")]
153pub struct OverlayState {
154 event_cursor: usize,
155 mouse_held: bool,
156 held_pos: (f32, f32),
157 drag_trail: Vec<(f32, f32)>,
158 drag_ended_at: Option<Instant>,
159 recent_mouse_ups: Vec<(f32, f32, Instant)>,
160 recent_keys: Vec<(String, Instant)>,
161}
162 
163#[cfg(feature = "integration_tests")]
164impl Default for OverlayState {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169 
170#[cfg(feature = "integration_tests")]
171impl OverlayState {
172 pub fn new() -> Self {
173 Self {
174 event_cursor: 0,
175 mouse_held: false,
176 held_pos: (0.0, 0.0),
177 drag_trail: Vec::new(),
178 drag_ended_at: None,
179 recent_mouse_ups: Vec::new(),
180 recent_keys: Vec::new(),
181 }
182 }
183 
184 pub fn advance_to(&mut self, timestamp: Instant, events: &[OverlayEvent]) {
185 while self.event_cursor < events.len() {
186 let ev = &events[self.event_cursor];
187 if ev.recorded_at > timestamp {
188 break;
189 }
190 match &ev.kind {
191 OverlayKind::MouseDown { x, y } => {
192 self.mouse_held = true;
193 self.held_pos = (*x, *y);
194 self.drag_trail.clear();
195 self.drag_trail.push((*x, *y));
196 self.drag_ended_at = None;
197 }
198 OverlayKind::MouseMove { x, y } => {
199 if self.mouse_held {
200 self.held_pos = (*x, *y);
201 self.drag_trail.push((*x, *y));
202 }
203 }
204 OverlayKind::MouseUp { x, y } => {
205 self.mouse_held = false;
206 self.held_pos = (*x, *y);
207 if self.drag_trail.is_empty() {
208 self.drag_trail.push((*x, *y));
209 }
210 self.drag_ended_at = Some(ev.recorded_at);
211 self.recent_mouse_ups.push((*x, *y, ev.recorded_at));
212 }
213 OverlayKind::KeyPress { display_text } => {
214 self.recent_keys
215 .push((display_text.clone(), ev.recorded_at));
216 }
217 }
218 self.event_cursor += 1;
219 }
220 
221 self.recent_mouse_ups
222 .retain(|&(_, _, t)| timestamp.duration_since(t) < CLICK_RING_DURATION);
223 self.recent_keys
224 .retain(|&(_, t)| timestamp.duration_since(t) < KEY_DISPLAY_DURATION);
225 
226 if let Some(end_t) = self.drag_ended_at {
227 if timestamp.duration_since(end_t) >= DRAG_TRAIL_FADE_DURATION {
228 self.drag_trail.clear();
229 self.drag_ended_at = None;
230 }
231 }
232 }
233 
234 pub fn render_onto(
235 &self,
236 buf: &mut [u8],
237 width: u32,
238 height: u32,
239 timestamp: Instant,
240 scale: f32,
241 ) {
242 if self.drag_trail.len() > 1 {
243 let alpha_mult = if let Some(end_t) = self.drag_ended_at {
244 let elapsed = timestamp.duration_since(end_t).as_secs_f32();
245 let total = DRAG_TRAIL_FADE_DURATION.as_secs_f32();
246 (1.0 - (elapsed / total)).clamp(0.0, 1.0)
247 } else {
248 1.0
249 };
250 draw_drag_trail(buf, width, height, &self.drag_trail, scale, alpha_mult);
251 if let Some(&(x, y)) = self.drag_trail.first() {
252 draw_drag_anchor(buf, width, height, x * scale, y * scale, alpha_mult);
253 }
254 }
255 
256 if self.mouse_held {
257 let px = self.held_pos.0 * scale;
258 let py = self.held_pos.1 * scale;
259 draw_mouse_down_indicator(buf, width, height, px, py);
260 }
261 
262 for &(x, y, t) in &self.recent_mouse_ups {
263 let elapsed = timestamp.duration_since(t).as_secs_f32();
264 let total = CLICK_RING_DURATION.as_secs_f32();
265 let progress = (elapsed / total).clamp(0.0, 1.0);
266 let px = x * scale;
267 let py = y * scale;
268 draw_click_ring(buf, width, height, px, py, progress);
269 }
270 
271 if !self.recent_keys.is_empty() {
272 for (stack_index, (text, t)) in self
273 .recent_keys
274 .iter()
275 .rev()
276 .take(MAX_VISIBLE_KEY_EVENTS)
277 .enumerate()
278 {
279 let elapsed = timestamp.duration_since(*t).as_secs_f32();
280 let total = KEY_DISPLAY_DURATION.as_secs_f32();
281 let progress = (elapsed / total).clamp(0.0, 1.0);
282 draw_key_overlay(buf, width, height, text, progress, stack_index as u32);
283 }
284 }
285 }
286}
287 
288#[cfg(feature = "integration_tests")]
289fn blend_pixel(buf: &mut [u8], offset: usize, r: u8, g: u8, b: u8, a: u8) {
290 if a == 0 || offset + 3 >= buf.len() {
291 return;
292 }
293 let alpha = a as f32 / 255.0;
294 let inv = 1.0 - alpha;
295 buf[offset] = (buf[offset] as f32 * inv + r as f32 * alpha) as u8;
296 buf[offset + 1] = (buf[offset + 1] as f32 * inv + g as f32 * alpha) as u8;
297 buf[offset + 2] = (buf[offset + 2] as f32 * inv + b as f32 * alpha) as u8;
298 buf[offset + 3] = 255;
299}
300 
301#[cfg(feature = "integration_tests")]
302fn draw_filled_circle(
303 buf: &mut [u8],
304 width: u32,
305 height: u32,
306 center: (f32, f32),
307 r: f32,
308 color: [u8; 4],
309 alpha_mult: f32,
310) {
311 let (cx, cy) = center;
312 let r2 = r * r;
313 let x0 = ((cx - r - 1.0).max(0.0)) as u32;
314 let y0 = ((cy - r - 1.0).max(0.0)) as u32;
315 let x1 = ((cx + r + 1.0).min(width as f32 - 1.0)) as u32;
316 let y1 = ((cy + r + 1.0).min(height as f32 - 1.0)) as u32;
317 
318 for py in y0..=y1 {
319 for px in x0..=x1 {
320 let dx = px as f32 + 0.5 - cx;
321 let dy = py as f32 + 0.5 - cy;
322 let d2 = dx * dx + dy * dy;
323 if d2 <= r2 {
324 let edge = ((r - d2.sqrt()) * 2.0).clamp(0.0, 1.0);
325 let a = (color[3] as f32 * edge * alpha_mult) as u8;
326 let off = (py * width + px) as usize * 4;
327 blend_pixel(buf, off, color[0], color[1], color[2], a);
328 }
329 }
330 }
331}
332 
333#[cfg(feature = "integration_tests")]
334fn draw_mouse_down_indicator(buf: &mut [u8], width: u32, height: u32, cx: f32, cy: f32) {
335 draw_filled_circle(
336 buf,
337 width,
338 height,
339 (cx, cy),
340 MOUSE_DOWN_RADIUS,
341 MOUSE_DOWN_COLOR,
342 1.0,
343 );
344}
345 
346#[cfg(feature = "integration_tests")]
347fn draw_drag_anchor(buf: &mut [u8], width: u32, height: u32, cx: f32, cy: f32, alpha_mult: f32) {
348 draw_filled_circle(
349 buf,
350 width,
351 height,
352 (cx, cy),
353 DRAG_ANCHOR_RADIUS,
354 DRAG_ANCHOR_COLOR,
355 alpha_mult,
356 );
357}
358 
359#[cfg(feature = "integration_tests")]
360fn draw_click_ring(buf: &mut [u8], width: u32, height: u32, cx: f32, cy: f32, progress: f32) {
361 let radius = CLICK_RING_MIN_RADIUS + (CLICK_RING_MAX_RADIUS - CLICK_RING_MIN_RADIUS) * progress;
362 let alpha_f = (1.0 - progress) * 255.0;
363 let half_thick = CLICK_RING_THICKNESS / 2.0;
364 let outer = radius + half_thick;
365 let inner = (radius - half_thick).max(0.0);
366 
367 let x0 = ((cx - outer - 1.0).max(0.0)) as u32;
368 let y0 = ((cy - outer - 1.0).max(0.0)) as u32;
369 let x1 = ((cx + outer + 1.0).min(width as f32 - 1.0)) as u32;
370 let y1 = ((cy + outer + 1.0).min(height as f32 - 1.0)) as u32;
371 
372 for py in y0..=y1 {
373 for px in x0..=x1 {
374 let dx = px as f32 + 0.5 - cx;
375 let dy = py as f32 + 0.5 - cy;
376 let dist = (dx * dx + dy * dy).sqrt();
377 if dist >= inner && dist <= outer {
378 let edge_outer = ((outer - dist) * 2.0).clamp(0.0, 1.0);
379 let edge_inner = ((dist - inner) * 2.0).clamp(0.0, 1.0);
380 let edge = edge_outer.min(edge_inner);
381 let a = (alpha_f * edge) as u8;
382 let off = (py * width + px) as usize * 4;
383 blend_pixel(
384 buf,
385 off,
386 CLICK_RING_COLOR[0],
387 CLICK_RING_COLOR[1],
388 CLICK_RING_COLOR[2],
389 a,
390 );
391 }
392 }
393 }
394}
395 
396#[cfg(feature = "integration_tests")]
397fn draw_drag_trail(
398 buf: &mut [u8],
399 width: u32,
400 height: u32,
401 points: &[(f32, f32)],
402 scale: f32,
403 alpha_mult: f32,
404) {
405 for window in points.windows(2) {
406 let start = (window[0].0 * scale, window[0].1 * scale);
407 let end = (window[1].0 * scale, window[1].1 * scale);
408 draw_thick_line(buf, width, height, start, end, alpha_mult);
409 }
410}
411 
412#[cfg(feature = "integration_tests")]
413fn draw_thick_line(
414 buf: &mut [u8],
415 width: u32,
416 height: u32,
417 start: (f32, f32),
418 end: (f32, f32),
419 alpha_mult: f32,
420) {
421 let (x0, y0) = start;
422 let (x1, y1) = end;
423 let dx = x1 - x0;
424 let dy = y1 - y0;
425 let len = (dx * dx + dy * dy).sqrt();
426 if len < 0.5 {
427 return;
428 }
429 let steps = (len * 2.0) as u32;
430 let half_thick = DRAG_TRAIL_THICKNESS / 2.0;
431 
432 for i in 0..=steps {
433 let t = i as f32 / steps as f32;
434 let cx = x0 + dx * t;
435 let cy = y0 + dy * t;
436 
437 let px_min = ((cx - half_thick - 1.0).max(0.0)) as u32;
438 let py_min = ((cy - half_thick - 1.0).max(0.0)) as u32;
439 let px_max = ((cx + half_thick + 1.0).min(width as f32 - 1.0)) as u32;
440 let py_max = ((cy + half_thick + 1.0).min(height as f32 - 1.0)) as u32;
441 
442 for py in py_min..=py_max {
443 for px in px_min..=px_max {
444 let ddx = px as f32 + 0.5 - cx;
445 let ddy = py as f32 + 0.5 - cy;
446 let dist = (ddx * ddx + ddy * ddy).sqrt();
447 if dist <= half_thick {
448 let edge = ((half_thick - dist) * 2.0).clamp(0.0, 1.0);
449 let a = (DRAG_TRAIL_COLOR[3] as f32 * edge * alpha_mult) as u8;
450 let off = (py * width + px) as usize * 4;
451 blend_pixel(
452 buf,
453 off,
454 DRAG_TRAIL_COLOR[0],
455 DRAG_TRAIL_COLOR[1],
456 DRAG_TRAIL_COLOR[2],
457 a,
458 );
459 }
460 }
461 }
462 }
463}
464 
465#[cfg(feature = "integration_tests")]
466fn draw_key_overlay(
467 buf: &mut [u8],
468 width: u32,
469 height: u32,
470 text: &str,
471 progress: f32,
472 stack_index: u32,
473) {
474 let alpha_mult = if progress > 0.7 {
475 ((1.0 - progress) / 0.3).clamp(0.0, 1.0)
476 } else {
477 1.0
478 };
479 if alpha_mult < 0.01 {
480 return;
481 }
482 
483 let char_w = FONT_CHAR_W;
484 let char_h = FONT_CHAR_H;
485 let text_w = text.chars().count() as u32 * char_w;
486 let box_w = text_w + KEY_BOX_PADDING_X * 2;
487 let box_h = char_h + KEY_BOX_PADDING_Y * 2;
488 
489 let box_x = (width.saturating_sub(box_w)) / 2;
490 let stack_offset = stack_index.saturating_mul(box_h + KEY_BOX_SPACING_Y);
491 let box_y = height.saturating_sub(box_h + KEY_BOX_MARGIN_BOTTOM + stack_offset);
492 
493 let corner_r = 8u32;
494 for row in 0..box_h {
495 for col in 0..box_w {
496 let px = box_x + col;
497 let py = box_y + row;
498 if px >= width || py >= height {
499 continue;
500 }
501 
502 let dx_left = corner_r.saturating_sub(col);
503 let dx_right = col.saturating_sub(box_w - 1 - corner_r);
504 let dy_top = corner_r.saturating_sub(row);
505 let dy_bottom = row.saturating_sub(box_h - 1 - corner_r);
506 let dx = dx_left.max(dx_right);
507 let dy = dy_top.max(dy_bottom);
508 if dx > 0 && dy > 0 {
509 let dist = ((dx * dx + dy * dy) as f32).sqrt();
510 if dist > corner_r as f32 + 0.5 {
511 continue;
512 }
513 }
514 
515 let a = (KEY_BG_COLOR[3] as f32 * alpha_mult) as u8;
516 let off = (py * width + px) as usize * 4;
517 blend_pixel(
518 buf,
519 off,
520 KEY_BG_COLOR[0],
521 KEY_BG_COLOR[1],
522 KEY_BG_COLOR[2],
523 a,
524 );
525 }
526 }
527 
528 let text_x = box_x + KEY_BOX_PADDING_X;
529 let text_y = box_y + KEY_BOX_PADDING_Y;
530 draw_text(buf, width, height, text_x, text_y, text, alpha_mult);
531}
532 
533#[cfg(feature = "integration_tests")]
534fn draw_text(
535 buf: &mut [u8],
536 width: u32,
537 height: u32,
538 start_x: u32,
539 start_y: u32,
540 text: &str,
541 alpha_mult: f32,
542) {
543 let char_w = FONT_CHAR_W;
544 
545 for (i, ch) in text.chars().enumerate() {
546 let glyph = rasterize_glyph(ch);
547 let cx = start_x + i as u32 * char_w;
548 
549 for row in 0..FONT_CHAR_H {
550 for col in 0..FONT_CHAR_W {
551 let glyph_alpha = glyph[(row * FONT_CHAR_W + col) as usize];
552 if glyph_alpha == 0 {
553 continue;
554 }
555 
556 let px = cx + col;
557 let py = start_y + row;
558 if px < width && py < height {
559 let a = (glyph_alpha as f32 * alpha_mult) as u8;
560 let off = (py * width + px) as usize * 4;
561 blend_pixel(
562 buf,
563 off,
564 KEY_TEXT_COLOR[0],
565 KEY_TEXT_COLOR[1],
566 KEY_TEXT_COLOR[2],
567 a,
568 );
569 }
570 }
571 }
572 }
573}
574 
575#[cfg(feature = "integration_tests")]
576fn rasterize_glyph(ch: char) -> Vec<u8> {
577 let source = get_glyph(ch);
578 let mut bitmap = vec![0; (FONT_CHAR_W * FONT_CHAR_H) as usize];
579 
580 for row in 0..FONT_CHAR_H {
581 for col in 0..FONT_CHAR_W {
582 bitmap[(row * FONT_CHAR_W + col) as usize] = rasterize_glyph_pixel(source, col, row);
583 }
584 }
585 
586 bitmap
587}
588 
589#[cfg(feature = "integration_tests")]
590fn rasterize_glyph_pixel(source: &[u8; 16], x: u32, y: u32) -> u8 {
591 let mut covered_samples = 0u32;
592 let total_samples = FONT_SUPERSAMPLE * FONT_SUPERSAMPLE;
593 
594 for sample_y in 0..FONT_SUPERSAMPLE {
595 for sample_x in 0..FONT_SUPERSAMPLE {
596 let src_x = (x as f32 + (sample_x as f32 + 0.5) / FONT_SUPERSAMPLE as f32)
597 * FONT_SOURCE_CHAR_W as f32
598 / FONT_CHAR_W as f32;
599 let src_y = (y as f32 + (sample_y as f32 + 0.5) / FONT_SUPERSAMPLE as f32)
600 * FONT_SOURCE_CHAR_H as f32
601 / FONT_CHAR_H as f32;
602 let src_col = src_x.floor().min((FONT_SOURCE_CHAR_W - 1) as f32) as u32;
603 let src_row = src_y.floor().min((FONT_SOURCE_CHAR_H - 1) as f32) as usize;
604 if source[src_row] & (0x80 >> src_col) != 0 {
605 covered_samples += 1;
606 }
607 }
608 }
609 
610 ((covered_samples * 255) / total_samples) as u8
611}
612 
613#[cfg(feature = "integration_tests")]
614fn get_glyph(ch: char) -> &'static [u8; 16] {
615 let code = ch as u32;
616 if (0x20..0x7F).contains(&code) {
617 &FONT_DATA[(code - 0x20) as usize]
618 } else {
619 match ch {
620 '\u{2318}' => &GLYPH_CMD,
621 '\u{2303}' => &GLYPH_CTRL,
622 '\u{2325}' => &GLYPH_OPT,
623 '\u{21e7}' => &GLYPH_SHIFT,
624 '\u{21a9}' => &GLYPH_RETURN,
625 '\u{21e5}' => &GLYPH_TAB,
626 '\u{232b}' => &GLYPH_DELETE,
627 '\u{2326}' => &GLYPH_FWD_DELETE,
628 '\u{2191}' => &GLYPH_ARROW_UP,
629 '\u{2193}' => &GLYPH_ARROW_DOWN,
630 '\u{2190}' => &GLYPH_ARROW_LEFT,
631 '\u{2192}' => &GLYPH_ARROW_RIGHT,
632 _ => &GLYPH_FALLBACK,
633 }
634 }
635}
636 
637// 8x16 bitmap font for printable ASCII 0x20..0x7E (space through tilde).
638// Each glyph is 16 bytes, one byte per row, MSB = leftmost pixel.
639#[cfg(feature = "integration_tests")]
640#[rustfmt::skip]
641static FONT_DATA: [[u8; 16]; 95] = [
642 // 0x20 ' '
643 [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
644 // 0x21 '!'
645 [0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
646 // 0x22 '"'
647 [0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
648 // 0x23 '#'
649 [0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C,0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00],
650 // 0x24 '$'
651 [0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06,0x06,0x86,0xC6,0x7C,0x18,0x18,0x00,0x00],
652 // 0x25 '%'
653 [0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00],
654 // 0x26 '&'
655 [0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00],
656 // 0x27 '''
657 [0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
658 // 0x28 '('
659 [0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00],
660 // 0x29 ')'
661 [0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00],
662 // 0x2A '*'
663 [0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00],
664 // 0x2B '+'
665 [0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00],
666 // 0x2C ','
667 [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00],
668 // 0x2D '-'
669 [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
670 // 0x2E '.'
671 [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
672 // 0x2F '/'
673 [0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00],
674 // 0x30 '0'
675 [0x00,0x00,0x38,0x6C,0xC6,0xC6,0xD6,0xD6,0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00],
676 // 0x31 '1'
677 [0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00],
678 // 0x32 '2'
679 [0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30,0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00],
680 // 0x33 '3'
681 [0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00],
682 // 0x34 '4'
683 [0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00],
684 // 0x35 '5'
685 [0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00],
686 // 0x36 '6'
687 [0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
688 // 0x37 '7'
689 [0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00],
690 // 0x38 '8'
691 [0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
692 // 0x39 '9'
693 [0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06,0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00],
694 // 0x3A ':'
695 [0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00],
696 // 0x3B ';'
697 [0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00],
698 // 0x3C '<'
699 [0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00],
700 // 0x3D '='
701 [0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
702 // 0x3E '>'
703 [0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00],
704 // 0x3F '?'
705 [0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
706 // 0x40 '@'
707 [0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE,0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00],
708 // 0x41 'A'
709 [0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00],
710 // 0x42 'B'
711 [0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66,0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00],
712 // 0x43 'C'
713 [0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0,0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00],
714 // 0x44 'D'
715 [0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00],
716 // 0x45 'E'
717 [0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00],
718 // 0x46 'F'
719 [0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00],
720 // 0x47 'G'
721 [0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE,0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00],
722 // 0x48 'H'
723 [0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00],
724 // 0x49 'I'
725 [0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
726 // 0x4A 'J'
727 [0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00],
728 // 0x4B 'K'
729 [0x00,0x00,0xE6,0x66,0x66,0x6C,0x78,0x78,0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00],
730 // 0x4C 'L'
731 [0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00],
732 // 0x4D 'M'
733 [0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00],
734 // 0x4E 'N'
735 [0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00],
736 // 0x4F 'O'
737 [0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
738 // 0x50 'P'
739 [0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00],
740 // 0x51 'Q'
741 [0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00],
742 // 0x52 'R'
743 [0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00],
744 // 0x53 'S'
745 [0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C,0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
746 // 0x54 'T'
747 [0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
748 // 0x55 'U'
749 [0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
750 // 0x56 'V'
751 [0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00],
752 // 0x57 'W'
753 [0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0xEE,0x6C,0x00,0x00,0x00,0x00],
754 // 0x58 'X'
755 [0x00,0x00,0xC6,0xC6,0x6C,0x7C,0x38,0x38,0x7C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00],
756 // 0x59 'Y'
757 [0x00,0x00,0xC6,0xC6,0xC6,0x6C,0x38,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
758 // 0x5A 'Z'
759 [0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30,0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00],
760 // 0x5B '['
761 [0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00],
762 // 0x5C '\'
763 [0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00],
764 // 0x5D ']'
765 [0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00],
766 // 0x5E '^'
767 [0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
768 // 0x5F '_'
769 [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00],
770 // 0x60 '`'
771 [0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
772 // 0x61 'a'
773 [0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00],
774 // 0x62 'b'
775 [0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66,0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00],
776 // 0x63 'c'
777 [0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00],
778 // 0x64 'd'
779 [0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00],
780 // 0x65 'e'
781 [0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00],
782 // 0x66 'f'
783 [0x00,0x00,0x1C,0x36,0x32,0x30,0x78,0x30,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00],
784 // 0x67 'g'
785 [0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00],
786 // 0x68 'h'
787 [0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00],
788 // 0x69 'i'
789 [0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
790 // 0x6A 'j'
791 [0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00],
792 // 0x6B 'k'
793 [0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78,0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00],
794 // 0x6C 'l'
795 [0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
796 // 0x6D 'm'
797 [0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6,0xD6,0xD6,0xD6,0xC6,0x00,0x00,0x00,0x00],
798 // 0x6E 'n'
799 [0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00],
800 // 0x6F 'o'
801 [0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
802 // 0x70 'p'
803 [0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00],
804 // 0x71 'q'
805 [0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00],
806 // 0x72 'r'
807 [0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x66,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00],
808 // 0x73 's'
809 [0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00],
810 // 0x74 't'
811 [0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00],
812 // 0x75 'u'
813 [0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00],
814 // 0x76 'v'
815 [0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00],
816 // 0x77 'w'
817 [0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00],
818 // 0x78 'x'
819 [0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00],
820 // 0x79 'y'
821 [0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00],
822 // 0x7A 'z'
823 [0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18,0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00],
824 // 0x7B '{'
825 [0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00],
826 // 0x7C '|'
827 [0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00],
828 // 0x7D '}'
829 [0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00],
830 // 0x7E '~'
831 [0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
832];
833 
834// Special Unicode symbol glyphs (8x16 bitmaps)
835#[cfg(feature = "integration_tests")]
836#[rustfmt::skip]
837static GLYPH_CMD: [u8; 16] = [
838 0x00,0x00,0x6C,0x92,0x92,0x7C,0x28,0x7C,0x92,0x92,0x6C,0x00,0x00,0x00,0x00,0x00,
839];
840#[cfg(feature = "integration_tests")]
841#[rustfmt::skip]
842static GLYPH_CTRL: [u8; 16] = [
843 0x00,0x00,0x00,0x00,0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
844];
845#[cfg(feature = "integration_tests")]
846#[rustfmt::skip]
847static GLYPH_OPT: [u8; 16] = [
848 0x00,0x00,0x00,0xC0,0x60,0x30,0x18,0x0C,0x06,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,
849];
850#[cfg(feature = "integration_tests")]
851#[rustfmt::skip]
852static GLYPH_SHIFT: [u8; 16] = [
853 0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,
854];
855#[cfg(feature = "integration_tests")]
856#[rustfmt::skip]
857static GLYPH_RETURN: [u8; 16] = [
858 0x00,0x00,0x00,0x00,0x04,0x04,0x04,0x44,0x64,0x7E,0x20,0x00,0x00,0x00,0x00,0x00,
859];
860#[cfg(feature = "integration_tests")]
861#[rustfmt::skip]
862static GLYPH_TAB: [u8; 16] = [
863 0x00,0x00,0x00,0x00,0x20,0x60,0xFE,0x60,0x20,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,
864];
865#[cfg(feature = "integration_tests")]
866#[rustfmt::skip]
867static GLYPH_DELETE: [u8; 16] = [
868 0x00,0x00,0x00,0x7C,0xCC,0x0C,0x0C,0x3C,0x0C,0xCC,0x7C,0x00,0x00,0x00,0x00,0x00,
869];
870#[cfg(feature = "integration_tests")]
871#[rustfmt::skip]
872static GLYPH_FWD_DELETE: [u8; 16] = [
873 0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xFC,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00,0x00,
874];
875#[cfg(feature = "integration_tests")]
876#[rustfmt::skip]
877static GLYPH_ARROW_UP: [u8; 16] = [
878 0x00,0x00,0x18,0x3C,0x7E,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
879];
880#[cfg(feature = "integration_tests")]
881#[rustfmt::skip]
882static GLYPH_ARROW_DOWN: [u8; 16] = [
883 0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x7E,0x3C,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
884];
885#[cfg(feature = "integration_tests")]
886#[rustfmt::skip]
887static GLYPH_ARROW_LEFT: [u8; 16] = [
888 0x00,0x00,0x00,0x00,0x10,0x30,0x7E,0x30,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
889];
890#[cfg(feature = "integration_tests")]
891#[rustfmt::skip]
892static GLYPH_ARROW_RIGHT: [u8; 16] = [
893 0x00,0x00,0x00,0x00,0x08,0x0C,0x7E,0x0C,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
894];
895#[cfg(feature = "integration_tests")]
896#[rustfmt::skip]
897static GLYPH_FALLBACK: [u8; 16] = [
898 0x00,0x00,0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0x00,0x00,0x00,0x00,0x00,
899];
900