1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum MouseResizeMode {
3 None,
4 MiddleDrag,
5}
6
7use log::{error, info};
8use std::sync::mpsc;
9use uxn::Uxn;
10use varvara::{Key, Varvara};
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum MouseDragMode {
13 None,
14 DoubleDrag,
15 }
17pub struct EffectsConfig {
18 pub efxt: f32,
19 pub effect_mode: usize,
20 pub effect_order: Vec<usize>,
21 pub efx: Option<String>,
22 pub efxmode: Option<String>,
23 pub efx_ndx: usize, pub last_effect_switch: f64, }
26
27pub struct StageConfig {
28 pub size: (u16, u16),
29 pub scale: f32,
30 pub rom_title: String,
31 pub transparent: Option<String>,
32 pub color_transform_name: String,
33 pub color_params: Vec<f32>,
34 pub effects: EffectsConfig,
35 pub fit_mode: String,
36 pub mouse_mode: MouseDragMode,
37 pub mouse_resize: MouseResizeMode,
38}
39
40#[derive(Debug)]
41pub enum Event {
42 LoadRom(Vec<u8>),
43 SetMuted(bool),
44 Console(u8),
45}
46
47pub trait ColorTransform {
49 fn apply(&self, r: u8, g: u8, b: u8, x: usize, y: usize, t: f32) -> (u8, u8, u8);
50}
51
52pub struct ColorShift {
53 pub r: f32,
54 pub g: f32,
55 pub b: f32,
56}
57impl ColorTransform for ColorShift {
58 fn apply(&self, r: u8, g: u8, b: u8, _x: usize, _y: usize, _t: f32) -> (u8, u8, u8) {
59 (
60 ((r as f32 + self.r * 255.0).clamp(0.0, 255.0)) as u8,
61 ((g as f32 + self.g * 255.0).clamp(0.0, 255.0)) as u8,
62 ((b as f32 + self.b * 255.0).clamp(0.0, 255.0)) as u8,
63 )
64 }
65}
66
67pub struct Invert;
68impl ColorTransform for Invert {
69 fn apply(&self, r: u8, g: u8, b: u8, _x: usize, _y: usize, _t: f32) -> (u8, u8, u8) {
70 (255 - r, 255 - g, 255 - b)
71 }
72}
73
74pub struct Grayscale;
75impl ColorTransform for Grayscale {
76 fn apply(&self, r: u8, g: u8, b: u8, _x: usize, _y: usize, _t: f32) -> (u8, u8, u8) {
77 let avg = ((r as u16 + g as u16 + b as u16) / 3) as u8;
78 (avg, avg, avg)
79 }
80}
81
82pub fn get_transform(name: &str, params: &[f32]) -> Box<dyn ColorTransform> {
83 match name {
84 "shift" => Box::new(ColorShift {
85 r: params.first().copied().unwrap_or(0.0),
86 g: params.get(1).copied().unwrap_or(0.0),
87 b: params.get(2).copied().unwrap_or(0.0),
88 }),
89 "invert" => Box::new(Invert),
90 "grayscale" => Box::new(Grayscale),
91 _ => Box::new(ColorShift {
92 r: 0.0,
93 g: 0.0,
94 b: 0.0,
95 }),
96 }
97}
98
99pub struct Stage<'a> {
100 pub should_exit: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
102 pub vm: Uxn<'a>,
103 pub dev: Varvara,
104 pub scale: f32,
105 pub size: (u16, u16),
106 pub next_frame: f64,
107 pub scroll: (f32, f32),
108 pub cursor_pos: Option<(f32, f32)>,
109 pub texture: Option<egui::TextureHandle>,
110 pub event_rx: mpsc::Receiver<Event>,
111 pub resized: Option<Box<dyn FnMut(u16, u16)>>,
112 pub transparent_rgb: Option<(u8, u8, u8)>,
113 pub stopped: bool,
114 pub drag_started: bool,
115 pub color_transform: Box<dyn ColorTransform>,
116 pub rom_title: String,
117 pub config: StageConfig,
118 pub drag_window: bool,
119 pub drag_start_pos: Option<egui::Pos2>,
120 pub resize_start_size: Option<(u16, u16)>,
121 pub last_resize_size: Option<(u16, u16)>,
122}
123
124impl<'a> Stage<'a> {
125 pub fn new(
126 vm: Uxn<'a>,
127 dev: Varvara,
128 event_rx: mpsc::Receiver<Event>,
129 config: StageConfig,
130 should_exit: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
131 ) -> Self {
132 let transparent_rgb = config.transparent.as_ref().and_then(|s| {
138 if s.len() == 6 {
139 let r = u8::from_str_radix(&s[0..2], 16).ok()?;
140 let g = u8::from_str_radix(&s[2..4], 16).ok()?;
141 let b = u8::from_str_radix(&s[4..6], 16).ok()?;
142 Some((r, g, b))
143 } else {
144 None
145 }
146 });
147 let color_transform = get_transform(&config.color_transform_name, &config.color_params);
148 Stage {
158 vm,
159 dev,
160 scale: config.scale,
161 size: config.size,
162 next_frame: 0.0,
163 event_rx,
164 resized: None,
165 scroll: (0.0, 0.0),
166 cursor_pos: None,
167 texture: None,
168 transparent_rgb,
169 drag_window: false,
170 drag_start_pos: None,
171 stopped: false,
172 drag_started: false,
173 color_transform,
174 rom_title: config.rom_title.clone(),
175 should_exit,
176 config,
177 resize_start_size: None,
178 last_resize_size: None,
179 }
180 }
181
182 pub fn set_resize_callback(&mut self, f: Box<dyn FnMut(u16, u16)>) {
183 self.resized = Some(f);
184 }
185 pub fn load_rom(&mut self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
186 let _ = self.vm.reset(data);
187 self.dev.reset(data);
188 self.vm.run(&mut self.dev, 0x100);
189 let out = self.dev.output(&self.vm);
190 self.size = out.size;
191 Ok(())
192 }
193 pub fn step(&mut self) {
194 self.dev.redraw(&mut self.vm);
196 }
197 pub fn update_texture(&mut self, ctx: &egui::Context) {
198 let out = self.dev.output(&self.vm);
200 let mut image = egui::ColorImage::new(
201 [out.size.0 as usize, out.size.1 as usize],
202 vec![egui::Color32::BLACK; (out.size.0 as usize) * (out.size.1 as usize)],
203 );
204 let mut x = 0;
205 let mut y = 0;
206 let w = out.size.0 as usize;
207 let h = out.size.1 as usize;
208 let effects = &self.config.effects;
209 let effect_index = if let Some(ref efx_name) = effects.efx {
210 let name = efx_name.to_ascii_lowercase();
211 if name == "random" || name == "sequential" {
212 effects.efx_ndx
213 } else {
214 crate::effects::effect_name_to_index(efx_name).unwrap_or(0)
215 }
216 } else {
217 0
218 };
219 {
223 use std::sync::atomic::{AtomicUsize, Ordering};
224 static LAST_PRINTED_INDEX: AtomicUsize = AtomicUsize::new(usize::MAX);
225 let blend_mode_str = self
226 .config
227 .effects
228 .efxmode
229 .clone()
230 .unwrap_or_else(|| "none".to_string());
231 if let Some(ref efx_name) = effects.efx {
232 let name = efx_name.to_ascii_lowercase();
233 if name == "random" || name == "sequential" {
234 let last = LAST_PRINTED_INDEX.load(Ordering::Relaxed);
235 if last != effect_index {
236 println!(
237 "Effect cycling: index {} - {} | blend mode: {}",
238 effect_index,
239 crate::effects::effect_name(effect_index),
240 blend_mode_str
241 );
242 LAST_PRINTED_INDEX.store(effect_index, Ordering::Relaxed);
243 }
244 }
245 }
246 }
247 let t = ctx.input(|i| i.time) as f32;
249 let zebra_offset = effects.efx_ndx;
250 let blend_mode = if let Some(ref mode) = effects.efxmode {
251 match mode.as_str() {
252 "blend" => crate::effects::BlendMode::BlendMode,
253 "full" => crate::effects::BlendMode::FullMode,
254 "fullDyn" | "fulldyn" => crate::effects::BlendMode::FullDyn,
255 "random" => crate::effects::BlendMode::Random,
256 "diagonal" => crate::effects::BlendMode::Diagonal,
257 "checker" => crate::effects::BlendMode::Checker,
258 "threshold" => crate::effects::BlendMode::Threshold,
259 "intensity" => crate::effects::BlendMode::Intensity,
260 "anylit" => crate::effects::BlendMode::AnyLit,
261 "optimal" => crate::effects::BlendMode::Optimal,
262 "optimalperframe" => crate::effects::BlendMode::OptimalPerFrame,
263 "randommode" => crate::effects::BlendMode::RandomMode,
264 "randommodemode" => crate::effects::BlendMode::RandomModeMode,
265 _ => crate::effects::BlendMode::Optimal,
266 }
267 } else {
268 crate::effects::BlendMode::Optimal
269 };
270
271 let _selected_blend = blend_mode;
273 use std::collections::HashSet;
274 use once_cell::sync::OnceCell;
276 static OPTIMAL_COLORS: OnceCell<HashSet<(u8, u8, u8)>> = OnceCell::new();
277 static OPTIMAL_INTENSITIES: OnceCell<HashSet<u16>> = OnceCell::new();
278 let (unique_colors_len, unique_intensities_len) = match blend_mode {
279 crate::effects::BlendMode::Optimal => {
280 let colors = OPTIMAL_COLORS.get_or_init(|| {
281 let mut set = HashSet::new();
282 for i in out.frame.chunks(4) {
283 set.insert((i[2], i[1], i[0]));
284 }
285 set
286 });
287 let intensities = OPTIMAL_INTENSITIES.get_or_init(|| {
288 let mut set = HashSet::new();
289 for i in out.frame.chunks(4) {
290 let intensity = (i[2] as u16 + i[1] as u16 + i[0] as u16) / 3;
291 set.insert(intensity);
292 }
293 set
294 });
295 if !colors.is_empty()
297 && !intensities.is_empty()
298 && colors.len() + intensities.len() == colors.len() + intensities.len()
299 {
300 static PRINTED: std::sync::atomic::AtomicBool =
302 std::sync::atomic::AtomicBool::new(false);
303 if !PRINTED.swap(true, std::sync::atomic::Ordering::Relaxed) {
304 println!(
305 "[BlendMode::Optimal] unique_colors: {}, unique_intensities: {}",
306 colors.len(),
307 intensities.len()
308 );
309 }
310 }
311 (colors.len(), intensities.len())
312 }
313 crate::effects::BlendMode::OptimalPerFrame => {
314 let mut unique_colors = HashSet::new();
315 let mut unique_intensities = HashSet::new();
316 for i in out.frame.chunks(4) {
317 unique_colors.insert((i[2], i[1], i[0]));
318 let intensity = (i[2] as u16 + i[1] as u16 + i[0] as u16) / 3;
319 unique_intensities.insert(intensity);
320 }
321 println!(
322 "[BlendMode::Optimal] unique_colors: {}, unique_intensities: {}",
323 unique_colors.len(),
324 unique_intensities.len()
325 );
326 (unique_colors.len(), unique_intensities.len())
327 }
328 _ => (0, 0),
329 };
330
331 if matches!(
333 blend_mode,
334 crate::effects::BlendMode::Optimal | crate::effects::BlendMode::OptimalPerFrame
335 ) {
336 if unique_colors_len <= 3 || unique_intensities_len <= 3 {
337 self.config.effects.efxmode = Some("none".to_string());
338 } else if unique_colors_len < 10 || unique_intensities_len < 10 {
339 self.config.effects.efxmode = Some("full".to_string());
340 } else {
341 self.config.effects.efxmode = Some("blend".to_string());
342 }
343 }
344
345 for (i, o) in out.frame.chunks(4).zip(image.pixels.iter_mut()) {
346 let r = i[2];
347 let g = i[1];
348 let b = i[0];
349 let a = if let Some((tr, tg, tb)) = self.transparent_rgb {
350 if r == tr && g == tg && b == tb {
351 0
352 } else {
353 255
354 }
355 } else {
356 255
357 };
358 *o = crate::effects::apply_effect_blend(
359 effect_index,
360 r,
361 g,
362 b,
363 a,
364 x,
365 y,
366 w,
367 h,
368 t,
369 zebra_offset,
370 blend_mode,
371 );
372 x += 1;
373 if x >= w {
374 x = 0;
375 y += 1;
376 }
377 }
378 if let Some(texture) = &mut self.texture {
379 texture.set(image, egui::TextureOptions::NEAREST);
380 } else {
381 self.texture = Some(ctx.load_texture("frame", image, egui::TextureOptions::NEAREST));
382 }
383 }
384 pub fn draw(&self, ui: &mut egui::Ui) {
385 let window_size = ui.available_size();
386 let rom_size = egui::Vec2::new(self.size.0 as f32, self.size.1 as f32) * self.scale;
387 let fit_mode = self.config.fit_mode.as_str();
388 let (_rect_id, _full_rect) = ui.allocate_space(window_size);
389 let (draw_size, offset) = match fit_mode {
390 "cover" => {
391 let scale = f32::max(window_size.x / rom_size.x, window_size.y / rom_size.y);
392 let size = rom_size * scale;
393 let offset = (window_size - size) / 2.0;
394 (size, offset)
395 }
396 "stretch" => (window_size, egui::Vec2::ZERO),
397 "none" => {
398 let size = egui::Vec2::new(
399 self.size.0 as f32 * self.scale,
400 self.size.1 as f32 * self.scale,
401 );
402 (size, egui::Vec2::ZERO)
403 }
404 _ => {
405 let scale = f32::min(window_size.x / rom_size.x, window_size.y / rom_size.y);
406 let size = rom_size * scale;
407 let offset = (window_size - size) / 2.0;
408 (size, offset)
409 }
410 };
411 let rect = egui::Rect::from_min_size(ui.min_rect().min + offset, draw_size);
412 if let Some(texture) = &self.texture {
413 let mut mesh = egui::Mesh::with_texture(texture.id());
414 mesh.add_rect_with_uv(
415 rect,
416 egui::Rect {
417 min: egui::Pos2::new(0.0, 0.0),
418 max: egui::Pos2::new(1.0, 1.0),
419 },
420 egui::Color32::WHITE,
421 );
422 ui.painter().add(egui::Shape::mesh(mesh));
423 }
424 }
425
426 pub fn handle_usb_input(&mut self) {
427 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
428 {
429 if let Some(controller_usb) = self.dev.controller_usb_mut() {
430 let events = varvara::controller_usb::ControllerPollEvents::poll_usb_events(
431 controller_usb,
432 &mut self.vm,
433 );
434 for event in events {
435 self.dev.process_event(&mut self.vm, event);
436 }
437 }
438 }
439 }
448
449 pub fn load_symbols(&mut self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
451 let map = varvara::Varvara::parse_symbols_from_bytes(data)
452 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
453 self.dev.symbols = Some(map);
454 Ok(())
455 }
456 pub fn load_rom_with_path<P: AsRef<std::path::Path>>(&mut self, path: P) {
458 let path = path.as_ref();
459 if let Ok(data) = std::fs::read(path) {
460 let _ = self.vm.reset(&data);
461 self.dev.reset(&data);
462 self.vm.run(&mut self.dev, 0x100);
463 let out = self.dev.output(&self.vm);
464 self.size = out.size;
465 if let Some(file_name) = path.file_name() {
467 use std::ffi::OsString;
468 use std::path::PathBuf;
469 let mut sys_file = OsString::from(file_name);
470 sys_file.push(".sym");
471 let mut sys_path = PathBuf::from(path);
472 sys_path.set_file_name(sys_file);
473 #[cfg(windows)]
474 let mut sys_path_str = sys_path.to_string_lossy().to_string();
475 #[cfg(windows)]
476 {
477 sys_path_str = sys_path_str.replace("/", "\\");
478 }
479 #[cfg(not(windows))]
480 let sys_path_str = sys_path.to_string_lossy().to_string();
481 println!("[DEBUG][Stage] Attempting to load symbols from {sys_path_str}");
482 if sys_path.exists() {
483 let _ = self.dev.load_symbols_into_self(&sys_path_str);
484 println!("[DEBUG][Stage] Loaded symbols from {sys_path_str}");
485 }
486 }
487 }
488 }
489
490 pub fn handle_mouse_input(
492 &mut self,
493 _ctx: &egui::Context,
494 input: &egui::InputState,
495 _response: &egui::Response,
496 rect: egui::Rect,
497 ) {
498 if let Some(mouse_pos) = input.pointer.hover_pos() {
499 if rect.contains(mouse_pos) {
500 let rel_x = (mouse_pos.x - rect.min.x).max(0.0).min(rect.width());
501 let rel_y = (mouse_pos.y - rect.min.y).max(0.0).min(rect.height());
502 let mut buttons = 0u8;
503 if input.pointer.button_down(egui::PointerButton::Primary) {
504 buttons |= 1;
505 }
506 if input.pointer.button_down(egui::PointerButton::Middle) {
507 buttons |= 2;
508 }
509 if input.pointer.button_down(egui::PointerButton::Secondary) {
510 buttons |= 4;
511 }
512 let mouse_state = varvara::MouseState {
513 pos: (rel_x, rel_y),
514 scroll: (0.0, 0.0),
515 buttons,
516 };
517 self.dev.mouse(&mut self.vm, mouse_state);
518
519 }
522 }
523 for event in &input.events {
525 if let egui::Event::PointerButton {
526 pos,
527 button,
528 pressed,
529 ..
530 } = event
531 {
532 if rect.contains(*pos) {
533 let rel_x = (pos.x - rect.min.x).max(0.0).min(rect.width());
534 let rel_y = (pos.y - rect.min.y).max(0.0).min(rect.height());
535 let mut buttons = 0u8;
536 if *button == egui::PointerButton::Primary && *pressed {
537 buttons |= 1;
538 }
539 if *button == egui::PointerButton::Middle && *pressed {
540 buttons |= 2;
541 }
542 if *button == egui::PointerButton::Secondary && *pressed {
543 buttons |= 4;
544 }
545 let mouse_state = varvara::MouseState {
546 pos: (rel_x, rel_y),
547 scroll: (0.0, 0.0),
548 buttons,
549 };
550 self.dev.mouse(&mut self.vm, mouse_state);
551 }
552 }
553 }
554 }
555
556 pub fn handle_keyboard_input(&mut self, input: &egui::InputState) {
558 for event in &input.events {
559 if let egui::Event::Key { key, pressed, .. } = event {
560 if let Some(varvara_key) = map_egui_key_to_varvara_key(*key) {
561 println!("[DEBUG][Stage] Forwarding key: {key:?} pressed={pressed} to VM as {varvara_key:?}");
562 if *pressed {
563 self.dev.pressed(&mut self.vm, varvara_key, false);
564 } else {
565 self.dev.released(&mut self.vm, varvara_key);
566 }
567 } else {
568 println!("[DEBUG][Stage] Ignored key: {key:?}");
569 }
570 }
571 }
572 }
573
574 pub fn draw_at(&self, ui: &mut egui::Ui, rect: egui::Rect) {
576 let panel_size = egui::Vec2::new(self.size.0 as f32, self.size.1 as f32) * self.scale;
577 if let Some(texture) = &self.texture {
578 let tex_size = texture.size();
579 println!("[draw_at] Texture size: {:?}", tex_size);
580 let mut mesh = egui::Mesh::with_texture(texture.id());
581 mesh.add_rect_with_uv(
582 egui::Rect {
583 min: rect.min,
584 max: rect.min + panel_size,
585 },
586 egui::Rect {
587 min: egui::Pos2::new(0.0, 0.0),
588 max: egui::Pos2::new(1.0, 1.0),
589 },
590 egui::Color32::WHITE,
591 );
592 ui.painter().add(egui::Shape::mesh(mesh));
593 }
594 }
595
596 pub fn handle_input(&mut self, input: &egui::InputState, rect: egui::Rect) {
598 for event in &input.events {
600 if let egui::Event::Text(s) = event {
601 for c in s.chars() {
602 if c.is_ascii_graphic() || c == ' ' {
604 let byte = c as u8;
605 println!("[DEBUG][Stage] Forwarding char event: '{c}' (0x{byte:02x}) to VM (from egui::Event::Text)");
606 self.dev
607 .pressed(&mut self.vm, varvara::Key::Char(byte), false);
608 self.dev.char(&mut self.vm, byte);
609 } else {
610 println!(
611 "[DEBUG][Stage] Ignored char event: '{}' (0x{:02x}) (not printable)",
612 c, c as u8
613 );
614 }
615 }
616 }
617 }
618 use egui::Key;
620 for event in &input.events {
621 if let egui::Event::Key { key, pressed, .. } = event {
622 let is_printable = matches!(
623 key,
624 Key::A
625 | Key::B
626 | Key::C
627 | Key::D
628 | Key::E
629 | Key::F
630 | Key::G
631 | Key::H
632 | Key::I
633 | Key::J
634 | Key::K
635 | Key::L
636 | Key::M
637 | Key::N
638 | Key::O
639 | Key::P
640 | Key::Q
641 | Key::R
642 | Key::S
643 | Key::T
644 | Key::U
645 | Key::V
646 | Key::W
647 | Key::X
648 | Key::Y
649 | Key::Z
650 | Key::Num0
651 | Key::Num1
652 | Key::Num2
653 | Key::Num3
654 | Key::Num4
655 | Key::Num5
656 | Key::Num6
657 | Key::Num7
658 | Key::Num8
659 | Key::Num9
660 | Key::Space
661 | Key::Tab
662 | Key::Backtick
663 | Key::Backslash
664 | Key::Pipe
665 | Key::Comma
666 | Key::Equals
667 | Key::Plus
668 | Key::OpenBracket
669 | Key::Minus
670 | Key::Period
671 | Key::CloseBracket
672 | Key::Semicolon
673 | Key::Colon
674 | Key::Slash
675 | Key::Questionmark
676 );
677 if !is_printable {
678 if let Some(varvara_key) = map_egui_key_to_varvara_key(*key) {
679 println!("[DEBUG][Stage] Forwarding non-printable key: {key:?} pressed={pressed} to VM as {varvara_key:?}");
680 if *pressed {
681 self.dev.pressed(&mut self.vm, varvara_key, false);
682 } else {
683 self.dev.released(&mut self.vm, varvara_key);
684 }
685 } else {
686 println!("[DEBUG][Stage] Ignored key: {key:?}");
687 }
688 }
689 }
690 }
691 if let Some(mouse_pos) = input.pointer.hover_pos() {
693 if rect.contains(mouse_pos) {
694 let rel_x = (mouse_pos.x - rect.min.x).max(0.0).min(rect.width());
695 let rel_y = (mouse_pos.y - rect.min.y).max(0.0).min(rect.height());
696 let mut buttons = 0u8;
697 if input.pointer.button_down(egui::PointerButton::Primary) {
698 buttons |= 1;
699 }
700 if input.pointer.button_down(egui::PointerButton::Middle) {
701 buttons |= 2;
702 }
703 if input.pointer.button_down(egui::PointerButton::Secondary) {
704 buttons |= 4;
705 }
706 let mouse_state = varvara::MouseState {
707 pos: (rel_x, rel_y),
708 scroll: (0.0, 0.0),
709 buttons,
710 };
711 self.dev.mouse(&mut self.vm, mouse_state);
712 }
713 }
714 for event in &input.events {
716 if let egui::Event::PointerButton {
717 pos,
718 button,
719 pressed,
720 ..
721 } = event
722 {
723 if rect.contains(*pos) {
724 let rel_x = (pos.x - rect.min.x).max(0.0).min(rect.width());
725 let rel_y = (pos.y - rect.min.y).max(0.0).min(rect.height());
726 let mut buttons = 0u8;
727 if *button == egui::PointerButton::Primary && *pressed {
728 buttons |= 1;
729 }
730 if *button == egui::PointerButton::Middle && *pressed {
731 buttons |= 2;
732 }
733 if *button == egui::PointerButton::Secondary && *pressed {
734 buttons |= 4;
735 }
736 let mouse_state = varvara::MouseState {
737 pos: (rel_x, rel_y),
738 scroll: (0.0, 0.0),
739 buttons,
740 };
741 println!("[DEBUG][Stage] Forwarding pointer button: {button:?} pressed={pressed} at rel=({rel_x:.1},{rel_y:.1})");
742 self.dev.mouse(&mut self.vm, mouse_state);
743 }
744 }
745 }
746 }
747
748 pub fn shutdown(&mut self) {
750 self.stopped = true;
752 self.dev.audio_set_muted(true);
754 }
755}
756
757fn map_egui_key_to_varvara_key(key: egui::Key) -> Option<varvara::Key> {
759 use egui::Key;
760 use varvara::Key as VKey;
761 Some(match key {
762 Key::ArrowUp => VKey::Up,
763 Key::ArrowDown => VKey::Down,
764 Key::ArrowLeft => VKey::Left,
765 Key::ArrowRight => VKey::Right,
766 Key::Home => VKey::Home,
767 Key::End => VKey::End,
768 Key::Tab => VKey::Char(b'\t'),
769 Key::Backspace => VKey::Char(8),
770 Key::Enter => VKey::Char(b'\n'),
771 Key::Space => VKey::Char(b' '),
772 Key::A => VKey::Char(b'a'),
773 Key::B => VKey::Char(b'b'),
774 Key::C => VKey::Char(b'c'),
775 Key::D => VKey::Char(b'd'),
776 Key::E => VKey::Char(b'e'),
777 Key::F => VKey::Char(b'f'),
778 Key::G => VKey::Char(b'g'),
779 Key::H => VKey::Char(b'h'),
780 Key::I => VKey::Char(b'i'),
781 Key::J => VKey::Char(b'j'),
782 Key::K => VKey::Char(b'k'),
783 Key::L => VKey::Char(b'l'),
784 Key::M => VKey::Char(b'm'),
785 Key::N => VKey::Char(b'n'),
786 Key::O => VKey::Char(b'o'),
787 Key::P => VKey::Char(b'p'),
788 Key::Q => VKey::Char(b'q'),
789 Key::R => VKey::Char(b'r'),
790 Key::S => VKey::Char(b's'),
791 Key::T => VKey::Char(b't'),
792 Key::U => VKey::Char(b'u'),
793 Key::V => VKey::Char(b'v'),
794 Key::W => VKey::Char(b'w'),
795 Key::X => VKey::Char(b'x'),
796 Key::Y => VKey::Char(b'y'),
797 Key::Z => VKey::Char(b'z'),
798 Key::Num0 => VKey::Char(b'0'),
799 Key::Num1 => VKey::Char(b'1'),
800 Key::Num2 => VKey::Char(b'2'),
801 Key::Num3 => VKey::Char(b'3'),
802 Key::Num4 => VKey::Char(b'4'),
803 Key::Num5 => VKey::Char(b'5'),
804 Key::Num6 => VKey::Char(b'6'),
805 Key::Num7 => VKey::Char(b'7'),
806 Key::Num8 => VKey::Char(b'8'),
807 Key::Num9 => VKey::Char(b'9'),
808 _ => return None,
809 })
810}
811
812impl<'a> eframe::App for Stage<'a> {
813 fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
814 [0.0, 0.0, 0.0, 0.0]
815 }
816
817 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
818 let available = ctx.available_rect().size();
820 let rom_size = egui::Vec2::new(self.size.0 as f32, self.size.1 as f32) * self.scale;
821 let fit_mode = self.config.fit_mode.as_str();
822 let (_draw_size, _offset) = match fit_mode {
823 "cover" => {
824 let scale = f32::max(available.x / rom_size.x, available.y / rom_size.y);
825 let size = rom_size * scale;
826 let offset = (available - size) / 2.0;
827 (size, offset)
828 }
829 "stretch" => (available, egui::Vec2::ZERO),
830 "none" => {
831 let size = egui::Vec2::new(
832 self.size.0 as f32 * self.scale,
833 self.size.1 as f32 * self.scale,
834 );
835 (size, egui::Vec2::ZERO)
836 }
837 _ => {
838 let scale = f32::min(available.x / rom_size.x, available.y / rom_size.y);
839 let size = rom_size * scale;
840 let offset = (available - size) / 2.0;
841 (size, offset)
842 }
843 };
844 egui::CentralPanel::default()
846 .frame(egui::Frame::default().fill(egui::Color32::TRANSPARENT))
847 .show(ctx, |ui| {
848 self.draw(ui);
849 });
850 if self.stopped {
851 return;
852 }
853 if let Some(flag) = &self.should_exit {
855 if flag.load(std::sync::atomic::Ordering::Relaxed) {
856 self.shutdown();
857 ctx.send_viewport_cmd(egui::ViewportCommand::Close);
858 return;
859 }
860 }
861 let input = ctx.input(|i| i.clone());
862 let ctrl_alt_drag =
864 input.modifiers.ctrl && input.modifiers.alt && input.pointer.primary_down();
865 let mouse_drag_mode = self.config.mouse_mode;
866 let mouse_drag_now = match mouse_drag_mode {
867 MouseDragMode::DoubleDrag => {
868 input.pointer.button_down(egui::PointerButton::Primary)
869 && input.pointer.button_down(egui::PointerButton::Secondary)
870 }
871 MouseDragMode::None => false,
873 };
874 if self.config.mouse_resize == MouseResizeMode::MiddleDrag {
876 if input.pointer.button_down(egui::PointerButton::Middle) {
877 log::info!("Middle mouse button down for resize");
878 if let Some(pos) = input.pointer.interact_pos() {
879 log::info!("interact_pos: {:?}", pos);
880 if self.drag_start_pos.is_none() {
881 log::info!("Starting middle drag at {:?}", pos);
882 self.drag_start_pos = Some(pos);
883 self.resize_start_size = Some(self.size);
884 }
885 if let (Some(start_pos), Some((start_w, start_h))) =
886 (self.drag_start_pos, self.resize_start_size)
887 {
888 let delta = pos - start_pos;
889 let new_w = (start_w as f32 + delta.x).max(100.0) as u16;
890 let new_h = (start_h as f32 + delta.y).max(100.0) as u16;
891 log::info!(
892 "Resizing: start=({},{}) delta=({:.2},{:.2}) new=({}, {})",
893 start_w,
894 start_h,
895 delta.x,
896 delta.y,
897 new_w,
898 new_h
899 );
900 ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(egui::Vec2::new(
901 new_w as f32,
902 new_h as f32,
903 )));
904 self.last_resize_size = Some((new_w, new_h));
905 }
906 } else {
907 log::info!("interact_pos is None");
908 }
909 } else if self.resize_start_size.is_some() {
910 log::info!("Middle mouse button released");
911 if let Some((new_w, new_h)) = self.last_resize_size {
912 log::info!("Finalizing resize to ({}, {})", new_w, new_h);
913 self.size = (new_w, new_h);
914 if let Some(f) = self.resized.as_mut() {
915 f(new_w, new_h);
916 }
917 ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(egui::Vec2::new(
918 new_w as f32,
919 new_h as f32,
920 )));
921 }
922 self.drag_start_pos = None;
923 self.resize_start_size = None;
924 self.last_resize_size = None;
925 }
926 } else {
927 self.drag_start_pos = None;
928 }
929 let dragging_now = ctrl_alt_drag || mouse_drag_now;
930 if dragging_now && !self.drag_started {
931 ctx.send_viewport_cmd(egui::ViewportCommand::StartDrag);
932 self.drag_started = true;
933 }
934 if !dragging_now {
935 self.drag_started = false;
936 }
937 while let Ok(e) = self.event_rx.try_recv() {
938 match e {
939 Event::LoadRom(data) => {
940 if let Err(e) = self.load_rom(&data) {
941 error!("could not load rom: {e:?}");
942 }
943 self.update_texture(ctx);
944 }
945 Event::SetMuted(m) => {
946 self.dev.audio_set_muted(m);
947 }
948 Event::Console(b) => {
949 self.dev.console(&mut self.vm, b);
950 }
951 }
952 }
953
954 ctx.request_repaint();
956 let mut next_efx_ndx = self.config.effects.efx_ndx;
957 let efx_name = self
958 .config
959 .effects
960 .efx
961 .as_ref()
962 .map(|s| s.to_ascii_lowercase());
963 let is_random = efx_name.as_deref() == Some("random");
964 let is_sequential = efx_name.as_deref() == Some("sequential");
965 while input.time >= self.next_frame {
966 self.next_frame += 0.0166667;
967 self.dev.redraw(&mut self.vm);
968 if is_random {
970 if self.config.effects.last_effect_switch == 0.0
971 || input.time - self.config.effects.last_effect_switch
972 >= self.config.effects.efxt as f64
973 {
974 next_efx_ndx = random_helpers::random_range(0, crate::effects::EFFECT_COUNT);
976 self.config.effects.last_effect_switch = input.time;
977 }
978 } else if is_sequential
979 && (self.config.effects.last_effect_switch == 0.0
980 || input.time - self.config.effects.last_effect_switch
981 >= self.config.effects.efxt as f64)
982 {
983 next_efx_ndx = (next_efx_ndx + 1) % crate::effects::EFFECT_COUNT;
984 self.config.effects.last_effect_switch = input.time;
985 }
986 self.update_texture(ctx);
987 }
988
989 if is_random || is_sequential {
990 self.config.effects.efx_ndx = next_efx_ndx;
991 }
992
993 if input.raw.dropped_files.len() == 1 {
994 let target = &input.raw.dropped_files[0];
995 let r = if let Some(path) = &target.path {
996 let data = std::fs::read(path).expect("failed to read file");
997 info!("loading {} bytes from {path:?}", data.len());
998 self.load_rom(&data)
999 } else if let Some(data) = &target.bytes {
1000 self.load_rom(data)
1001 } else {
1002 Ok(())
1003 };
1004 if let Err(e) = r {
1005 error!("could not load ROM: {e:?}");
1006 }
1007 }
1008
1009 let shift_held = input.modifiers.shift;
1010 for e in input.events.iter() {
1011 if let egui::Event::Key { key, pressed, .. } = e {
1013 println!(
1014 "Key event received: {:?}, pressed: {}, ctrl: {}",
1015 key, pressed, input.modifiers.ctrl
1016 );
1017 if *key == egui::Key::C && input.modifiers.ctrl {
1018 println!("Ctrl+C handler triggered");
1019 self.shutdown();
1020 ctx.send_viewport_cmd(egui::ViewportCommand::Close);
1021 return;
1022 }
1023 }
1024 match e {
1026 egui::Event::Text(s) => {
1027 const RAW_CHARS: [u8; 16] = [
1028 b'"', b'\'', b'{', b'}', b'_', b')', b'(', b'*', b'&', b'^', b'%', b'$',
1029 b'#', b'@', b'!', b'~',
1030 ];
1031 for c in s.bytes() {
1032 if RAW_CHARS.contains(&c) {
1033 self.dev.char(&mut self.vm, c);
1034 }
1035 }
1036 }
1037 egui::Event::Key {
1038 key,
1039 pressed,
1040 repeat,
1041 ..
1042 } => {
1043 println!(
1045 "Key event: {:?}, pressed: {}, ctrl: {}",
1046 key, pressed, input.modifiers.ctrl
1047 );
1048 if *pressed && *key == egui::Key::F2 {
1049 self.dev.system.debug(&mut self.vm);
1050 #[cfg(target_os = "windows")]
1051 unsafe {
1052 winapi::um::winuser::MessageBeep(winapi::um::winuser::MB_OK);
1053 }
1054 }
1055 if let Some(k) = decode_key(*key, shift_held) {
1056 if *pressed {
1057 self.dev.pressed(&mut self.vm, k, *repeat);
1058 } else {
1059 self.dev.released(&mut self.vm, k);
1060 }
1061 }
1062 }
1063 egui::Event::MouseWheel { delta, .. } => {
1064 self.scroll.0 += delta.x;
1065 self.scroll.1 -= delta.y;
1066 }
1067 _ => (),
1068 }
1069 }
1070 for (b, k) in [
1071 (input.modifiers.ctrl, varvara::Key::Ctrl),
1072 (input.modifiers.alt, varvara::Key::Alt),
1073 (input.modifiers.shift, varvara::Key::Shift),
1074 ] {
1075 if b {
1076 self.dev.pressed(&mut self.vm, k, false)
1077 } else {
1078 self.dev.released(&mut self.vm, k)
1079 }
1080 }
1081
1082 let ptr = &input.pointer;
1083 if let Some(p) = ptr.latest_pos() {
1084 self.cursor_pos = Some((p.x / self.scale, p.y / self.scale));
1085 }
1086
1087 let buttons = [
1088 egui::PointerButton::Primary,
1089 egui::PointerButton::Middle,
1090 egui::PointerButton::Secondary,
1091 ]
1092 .into_iter()
1093 .enumerate()
1094 .map(|(i, b)| (ptr.button_down(b) as u8) << i)
1095 .fold(0, |a, b| a | b);
1096 let m = varvara::MouseState {
1097 pos: self.cursor_pos.unwrap_or((0.0, 0.0)),
1098 scroll: std::mem::take(&mut self.scroll),
1099 buttons,
1100 };
1101 self.dev.mouse(&mut self.vm, m);
1102 let m = varvara::TrackerState {
1103 pos: self.cursor_pos.unwrap_or((0.0, 0.0)),
1104 scroll: std::mem::take(&mut self.scroll),
1105 buttons,
1106 };
1107 self.dev.tracker(&mut self.vm, m);
1108
1109 self.dev.audio(&mut self.vm);
1111
1112 let out = self.dev.output(&self.vm);
1113
1114 if out.hide_mouse {
1116 ctx.set_cursor_icon(egui::CursorIcon::None);
1117 }
1118 let mut image = egui::ColorImage::new(
1132 [out.size.0 as usize, out.size.1 as usize],
1133 vec![egui::Color32::BLACK; (out.size.0 as usize) * (out.size.1 as usize)],
1134 );
1135 let mut x = 0;
1136 let mut y = 0;
1137 let w = out.size.0 as usize;
1138 let _t = 0.0;
1139 let num_effects = 16; for (i, o) in out.frame.chunks(4).zip(image.pixels.iter_mut()) {
1141 let r = i[2];
1142 let g = i[1];
1143 let b = i[0];
1144 let a;
1145 if let Some((tr, tg, tb)) = self.transparent_rgb {
1146 if r == tr && g == tg && b == tb {
1147 a = 0;
1148 } else {
1149 a = 255;
1150 }
1151 } else {
1152 a = 255;
1153 }
1154 if a != 0 {
1155 let t = self.config.effects.efxt;
1156 let fx = x as f32;
1157 let fy = y as f32;
1158 let cx = (w as f32) / 2.0;
1159 let cy = (out.size.1 as f32) / 2.0;
1160 let dist = ((fx - cx).powi(2) + (fy - cy).powi(2)).sqrt();
1161 let diag = (fx + fy) * 0.5;
1162 let eff =
1163 self.config.effects.effect_order[self.config.effects.effect_mode % num_effects];
1164 let _effect_name = match eff {
1166 0 => "Plasma (horizontal)",
1167 1 => "Plasma (vertical)",
1168 2 => "Plasma (circle)",
1169 3 => "Plasma (diagonal)",
1170 4 => "Rainbow (horizontal)",
1171 5 => "Rainbow (vertical)",
1172 6 => "Rainbow (circle)",
1173 7 => "Rainbow (diagonal)",
1174 8 => "Waves (horizontal)",
1175 9 => "Waves (vertical)",
1176 10 => "Waves (circle)",
1177 11 => "Waves (diagonal)",
1178 12 => "Noise (horizontal)",
1179 13 => "Noise (vertical)",
1180 14 => "Noise (circle)",
1181 15 => "Noise (diagonal)",
1182 _ => "Unknown",
1183 };
1184 match eff {
1185 0 => {
1186 let v = ((fx * 0.08 + t * 0.12).sin() + (fy * 0.08 + t * 0.15).cos()) * 0.5;
1188 let r = ((v * 127.0 + 128.0) as u8).saturating_add((t as u8) % 128);
1189 let g = ((v * 127.0 + 128.0) as u8).saturating_add((x as u8) % 128);
1190 let b = ((v * 127.0 + 128.0) as u8).saturating_add((y as u8) % 128);
1191 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1192 continue;
1193 }
1194 1 => {
1195 let v = ((fy * 0.08 + t * 0.12).sin() + (fx * 0.08 + t * 0.15).cos()) * 0.5;
1197 let r = ((v * 127.0 + 128.0) as u8).saturating_add((t as u8) % 128);
1198 let g = ((v * 127.0 + 128.0) as u8).saturating_add((y as u8) % 128);
1199 let b = ((v * 127.0 + 128.0) as u8).saturating_add((x as u8) % 128);
1200 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1201 continue;
1202 }
1203 2 => {
1204 let theta = (fy - cy).atan2(fx - cx);
1206 let v =
1207 ((dist * 0.15 + t * 0.12).sin() + (theta * 6.0 + t * 0.2).cos()) * 0.5;
1208 let r = ((v * 127.0 + 128.0) as u8)
1209 .saturating_add((theta.sin() * 127.0 + 128.0) as u8);
1210 let g = ((v * 127.0 + 128.0) as u8)
1211 .saturating_add((theta.cos() * 127.0 + 128.0) as u8);
1212 let b = ((v * 127.0 + 128.0) as u8)
1213 .saturating_add((dist.sin() * 127.0 + 128.0) as u8);
1214 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1215 continue;
1216 }
1217 3 => {
1218 let v = ((diag * 0.08 + t * 0.12).sin() + (t * 0.15).cos()) * 0.5;
1220 let r = ((v * 127.0 + 128.0) as u8).saturating_add((t as u8) % 128);
1221 let g = ((v * 127.0 + 128.0) as u8).saturating_add((x as u8) % 128);
1222 let b = ((v * 127.0 + 128.0) as u8).saturating_add((y as u8) % 128);
1223 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1224 continue;
1225 }
1226 4 => {
1227 let r = ((fx * 0.12 + t * 0.2).sin() * 127.0 + 128.0) as u8;
1229 let g = ((fy * 0.12 + t * 0.3).cos() * 127.0 + 128.0) as u8;
1230 let b = (((fx + fy) * 0.07 + t * 0.4).sin() * 127.0 + 128.0) as u8;
1231 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1232 continue;
1233 }
1234 5 => {
1235 let r = ((fy * 0.12 + t * 0.2).sin() * 127.0 + 128.0) as u8;
1237 let g = ((fx * 0.12 + t * 0.3).cos() * 127.0 + 128.0) as u8;
1238 let b = (((fx + fy) * 0.07 + t * 0.4).sin() * 127.0 + 128.0) as u8;
1239 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1240 continue;
1241 }
1242 6 => {
1243 let theta = (fy - cy).atan2(fx - cx);
1245 let r = ((dist * 0.18 + t * 0.2).sin() * 127.0 + 128.0) as u8;
1246 let g = ((theta * 4.0 + t * 0.3).cos() * 127.0 + 128.0) as u8;
1247 let b = ((dist * 0.09 + theta + t * 0.4).sin() * 127.0 + 128.0) as u8;
1248 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1249 continue;
1250 }
1251 7 => {
1252 let r = ((diag * 0.12 + t * 0.2).sin() * 127.0 + 128.0) as u8;
1254 let g = ((diag * 0.12 + t * 0.3).cos() * 127.0 + 128.0) as u8;
1255 let b = ((diag * 0.07 + t * 0.4).sin() * 127.0 + 128.0) as u8;
1256 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1257 continue;
1258 }
1259 8 => {
1260 let v = ((fx * 0.2 + t * 0.25).sin() * 127.0 + 128.0) as u8;
1262 *o = egui::Color32::from_rgba_unmultiplied(v, 255 - v, v / 2, a);
1263 continue;
1264 }
1265 9 => {
1266 let v = ((fy * 0.2 + t * 0.25).sin() * 127.0 + 128.0) as u8;
1268 *o = egui::Color32::from_rgba_unmultiplied(v, 255 - v, v / 2, a);
1269 continue;
1270 }
1271 10 => {
1272 let v = ((dist * 0.25 + t * 0.25).sin() * 127.0 + 128.0) as u8;
1274 let w = ((dist * 0.12 + t * 0.1).cos() * 127.0 + 128.0) as u8;
1275 *o = egui::Color32::from_rgba_unmultiplied(v, 255 - w, w / 2, a);
1276 continue;
1277 }
1278 11 => {
1279 let v = ((diag * 0.2 + t * 0.25).sin() * 127.0 + 128.0) as u8;
1281 *o = egui::Color32::from_rgba_unmultiplied(v, 255 - v, v / 2, a);
1282 continue;
1283 }
1284 12 => {
1285 let seed = (x as u32).wrapping_mul(73856093)
1287 ^ (self.config.effects.effect_mode as u32).wrapping_mul(83492791);
1288 let val = ((seed >> 3) ^ (seed << 7)).wrapping_add(seed) as u8;
1289 *o = egui::Color32::from_rgba_unmultiplied(
1290 val,
1291 val.wrapping_mul(3),
1292 val.wrapping_mul(7),
1293 a,
1294 );
1295 continue;
1296 }
1297 13 => {
1298 let seed = (y as u32).wrapping_mul(19349663)
1300 ^ (self.config.effects.efx_ndx as u32).wrapping_mul(83492791);
1301 let val = ((seed >> 3) ^ (seed << 7)).wrapping_add(seed) as u8;
1302 *o = egui::Color32::from_rgba_unmultiplied(
1303 val,
1304 val.wrapping_mul(3),
1305 val.wrapping_mul(7),
1306 a,
1307 );
1308 continue;
1309 }
1310 14 => {
1311 let theta = (fy - cy).atan2(fx - cx);
1313 let seed = (dist as u32).wrapping_mul(1234567)
1314 ^ (theta.to_bits().wrapping_mul(314159))
1315 ^ (self.config.effects.effect_mode as u32).wrapping_mul(83492791);
1316 let val = ((seed >> 3) ^ (seed << 7)).wrapping_add(seed) as u8;
1317 *o = egui::Color32::from_rgba_unmultiplied(
1318 val,
1319 val.rotate_left(2),
1320 val.rotate_left(4),
1321 a,
1322 );
1323 continue;
1324 }
1325 15 => {
1326 let seed = (diag as u32).wrapping_mul(9876543)
1328 ^ (self.config.effects.effect_mode as u32).wrapping_mul(83492791);
1329 let val = ((seed >> 3) ^ (seed << 7)).wrapping_add(seed) as u8;
1330 *o = egui::Color32::from_rgba_unmultiplied(
1331 val,
1332 val.wrapping_mul(3),
1333 val.wrapping_mul(7),
1334 a,
1335 );
1336 continue;
1337 }
1338 _ => {}
1339 }
1340 }
1341 *o = egui::Color32::from_rgba_unmultiplied(r, g, b, a);
1342 x += 1;
1343 if x >= w {
1344 x = 0;
1345 y += 1;
1346 }
1347 }
1348
1349 out.check().expect("failed to print output?");
1351 }
1352}
1353
1354fn decode_key(k: egui::Key, shift: bool) -> Option<Key> {
1355 let c = match (k, shift) {
1356 (egui::Key::ArrowUp, _) => Key::Up,
1357 (egui::Key::ArrowDown, _) => Key::Down,
1358 (egui::Key::ArrowLeft, _) => Key::Left,
1359 (egui::Key::ArrowRight, _) => Key::Right,
1360 (egui::Key::Home, _) => Key::Home,
1361 (egui::Key::Num0, false) => Key::Char(b'0'),
1362 (egui::Key::Num0, true) => Key::Char(b')'),
1363 (egui::Key::Num1, false) => Key::Char(b'1'),
1364 (egui::Key::Num1, true) => Key::Char(b'!'),
1365 (egui::Key::Num2, false) => Key::Char(b'2'),
1366 (egui::Key::Num2, true) => Key::Char(b'@'),
1367 (egui::Key::Num3, false) => Key::Char(b'3'),
1368 (egui::Key::Num3, true) => Key::Char(b'#'),
1369 (egui::Key::Num4, false) => Key::Char(b'4'),
1370 (egui::Key::Num4, true) => Key::Char(b'$'),
1371 (egui::Key::Num5, false) => Key::Char(b'5'),
1372 (egui::Key::Num5, true) => Key::Char(b'5'),
1373 (egui::Key::Num6, false) => Key::Char(b'6'),
1374 (egui::Key::Num6, true) => Key::Char(b'^'),
1375 (egui::Key::Num7, false) => Key::Char(b'7'),
1376 (egui::Key::Num7, true) => Key::Char(b'&'),
1377 (egui::Key::Num8, false) => Key::Char(b'8'),
1378 (egui::Key::Num8, true) => Key::Char(b'*'),
1379 (egui::Key::Num9, false) => Key::Char(b'9'),
1380 (egui::Key::Num9, true) => Key::Char(b'('),
1381 (egui::Key::A, false) => Key::Char(b'a'),
1382 (egui::Key::A, true) => Key::Char(b'A'),
1383 (egui::Key::B, false) => Key::Char(b'b'),
1384 (egui::Key::B, true) => Key::Char(b'B'),
1385 (egui::Key::C, false) => Key::Char(b'c'),
1386 (egui::Key::C, true) => Key::Char(b'C'),
1387 (egui::Key::D, false) => Key::Char(b'd'),
1388 (egui::Key::D, true) => Key::Char(b'D'),
1389 (egui::Key::E, false) => Key::Char(b'e'),
1390 (egui::Key::E, true) => Key::Char(b'E'),
1391 (egui::Key::F, false) => Key::Char(b'f'),
1392 (egui::Key::F, true) => Key::Char(b'F'),
1393 (egui::Key::G, false) => Key::Char(b'g'),
1394 (egui::Key::G, true) => Key::Char(b'G'),
1395 (egui::Key::H, false) => Key::Char(b'h'),
1396 (egui::Key::H, true) => Key::Char(b'H'),
1397 (egui::Key::I, false) => Key::Char(b'i'),
1398 (egui::Key::I, true) => Key::Char(b'I'),
1399 (egui::Key::J, false) => Key::Char(b'j'),
1400 (egui::Key::J, true) => Key::Char(b'J'),
1401 (egui::Key::K, false) => Key::Char(b'k'),
1402 (egui::Key::K, true) => Key::Char(b'K'),
1403 (egui::Key::L, false) => Key::Char(b'l'),
1404 (egui::Key::L, true) => Key::Char(b'L'),
1405 (egui::Key::M, false) => Key::Char(b'm'),
1406 (egui::Key::M, true) => Key::Char(b'M'),
1407 (egui::Key::N, false) => Key::Char(b'n'),
1408 (egui::Key::N, true) => Key::Char(b'N'),
1409 (egui::Key::O, false) => Key::Char(b'o'),
1410 (egui::Key::O, true) => Key::Char(b'O'),
1411 (egui::Key::P, false) => Key::Char(b'p'),
1412 (egui::Key::P, true) => Key::Char(b'P'),
1413 (egui::Key::Q, false) => Key::Char(b'q'),
1414 (egui::Key::Q, true) => Key::Char(b'Q'),
1415 (egui::Key::R, false) => Key::Char(b'r'),
1416 (egui::Key::R, true) => Key::Char(b'R'),
1417 (egui::Key::S, false) => Key::Char(b's'),
1418 (egui::Key::S, true) => Key::Char(b'S'),
1419 (egui::Key::T, false) => Key::Char(b't'),
1420 (egui::Key::T, true) => Key::Char(b'T'),
1421 (egui::Key::U, false) => Key::Char(b'u'),
1422 (egui::Key::U, true) => Key::Char(b'U'),
1423 (egui::Key::V, false) => Key::Char(b'v'),
1424 (egui::Key::V, true) => Key::Char(b'V'),
1425 (egui::Key::W, false) => Key::Char(b'w'),
1426 (egui::Key::W, true) => Key::Char(b'W'),
1427 (egui::Key::X, false) => Key::Char(b'x'),
1428 (egui::Key::X, true) => Key::Char(b'X'),
1429 (egui::Key::Y, false) => Key::Char(b'y'),
1430 (egui::Key::Y, true) => Key::Char(b'Y'),
1431 (egui::Key::Z, false) => Key::Char(b'z'),
1432 (egui::Key::Z, true) => Key::Char(b'Z'),
1433 (egui::Key::Backtick, false) => Key::Char(b'`'),
1435 (egui::Key::Backtick, true) => Key::Char(b'~'),
1436 (egui::Key::Backslash, _) => Key::Char(b'\\'),
1437 (egui::Key::Pipe, _) => Key::Char(b'|'),
1438 (egui::Key::Comma, false) => Key::Char(b','),
1439 (egui::Key::Comma, true) => Key::Char(b'<'),
1440 (egui::Key::Equals, _) => Key::Char(b'='),
1441 (egui::Key::Plus, _) => Key::Char(b'+'),
1442 (egui::Key::OpenBracket, false) => Key::Char(b'['),
1443 (egui::Key::OpenBracket, true) => Key::Char(b'{'),
1444 (egui::Key::Minus, false) => Key::Char(b'-'),
1445 (egui::Key::Minus, true) => Key::Char(b'_'),
1446 (egui::Key::Period, false) => Key::Char(b'.'),
1447 (egui::Key::Period, true) => Key::Char(b'>'),
1448 (egui::Key::CloseBracket, false) => Key::Char(b']'),
1449 (egui::Key::CloseBracket, true) => Key::Char(b'}'),
1450 (egui::Key::Semicolon, _) => Key::Char(b';'),
1451 (egui::Key::Colon, _) => Key::Char(b':'),
1452 (egui::Key::Slash, _) => Key::Char(b'/'),
1453 (egui::Key::Questionmark, _) => Key::Char(b'?'),
1454 (egui::Key::Space, _) => Key::Char(b' '),
1455 (egui::Key::Tab, _) => Key::Char(b'\t'),
1456 (egui::Key::Enter, _) => Key::Char(b'\r'),
1457 (egui::Key::Backspace, _) => Key::Char(0x08),
1458 _ => return None,
1459 };
1460 Some(c)
1461}
1462
1463mod random_helpers {
1464 use getrandom::fill;
1465
1466 pub fn random_range(start: usize, end: usize) -> usize {
1467 let mut bytes = [0u8; 8];
1468 fill(&mut bytes).expect("getrandom failed");
1469 let val = u64::from_le_bytes(bytes);
1470 let range = end - start;
1471 start + (val as usize % range)
1472 }
1473}