Add a fast-forward command
This commit is contained in:
		
							parent
							
								
									4a3385f04c
								
							
						
					
					
						commit
						caf3a9426e
					
				
							
								
								
									
										10
									
								
								src/app.rs
								
								
								
								
							
							
						
						
									
										10
									
								
								src/app.rs
								
								
								
								
							| 
						 | 
				
			
			@ -24,7 +24,7 @@ use crate::{
 | 
			
		|||
    persistence::Persistence,
 | 
			
		||||
    window::{
 | 
			
		||||
        AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow,
 | 
			
		||||
        GdbServerWindow, InputWindow, ObjectWindow, RegisterWindow, ShortcutsWindow, WorldWindow,
 | 
			
		||||
        GdbServerWindow, HotkeysWindow, InputWindow, ObjectWindow, RegisterWindow, WorldWindow,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -252,9 +252,9 @@ impl ApplicationHandler<UserEvent> for Application {
 | 
			
		|||
                let input = InputWindow::new(self.mappings.clone());
 | 
			
		||||
                self.open(event_loop, Box::new(input));
 | 
			
		||||
            }
 | 
			
		||||
            UserEvent::OpenShortcuts => {
 | 
			
		||||
                let shortcuts = ShortcutsWindow::new(self.shortcuts.clone());
 | 
			
		||||
                self.open(event_loop, Box::new(shortcuts));
 | 
			
		||||
            UserEvent::OpenHotkeys => {
 | 
			
		||||
                let hotkeys = HotkeysWindow::new(self.shortcuts.clone());
 | 
			
		||||
                self.open(event_loop, Box::new(hotkeys));
 | 
			
		||||
            }
 | 
			
		||||
            UserEvent::OpenPlayer2 => {
 | 
			
		||||
                let p2 = GameWindow::new(
 | 
			
		||||
| 
						 | 
				
			
			@ -515,7 +515,7 @@ pub enum UserEvent {
 | 
			
		|||
    OpenRegisters(SimId),
 | 
			
		||||
    OpenDebugger(SimId),
 | 
			
		||||
    OpenInput,
 | 
			
		||||
    OpenShortcuts,
 | 
			
		||||
    OpenHotkeys,
 | 
			
		||||
    OpenPlayer2,
 | 
			
		||||
    Quit(SimId),
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								src/audio.rs
								
								
								
								
							
							
						
						
									
										22
									
								
								src/audio.rs
								
								
								
								
							| 
						 | 
				
			
			@ -3,18 +3,20 @@ use std::time::Duration;
 | 
			
		|||
use anyhow::{Result, bail};
 | 
			
		||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
 | 
			
		||||
use itertools::Itertools;
 | 
			
		||||
use rubato::{FftFixedInOut, Resampler};
 | 
			
		||||
use rubato::{FastFixedOut, Resampler};
 | 
			
		||||
use tracing::error;
 | 
			
		||||
 | 
			
		||||
pub struct Audio {
 | 
			
		||||
    #[allow(unused)]
 | 
			
		||||
    stream: cpal::Stream,
 | 
			
		||||
    sampler: FftFixedInOut<f32>,
 | 
			
		||||
    sampler: FastFixedOut<f32>,
 | 
			
		||||
    input_buffer: Vec<Vec<f32>>,
 | 
			
		||||
    output_buffer: Vec<Vec<f32>>,
 | 
			
		||||
    sample_sink: rtrb::Producer<f32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const VB_FREQUENCY: usize = 41700;
 | 
			
		||||
 | 
			
		||||
impl Audio {
 | 
			
		||||
    pub fn init() -> Result<Self> {
 | 
			
		||||
        let host = cpal::default_host();
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +30,15 @@ impl Audio {
 | 
			
		|||
            bail!("No suitable output config available");
 | 
			
		||||
        };
 | 
			
		||||
        let mut config = config.with_max_sample_rate().config();
 | 
			
		||||
        let sampler = FftFixedInOut::new(41700, config.sample_rate.0 as usize, 834, 2)?;
 | 
			
		||||
        let resample_ratio = config.sample_rate.0 as f64 / VB_FREQUENCY as f64;
 | 
			
		||||
        let chunk_size = (834.0 * resample_ratio) as usize;
 | 
			
		||||
        let sampler = FastFixedOut::new(
 | 
			
		||||
            resample_ratio,
 | 
			
		||||
            64.0,
 | 
			
		||||
            rubato::PolynomialDegree::Cubic,
 | 
			
		||||
            chunk_size,
 | 
			
		||||
            2,
 | 
			
		||||
        )?;
 | 
			
		||||
        config.buffer_size = cpal::BufferSize::Fixed(sampler.output_frames_max() as u32);
 | 
			
		||||
 | 
			
		||||
        let input_buffer = sampler.input_buffer_allocate(true);
 | 
			
		||||
| 
						 | 
				
			
			@ -101,4 +111,10 @@ impl Audio {
 | 
			
		|||
            std::thread::sleep(Duration::from_micros(500));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_speed(&mut self, speed: f64) -> Result<()> {
 | 
			
		||||
        self.sampler
 | 
			
		||||
            .set_resample_ratio_relative(1.0 / speed, false)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -311,6 +311,10 @@ impl Emulator {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_speed(&mut self, speed: f64) -> Result<()> {
 | 
			
		||||
        self.audio.set_speed(speed)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn save_sram(&mut self, sim_id: SimId) -> Result<()> {
 | 
			
		||||
        let sim = self.sims.get_mut(sim_id.to_index());
 | 
			
		||||
        let cart = self.carts[sim_id.to_index()].as_mut();
 | 
			
		||||
| 
						 | 
				
			
			@ -567,6 +571,11 @@ impl Emulator {
 | 
			
		|||
            EmulatorCommand::FrameAdvance => {
 | 
			
		||||
                self.frame_advance();
 | 
			
		||||
            }
 | 
			
		||||
            EmulatorCommand::SetSpeed(speed) => {
 | 
			
		||||
                if let Err(error) = self.set_speed(speed) {
 | 
			
		||||
                    self.report_error(SimId::Player1, format!("Error setting speed: {error}"));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            EmulatorCommand::StartDebugging(sim_id, debugger) => {
 | 
			
		||||
                self.start_debugging(sim_id, debugger);
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -694,6 +703,7 @@ pub enum EmulatorCommand {
 | 
			
		|||
    Pause,
 | 
			
		||||
    Resume,
 | 
			
		||||
    FrameAdvance,
 | 
			
		||||
    SetSpeed(f64),
 | 
			
		||||
    StartDebugging(SimId, DebugSender),
 | 
			
		||||
    StopDebugging(SimId),
 | 
			
		||||
    DebugInterrupt(SimId),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										163
									
								
								src/input.rs
								
								
								
								
							
							
						
						
									
										163
									
								
								src/input.rs
								
								
								
								
							| 
						 | 
				
			
			@ -1,13 +1,13 @@
 | 
			
		|||
use std::{
 | 
			
		||||
    cmp::Ordering,
 | 
			
		||||
    collections::{HashMap, HashSet, hash_map::Entry},
 | 
			
		||||
    collections::{HashMap, hash_map::Entry},
 | 
			
		||||
    fmt::Display,
 | 
			
		||||
    str::FromStr,
 | 
			
		||||
    sync::{Arc, Mutex, RwLock},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use anyhow::anyhow;
 | 
			
		||||
use egui::{Key, KeyboardShortcut, Modifiers};
 | 
			
		||||
use egui::{Event, Key, KeyboardShortcut, Modifiers};
 | 
			
		||||
use gilrs::{Axis, Button, Gamepad, GamepadId, ev::Code};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use winit::keyboard::{KeyCode, PhysicalKey};
 | 
			
		||||
| 
						 | 
				
			
			@ -468,19 +468,21 @@ pub enum Command {
 | 
			
		|||
    OpenRom,
 | 
			
		||||
    Quit,
 | 
			
		||||
    FrameAdvance,
 | 
			
		||||
    FastForward(u32),
 | 
			
		||||
    Reset,
 | 
			
		||||
    PauseResume,
 | 
			
		||||
    // if you update this, update Command::all and add a default
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Command {
 | 
			
		||||
    pub fn all() -> [Self; 5] {
 | 
			
		||||
    pub fn all() -> [Self; 6] {
 | 
			
		||||
        [
 | 
			
		||||
            Self::OpenRom,
 | 
			
		||||
            Self::Quit,
 | 
			
		||||
            Self::PauseResume,
 | 
			
		||||
            Self::Reset,
 | 
			
		||||
            Self::FrameAdvance,
 | 
			
		||||
            Self::FastForward(0),
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -491,6 +493,7 @@ impl Command {
 | 
			
		|||
            Self::PauseResume => "Pause/Resume",
 | 
			
		||||
            Self::Reset => "Reset",
 | 
			
		||||
            Self::FrameAdvance => "Frame Advance",
 | 
			
		||||
            Self::FastForward(_) => "Fast Forward",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -532,6 +535,10 @@ impl Default for Shortcuts {
 | 
			
		|||
            Command::FrameAdvance,
 | 
			
		||||
            KeyboardShortcut::new(Modifiers::NONE, Key::F6),
 | 
			
		||||
        );
 | 
			
		||||
        shortcuts.set(
 | 
			
		||||
            Command::FastForward(0),
 | 
			
		||||
            KeyboardShortcut::new(Modifiers::NONE, Key::Space),
 | 
			
		||||
        );
 | 
			
		||||
        shortcuts
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -557,13 +564,11 @@ impl Shortcuts {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn save(&self) -> PersistedShortcuts {
 | 
			
		||||
        let mut shortcuts = PersistedShortcuts { shortcuts: vec![] };
 | 
			
		||||
    fn save(&self, saved: &mut PersistedSettings) {
 | 
			
		||||
        for command in Command::all() {
 | 
			
		||||
            let shortcut = self.by_command.get(&command).copied();
 | 
			
		||||
            shortcuts.shortcuts.push((command, shortcut));
 | 
			
		||||
            saved.shortcuts.push((command, shortcut));
 | 
			
		||||
        }
 | 
			
		||||
        shortcuts
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -589,52 +594,123 @@ fn specificity(modifiers: egui::Modifiers) -> usize {
 | 
			
		|||
    mods
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
struct PersistedShortcuts {
 | 
			
		||||
#[derive(Serialize, Deserialize, Default)]
 | 
			
		||||
struct PersistedSettings {
 | 
			
		||||
    shortcuts: Vec<(Command, Option<KeyboardShortcut>)>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    ff_settings: FastForwardSettings,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Clone)]
 | 
			
		||||
struct ShortcutState {
 | 
			
		||||
    ff_toggled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
struct Settings {
 | 
			
		||||
    shortcuts: Shortcuts,
 | 
			
		||||
    ff_settings: FastForwardSettings,
 | 
			
		||||
    state: ShortcutState,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Settings {
 | 
			
		||||
    fn save(&self) -> PersistedSettings {
 | 
			
		||||
        let mut saved = PersistedSettings {
 | 
			
		||||
            shortcuts: vec![],
 | 
			
		||||
            ff_settings: self.ff_settings.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        self.shortcuts.save(&mut saved);
 | 
			
		||||
        saved
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct ShortcutProvider {
 | 
			
		||||
    persistence: Persistence,
 | 
			
		||||
    shortcuts: Arc<Mutex<Shortcuts>>,
 | 
			
		||||
    settings: Arc<Mutex<Settings>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ShortcutProvider {
 | 
			
		||||
    pub fn new(persistence: Persistence) -> Self {
 | 
			
		||||
        let mut shortcuts = Shortcuts::default();
 | 
			
		||||
        if let Ok(saved) = persistence.load_config::<PersistedShortcuts>("shortcuts") {
 | 
			
		||||
        let mut settings = Settings::default();
 | 
			
		||||
        if let Ok(saved) = persistence.load_config::<PersistedSettings>("shortcuts") {
 | 
			
		||||
            for (command, shortcut) in saved.shortcuts {
 | 
			
		||||
                if let Some(shortcut) = shortcut {
 | 
			
		||||
                    shortcuts.set(command, shortcut);
 | 
			
		||||
                    settings.shortcuts.set(command, shortcut);
 | 
			
		||||
                } else {
 | 
			
		||||
                    shortcuts.unset(command);
 | 
			
		||||
                    settings.shortcuts.unset(command);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            settings.ff_settings = saved.ff_settings;
 | 
			
		||||
        };
 | 
			
		||||
        Self {
 | 
			
		||||
            persistence,
 | 
			
		||||
            shortcuts: Arc::new(Mutex::new(shortcuts)),
 | 
			
		||||
            settings: Arc::new(Mutex::new(settings)),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn shortcut_for(&self, command: Command) -> Option<KeyboardShortcut> {
 | 
			
		||||
        let lock = self.shortcuts.lock().unwrap();
 | 
			
		||||
        lock.by_command.get(&command).copied()
 | 
			
		||||
        let lock = self.settings.lock().unwrap();
 | 
			
		||||
        lock.shortcuts.by_command.get(&command).copied()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn consume_all(&self, input: &mut egui::InputState) -> HashSet<Command> {
 | 
			
		||||
        let lock = self.shortcuts.lock().unwrap();
 | 
			
		||||
        lock.all
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter_map(|(command, shortcut)| input.consume_shortcut(shortcut).then_some(*command))
 | 
			
		||||
            .collect()
 | 
			
		||||
    pub fn ff_settings(&self) -> FastForwardSettings {
 | 
			
		||||
        let lock = self.settings.lock().unwrap();
 | 
			
		||||
        lock.ff_settings.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn consume_all(&self, input: &mut egui::InputState) -> Vec<Command> {
 | 
			
		||||
        let mut lock = self.settings.lock().unwrap();
 | 
			
		||||
        let mut state = lock.state.clone();
 | 
			
		||||
        let mut consumed = vec![];
 | 
			
		||||
        for (command, shortcut) in &lock.shortcuts.all {
 | 
			
		||||
            input.events.retain(|event| {
 | 
			
		||||
                let Event::Key {
 | 
			
		||||
                    key,
 | 
			
		||||
                    pressed,
 | 
			
		||||
                    repeat,
 | 
			
		||||
                    modifiers,
 | 
			
		||||
                    ..
 | 
			
		||||
                } = event
 | 
			
		||||
                else {
 | 
			
		||||
                    return true;
 | 
			
		||||
                };
 | 
			
		||||
                if shortcut.logical_key != *key || !shortcut.modifiers.contains(*modifiers) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                if matches!(command, Command::FastForward(_)) {
 | 
			
		||||
                    if *repeat {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    let sped_up = if lock.ff_settings.toggle {
 | 
			
		||||
                        if !*pressed {
 | 
			
		||||
                            return true;
 | 
			
		||||
                        }
 | 
			
		||||
                        state.ff_toggled = !state.ff_toggled;
 | 
			
		||||
                        state.ff_toggled
 | 
			
		||||
                    } else {
 | 
			
		||||
                        *pressed
 | 
			
		||||
                    };
 | 
			
		||||
                    let speed = if sped_up { lock.ff_settings.speed } else { 1 };
 | 
			
		||||
                    consumed.push(Command::FastForward(speed));
 | 
			
		||||
                    false
 | 
			
		||||
                } else {
 | 
			
		||||
                    if !*pressed {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    consumed.push(*command);
 | 
			
		||||
                    false
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        lock.state = state;
 | 
			
		||||
        consumed
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set(&self, command: Command, shortcut: KeyboardShortcut) {
 | 
			
		||||
        let updated = {
 | 
			
		||||
            let mut lock = self.shortcuts.lock().unwrap();
 | 
			
		||||
            lock.set(command, shortcut);
 | 
			
		||||
            let mut lock = self.settings.lock().unwrap();
 | 
			
		||||
            lock.shortcuts.set(command, shortcut);
 | 
			
		||||
            lock.save()
 | 
			
		||||
        };
 | 
			
		||||
        let _ = self.persistence.save_config("shortcuts", &updated);
 | 
			
		||||
| 
						 | 
				
			
			@ -642,8 +718,20 @@ impl ShortcutProvider {
 | 
			
		|||
 | 
			
		||||
    pub fn unset(&self, command: Command) {
 | 
			
		||||
        let updated = {
 | 
			
		||||
            let mut lock = self.shortcuts.lock().unwrap();
 | 
			
		||||
            lock.unset(command);
 | 
			
		||||
            let mut lock = self.settings.lock().unwrap();
 | 
			
		||||
            lock.shortcuts.unset(command);
 | 
			
		||||
            lock.save()
 | 
			
		||||
        };
 | 
			
		||||
        let _ = self.persistence.save_config("shortcuts", &updated);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn update_ff_settings(&self, ff_settings: FastForwardSettings) {
 | 
			
		||||
        let updated = {
 | 
			
		||||
            let mut lock = self.settings.lock().unwrap();
 | 
			
		||||
            lock.ff_settings = ff_settings;
 | 
			
		||||
            if !lock.ff_settings.toggle {
 | 
			
		||||
                lock.state.ff_toggled = false;
 | 
			
		||||
            }
 | 
			
		||||
            lock.save()
 | 
			
		||||
        };
 | 
			
		||||
        let _ = self.persistence.save_config("shortcuts", &updated);
 | 
			
		||||
| 
						 | 
				
			
			@ -651,10 +739,25 @@ impl ShortcutProvider {
 | 
			
		|||
 | 
			
		||||
    pub fn reset(&self) {
 | 
			
		||||
        let updated = {
 | 
			
		||||
            let mut lock = self.shortcuts.lock().unwrap();
 | 
			
		||||
            *lock = Shortcuts::default();
 | 
			
		||||
            let mut lock = self.settings.lock().unwrap();
 | 
			
		||||
            *lock = Settings::default();
 | 
			
		||||
            lock.save()
 | 
			
		||||
        };
 | 
			
		||||
        let _ = self.persistence.save_config("shortcuts", &updated);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone)]
 | 
			
		||||
pub struct FastForwardSettings {
 | 
			
		||||
    pub toggle: bool,
 | 
			
		||||
    pub speed: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for FastForwardSettings {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            toggle: false,
 | 
			
		||||
            speed: 10,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,8 @@ pub use about::AboutWindow;
 | 
			
		|||
use egui::{Context, ViewportBuilder, ViewportId};
 | 
			
		||||
pub use game::GameWindow;
 | 
			
		||||
pub use gdb::GdbServerWindow;
 | 
			
		||||
pub use hotkeys::HotkeysWindow;
 | 
			
		||||
pub use input::InputWindow;
 | 
			
		||||
pub use shortcuts::ShortcutsWindow;
 | 
			
		||||
pub use vip::{
 | 
			
		||||
    BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, RegisterWindow, WorldWindow,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -15,8 +15,8 @@ mod about;
 | 
			
		|||
mod game;
 | 
			
		||||
mod game_screen;
 | 
			
		||||
mod gdb;
 | 
			
		||||
mod hotkeys;
 | 
			
		||||
mod input;
 | 
			
		||||
mod shortcuts;
 | 
			
		||||
mod utils;
 | 
			
		||||
mod vip;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -109,6 +109,10 @@ impl GameWindow {
 | 
			
		|||
                        self.client.send_command(EmulatorCommand::FrameAdvance);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Command::FastForward(speed) => {
 | 
			
		||||
                    self.client
 | 
			
		||||
                        .send_command(EmulatorCommand::SetSpeed(speed as f64));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -358,8 +362,8 @@ impl GameWindow {
 | 
			
		|||
                ui.close_menu();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        if ui.button("Key Shortcuts").clicked() {
 | 
			
		||||
            self.proxy.send_event(UserEvent::OpenShortcuts).unwrap();
 | 
			
		||||
        if ui.button("Hotkeys").clicked() {
 | 
			
		||||
            self.proxy.send_event(UserEvent::OpenHotkeys).unwrap();
 | 
			
		||||
            ui.close_menu();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,150 @@
 | 
			
		|||
use egui::{
 | 
			
		||||
    Button, CentralPanel, Context, Event, KeyboardShortcut, Label, Layout, Slider, Ui,
 | 
			
		||||
    ViewportBuilder, ViewportId,
 | 
			
		||||
};
 | 
			
		||||
use egui_extras::{Column, TableBuilder};
 | 
			
		||||
 | 
			
		||||
use crate::input::{Command, ShortcutProvider};
 | 
			
		||||
 | 
			
		||||
use super::{AppWindow, utils::UiExt};
 | 
			
		||||
 | 
			
		||||
pub struct HotkeysWindow {
 | 
			
		||||
    shortcuts: ShortcutProvider,
 | 
			
		||||
    now_binding: Option<Command>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HotkeysWindow {
 | 
			
		||||
    pub fn new(shortcuts: ShortcutProvider) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            shortcuts,
 | 
			
		||||
            now_binding: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show_shortcuts(&mut self, ui: &mut Ui) {
 | 
			
		||||
        let row_height = ui.spacing().interact_size.y;
 | 
			
		||||
        ui.section("Shortcuts", |ui| {
 | 
			
		||||
            let width = ui.available_width() - 16.0;
 | 
			
		||||
            TableBuilder::new(ui)
 | 
			
		||||
                .column(Column::exact(width * 0.3))
 | 
			
		||||
                .column(Column::exact(width * 0.5))
 | 
			
		||||
                .column(Column::exact(width * 0.2))
 | 
			
		||||
                .cell_layout(Layout::left_to_right(egui::Align::Center))
 | 
			
		||||
                .body(|mut body| {
 | 
			
		||||
                    for command in Command::all() {
 | 
			
		||||
                        body.row(row_height, |mut row| {
 | 
			
		||||
                            row.col(|ui| {
 | 
			
		||||
                                ui.add_sized(ui.available_size(), Label::new(command.name()));
 | 
			
		||||
                            });
 | 
			
		||||
                            row.col(|ui| {
 | 
			
		||||
                                let button = if self.now_binding == Some(command) {
 | 
			
		||||
                                    Button::new("Binding...")
 | 
			
		||||
                                } else if let Some(shortcut) = self.shortcuts.shortcut_for(command)
 | 
			
		||||
                                {
 | 
			
		||||
                                    Button::new(ui.ctx().format_shortcut(&shortcut))
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    Button::new("")
 | 
			
		||||
                                };
 | 
			
		||||
                                if ui.add_sized(ui.available_size(), button).clicked() {
 | 
			
		||||
                                    self.now_binding = Some(command);
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
                            row.col(|ui| {
 | 
			
		||||
                                if ui
 | 
			
		||||
                                    .add_sized(ui.available_size(), Button::new("Clear"))
 | 
			
		||||
                                    .clicked()
 | 
			
		||||
                                {
 | 
			
		||||
                                    self.shortcuts.unset(command);
 | 
			
		||||
                                    self.now_binding = None;
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
        });
 | 
			
		||||
        if let Some(command) = self.now_binding {
 | 
			
		||||
            if let Some(shortcut) = ui.input_mut(|i| {
 | 
			
		||||
                i.events.iter().find_map(|event| match event {
 | 
			
		||||
                    Event::Key {
 | 
			
		||||
                        key,
 | 
			
		||||
                        pressed: true,
 | 
			
		||||
                        modifiers,
 | 
			
		||||
                        ..
 | 
			
		||||
                    } => Some(KeyboardShortcut::new(*modifiers, *key)),
 | 
			
		||||
                    _ => None,
 | 
			
		||||
                })
 | 
			
		||||
            }) {
 | 
			
		||||
                self.shortcuts.set(command, shortcut);
 | 
			
		||||
                self.now_binding = None;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show_ff_settings(&mut self, ui: &mut Ui) {
 | 
			
		||||
        let row_height = ui.spacing().interact_size.y;
 | 
			
		||||
        ui.section("Fast Forward", |ui| {
 | 
			
		||||
            let width = ui.available_width() - 8.0;
 | 
			
		||||
            let mut ff_settings = self.shortcuts.ff_settings();
 | 
			
		||||
            let mut updated = false;
 | 
			
		||||
            TableBuilder::new(ui)
 | 
			
		||||
                .column(Column::exact(width * 0.5))
 | 
			
		||||
                .column(Column::exact(width * 0.5))
 | 
			
		||||
                .body(|mut body| {
 | 
			
		||||
                    body.row(row_height, |mut row| {
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            ui.label("Treat button as toggle");
 | 
			
		||||
                        });
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            if ui.checkbox(&mut ff_settings.toggle, "").changed() {
 | 
			
		||||
                                updated = true;
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                    body.row(row_height, |mut row| {
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            ui.label("Speed multiplier");
 | 
			
		||||
                        });
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            if ui
 | 
			
		||||
                                .add_sized(
 | 
			
		||||
                                    ui.available_size(),
 | 
			
		||||
                                    Slider::new(&mut ff_settings.speed, 1..=15),
 | 
			
		||||
                                )
 | 
			
		||||
                                .changed()
 | 
			
		||||
                            {
 | 
			
		||||
                                updated = true;
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            if updated {
 | 
			
		||||
                self.shortcuts.update_ff_settings(ff_settings);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AppWindow for HotkeysWindow {
 | 
			
		||||
    fn viewport_id(&self) -> ViewportId {
 | 
			
		||||
        ViewportId::from_hash_of("shortcuts")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn initial_viewport(&self) -> ViewportBuilder {
 | 
			
		||||
        ViewportBuilder::default()
 | 
			
		||||
            .with_title("Keyboard Shortcuts")
 | 
			
		||||
            .with_inner_size((400.0, 400.0))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show(&mut self, ctx: &Context) {
 | 
			
		||||
        CentralPanel::default().show(ctx, |ui| {
 | 
			
		||||
            ui.horizontal(|ui| {
 | 
			
		||||
                if ui.button("Use defaults").clicked() {
 | 
			
		||||
                    self.shortcuts.reset();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            ui.separator();
 | 
			
		||||
            self.show_shortcuts(ui);
 | 
			
		||||
            self.show_ff_settings(ui);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,103 +0,0 @@
 | 
			
		|||
use egui::{
 | 
			
		||||
    Button, CentralPanel, Context, Event, KeyboardShortcut, Label, Layout, Ui, ViewportBuilder,
 | 
			
		||||
    ViewportId,
 | 
			
		||||
};
 | 
			
		||||
use egui_extras::{Column, TableBuilder};
 | 
			
		||||
 | 
			
		||||
use crate::input::{Command, ShortcutProvider};
 | 
			
		||||
 | 
			
		||||
use super::AppWindow;
 | 
			
		||||
 | 
			
		||||
pub struct ShortcutsWindow {
 | 
			
		||||
    shortcuts: ShortcutProvider,
 | 
			
		||||
    now_binding: Option<Command>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ShortcutsWindow {
 | 
			
		||||
    pub fn new(shortcuts: ShortcutProvider) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            shortcuts,
 | 
			
		||||
            now_binding: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show_shortcuts(&mut self, ui: &mut Ui) {
 | 
			
		||||
        ui.horizontal(|ui| {
 | 
			
		||||
            if ui.button("Use defaults").clicked() {
 | 
			
		||||
                self.shortcuts.reset();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        ui.separator();
 | 
			
		||||
        let row_height = ui.spacing().interact_size.y;
 | 
			
		||||
        let width = ui.available_width() - 20.0;
 | 
			
		||||
        TableBuilder::new(ui)
 | 
			
		||||
            .column(Column::exact(width * 0.3))
 | 
			
		||||
            .column(Column::exact(width * 0.5))
 | 
			
		||||
            .column(Column::exact(width * 0.2))
 | 
			
		||||
            .cell_layout(Layout::left_to_right(egui::Align::Center))
 | 
			
		||||
            .body(|mut body| {
 | 
			
		||||
                for command in Command::all() {
 | 
			
		||||
                    body.row(row_height, |mut row| {
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            ui.add_sized(ui.available_size(), Label::new(command.name()));
 | 
			
		||||
                        });
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            let button = if self.now_binding == Some(command) {
 | 
			
		||||
                                Button::new("Binding...")
 | 
			
		||||
                            } else if let Some(shortcut) = self.shortcuts.shortcut_for(command) {
 | 
			
		||||
                                Button::new(ui.ctx().format_shortcut(&shortcut))
 | 
			
		||||
                            } else {
 | 
			
		||||
                                Button::new("")
 | 
			
		||||
                            };
 | 
			
		||||
                            if ui.add_sized(ui.available_size(), button).clicked() {
 | 
			
		||||
                                self.now_binding = Some(command);
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                        row.col(|ui| {
 | 
			
		||||
                            if ui
 | 
			
		||||
                                .add_sized(ui.available_size(), Button::new("Clear"))
 | 
			
		||||
                                .clicked()
 | 
			
		||||
                            {
 | 
			
		||||
                                self.shortcuts.unset(command);
 | 
			
		||||
                                self.now_binding = None;
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        if let Some(command) = self.now_binding {
 | 
			
		||||
            if let Some(shortcut) = ui.input_mut(|i| {
 | 
			
		||||
                i.events.iter().find_map(|event| match event {
 | 
			
		||||
                    Event::Key {
 | 
			
		||||
                        key,
 | 
			
		||||
                        pressed: true,
 | 
			
		||||
                        modifiers,
 | 
			
		||||
                        ..
 | 
			
		||||
                    } => Some(KeyboardShortcut::new(*modifiers, *key)),
 | 
			
		||||
                    _ => None,
 | 
			
		||||
                })
 | 
			
		||||
            }) {
 | 
			
		||||
                self.shortcuts.set(command, shortcut);
 | 
			
		||||
                self.now_binding = None;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AppWindow for ShortcutsWindow {
 | 
			
		||||
    fn viewport_id(&self) -> ViewportId {
 | 
			
		||||
        ViewportId::from_hash_of("shortcuts")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn initial_viewport(&self) -> ViewportBuilder {
 | 
			
		||||
        ViewportBuilder::default()
 | 
			
		||||
            .with_title("Keyboard Shortcuts")
 | 
			
		||||
            .with_inner_size((400.0, 400.0))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show(&mut self, ctx: &Context) {
 | 
			
		||||
        CentralPanel::default().show(ctx, |ui| {
 | 
			
		||||
            self.show_shortcuts(ui);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue