Skip to main content

cardinal_gui/
stage.rs

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    // Only None and DoubleDrag are supported for window movement
16}
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,          // current effect index
24    pub last_effect_switch: f64, // time of last effect switch
25}
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
47// Color transform trait and helpers
48pub 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    // zebra_offset moved to EffectsConfig
101    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 image = egui::ColorImage::new(
133        //     [usize::from(config.size.0), usize::from(config.size.1)],
134        //     vec![egui::Color32::BLACK; (config.size.0 as usize) * (config.size.1 as usize)],
135        // );
136        // Texture will be created in update_texture when ctx is available
137        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        // Validate efxmode (blend mode)
149        // let valid_efxmode = match config.efxmode.as_deref() {
150        //     Some("blend") => Some(crate::effects::BlendMode::BlendMode),
151        //     Some("full") => Some(crate::effects::BlendMode::FullMode),
152        //     Some("random") => Some(crate::effects::BlendMode::Random),
153        //     Some("normal") => Some(crate::effects::BlendMode::FullMode),
154        //     None => None,
155        //     _ => None,
156        // };
157        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.vm.run(&mut self.dev, 0x100);
195        self.dev.redraw(&mut self.vm);
196    }
197    pub fn update_texture(&mut self, ctx: &egui::Context) {
198        // Prepare image and pixel coordinates for effect loop
199        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        // Debug print: show current effect index and name during cycling
220        // Print blend mode only when it changes
221        // Print effect cycling info only when effect index changes
222        {
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        // Use actual frame time for dynamic effects
248        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        // For optimal/optimalPerFrame, scan image for unique intensities
272        let _selected_blend = blend_mode;
273        use std::collections::HashSet;
274        // BlendMode::Optimal: cache unique_colors/intensities only once
275        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                // Only print on first frame
296                if !colors.is_empty()
297                    && !intensities.is_empty()
298                    && colors.len() + intensities.len() == colors.len() + intensities.len()
299                {
300                    // crude way to print only once
301                    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        // Only override efxmode if blend_mode is Optimal or OptimalPerFrame
332        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        // let k = varvara::Key::Right;
440        // let repeat = false;
441        // self.dev.pressed(&mut self.vm, k, repeat);
442        // self.dev.released(&mut self.vm, k);
443        // let k = varvara::Key::Char(b'A');
444        //  self.dev
445        //      .pressed(&mut self.vm, varvara::Key::Char(b'a'), false);
446        // self.dev.char(&mut self.vm, b'a');
447    }
448
449    /// Load symbols from a byte slice (for embedded .sym files)
450    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    /// Load a ROM from a file path and attempt to load a .sys symbol file if present
457    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            // Look for a .sys file with the same file name as the ROM, e.g. orca.rom -> orca.rom.sys
466            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    /// Handle mouse events only (for hover)
491    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                // Window drag logic
520                // Drag logic is handled in update() via StartDrag
521            }
522        }
523        // Also handle pointer button events
524        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    /// Handle keyboard events only (for focus)
557    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    /// Draw mesh at a specific rect (used by UxnPanel)
575    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    /// Handle egui input events and forward to VM/device
597    pub fn handle_input(&mut self, input: &egui::InputState, rect: egui::Rect) {
598        // Forward text events as char events to the VM/device (for printable chars)
599        for event in &input.events {
600            if let egui::Event::Text(s) = event {
601                for c in s.chars() {
602                    // Only send printable ASCII (or adjust as needed for your ROM)
603                    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        // Only send key events for non-printable keys (arrows, enter, etc)
619        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        // Forward mouse position and button state
692        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        // Forward pointer button events (for clicks)
715        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    /// Gracefully shutdown the stage (stop emulation and mute audio)
749    pub fn shutdown(&mut self) {
750        // mark stopped to prevent further updates
751        self.stopped = true;
752        // mute audio to avoid audio continuing after close
753        self.dev.audio_set_muted(true);
754    }
755}
756
757// Helper: Map egui::Key to Varvara::Key (basic ASCII)
758fn 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        // Debug output for rendering and scaling
819        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        // Call draw to ensure rendering and debug output
845        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        // Timeout/exit flag: close window gracefully like Ctrl+C
854        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        // Ctrl+Alt+drag OR mouse drag mode: use StartDrag for native drag
863        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            // Only None and DoubleDrag are supported for window movement
872            MouseDragMode::None => false,
873        };
874        // Mouse resize logic: if mouse_resize is RightDrag and right button is down, resize window
875        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        // Repaint at vsync rate (60 FPS)
955        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            // Only cycle effect if efx is 'random' or 'sequential' and efxt dwell time has elapsed
969            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                    // Use getrandom-based helper for random effect index
975                    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            // Handle global shortcuts before passing to emulator
1012            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            // Only pass non-global shortcut events to emulator
1025            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                    // Only handle Ctrl+C on key press, not release
1044                    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        // Handle audio callback
1110        self.dev.audio(&mut self.vm);
1111
1112        let out = self.dev.output(&self.vm);
1113
1114        // Update our GUI based on current state
1115        if out.hide_mouse {
1116            ctx.set_cursor_icon(egui::CursorIcon::None);
1117        }
1118        // Only update self.size from emulator output if not currently resizing
1119        // let resizing_now = self.config.mouse_resize == MouseResizeMode::MiddleDrag && input.pointer.button_down(egui::PointerButton::Middle);
1120        //     if self.size != out.size && !resizing_now {
1121        //         info!("resizing window to {:?}", out.size);
1122        //         self.size = out.size;
1123        //         let size = egui::Vec2::new(out.size.0 as f32, out.size.1 as f32) * self.scale;
1124        //         ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
1125        //         if let Some(f) = self.resized.as_mut() {
1126        //             f(out.size.0, out.size.1);
1127        //         }
1128        //     }
1129
1130        // TODO reduce allocation here?
1131        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; // 4 variants for each of 4 effect types
1140        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                // Print current effect for debug
1165                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                        // Plasma (horizontal)
1187                        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                        // Plasma (vertical)
1196                        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                        // Plasma (circle, enhanced)
1205                        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                        // Plasma (diagonal)
1219                        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                        // Rainbow swirl (horizontal)
1228                        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                        // Rainbow swirl (vertical)
1236                        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                        // Rainbow swirl (circle, enhanced)
1244                        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                        // Rainbow swirl (diagonal)
1253                        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                        // Waves (horizontal)
1261                        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                        // Waves (vertical)
1267                        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                        // Waves (circle, enhanced)
1273                        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                        // Waves (diagonal)
1280                        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                        // Noise (horizontal)
1286                        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                        // Noise (vertical)
1299                        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                        // Noise (circle, enhanced)
1312                        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                        // Noise (diagonal)
1327                        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        // Update stdout / stderr / exiting
1350        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        // TODO missing Key::Quote
1434        (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}