diff --git a/src/controller/control.rs b/src/controller/control.rs index 59d50a23..2efc5678 100644 --- a/src/controller/control.rs +++ b/src/controller/control.rs @@ -54,7 +54,7 @@ pub struct Controller { /// We cache a right-hand form spec string similarly. right_hand_cached: String, /// We need to track keystate to implement modifier keys. - tracked_keys: HashMap, + tracked_keys: HashMap, /// True if we're using CGO's alternative grip. cgo_alt_grip: bool, } @@ -314,66 +314,62 @@ impl Controller { /// Returns an enum indicating what we did in response, so that the C++ layer can /// start a tick timer for cycle delay. pub fn handle_key_event(&mut self, key: u32, button: &ButtonEvent) -> KeyEventResponse { - let hotkey = Hotkey::from(key); - let state = KeyState::from(button); - // log::info!( - // "{key} {} {hotkey} {state}", - // char::from_u32(key).unwrap_or('X') - // ); - if matches!(hotkey, Hotkey::None) { + if matches!(Hotkey::from(key), Hotkey::None) { return KeyEventResponse::default(); } - // log::trace!("incoming key={}; state={};", hotkey, state); - - // We want all updates so we can track mod keys & long presses. // This call starts and stops long-press timers as well. - let keep_handling = self.update_tracked_key(&hotkey, button, false); + // It returns nothing if the handler should take no further action. + let Some(tracked) = self.create_or_update_tracked(key, button, false) else { + return KeyEventResponse::default(); + }; - // For mod keys, we're done. - if hotkey.is_modifier_key() || !keep_handling { + if tracked.ignore() || tracked.is_modifier() { return KeyEventResponse::default(); } + log::trace!("handling keypress={tracked}"); + // From here on, we only care if the key has gone up. - if state != KeyState::Up { + if tracked.state != KeyState::Up { return KeyEventResponse::handled(); } - let le_options = settings(); - // if the hud is NOT visible, we show it briefly before acting. - if showBriefly() { + let options = settings(); + let requested_action = tracked.action(); + // if the hud is NOT visible, we show it briefly before cycling. + if !matches!(requested_action, Action::RefreshLayout | Action::ShowHide) && showBriefly() { return KeyEventResponse::handled(); } - match hotkey { - Hotkey::Power => self.handle_cycle_power(), - Hotkey::Utility => self.handle_cycle_utility(), - Hotkey::Left => self.handle_cycle_left(&hotkey), - Hotkey::Right => self.handle_cycle_right(&hotkey), - Hotkey::Equipment => self.handle_cycle_equipset(&hotkey), - Hotkey::Activate => { - let activation_method = le_options.utility_activation_method(); + match requested_action { + Action::Power => self.handle_cycle_power(), + Action::Utility => self.handle_cycle_utility(), + Action::Left => self.handle_cycle_left(tracked), + Action::Right => self.handle_cycle_right(tracked), + Action::Equipment => self.handle_cycle_equipset(tracked), + Action::Activate => { + let activation_method = options.utility_activation_method(); if matches!(activation_method, ActivationMethod::Hotkey) { self.use_utility_item() } else { KeyEventResponse::default() } } - Hotkey::UnequipHands => { - let unarmed_method = le_options.unequip_method(); + Action::UnequipHands => { + let unarmed_method = options.unequip_method(); if matches!(unarmed_method, UnarmedMethod::Hotkey) { self.disarm_player() } else { KeyEventResponse::default() } } - Hotkey::Refresh => { + Action::RefreshLayout => { Layout::refresh(); KeyEventResponse::handled() } - Hotkey::ShowHide => { - if !le_options.autofade() { + Action::ShowHide => { + if !options.autofade() { self.cycles.toggle_hud(); } KeyEventResponse::handled() @@ -384,21 +380,20 @@ impl Controller { /// Handle the power/shouts key being pressed. fn handle_cycle_power(&mut self) -> KeyEventResponse { - // We don't need to worry about long presses here: those are handled by timers. - let settings = settings(); - let cycle_method = settings.cycle_advance_method(); - if matches!(cycle_method, ActivationMethod::Hotkey) { - self.advance_cycle_power() - } else if matches!(cycle_method, ActivationMethod::Modifier) { - let hotkey = self.get_tracked_key(&Hotkey::CycleModifier); - if hotkey.is_pressed() { - self.advance_cycle_power() - } else { - KeyEventResponse::default() + let options = settings(); + match options.cycle_advance_method() { + ActivationMethod::Hotkey => return self.advance_cycle_power(), + // We don't need to worry about long presses here: those are handled by timers. + ActivationMethod::LongPress => {} + ActivationMethod::Modifier => { + let hotkey = self.tracked_modifier(&Modifier::Cycle); + if hotkey.is_pressed() { + return self.advance_cycle_power(); + } } - } else { - KeyEventResponse::default() } + + KeyEventResponse::default() } /// The power/shouts keypress resulted in advancing the cycle. @@ -430,26 +425,26 @@ impl Controller { /// Hande the utilities/consumable key being pressed. fn handle_cycle_utility(&mut self) -> KeyEventResponse { // Same comment about long presses. - let settings = settings(); + let options = settings(); if matches!( - settings.utility_activation_method(), + options.utility_activation_method(), ActivationMethod::Modifier ) { - let modifier = self.get_tracked_key(&Hotkey::ActivateModifier); + let modifier = self.tracked_modifier(&Modifier::Activate); if modifier.is_pressed() { log::debug!("activating utilities/consumables"); return self.use_utility_item(); } } - let cycle_method = settings.cycle_advance_method(); + let cycle_method = options.cycle_advance_method(); if matches!(cycle_method, ActivationMethod::Hotkey) { log::debug!("cycling utilities/consumables"); return self.advance_cycle_utilities(); } if matches!(cycle_method, ActivationMethod::Modifier) { - let modifier = self.get_tracked_key(&Hotkey::CycleModifier); + let modifier = self.tracked_modifier(&Modifier::Cycle); if modifier.is_pressed() { log::debug!("cycling utilities/consumables"); return self.advance_cycle_utilities(); @@ -487,34 +482,39 @@ impl Controller { /// Figure out what is supposed to happen on a key up, given settings and keystate. /// Only used for right and left hand. - fn requested_keyup_action(&self, hotkey: &Hotkey) -> RequestedAction { - let settings = settings(); - let tracked = self.get_tracked_key(hotkey); + fn requested_keyup_action(&self, tracked: TrackedKey) -> RequestedAction { + let options = settings(); let is_long_press = tracked.is_long_press(); - let unequip_requested = match settings.unequip_method() { - UnarmedMethod::None => false, - UnarmedMethod::LongPress => tracked.is_long_press(), + let unequip_requested = match options.unequip_method() { + UnarmedMethod::LongPress => is_long_press, UnarmedMethod::Modifier => { - let unequipmod = self.get_tracked_key(&Hotkey::UnequipModifier); + let unequipmod = self.tracked_modifier(&Modifier::Unequip); unequipmod.is_pressed() } + UnarmedMethod::None => false, UnarmedMethod::AddToCycles => false, UnarmedMethod::Hotkey => false, // this hotkey has its own handler }; if unequip_requested { - RequestedAction::Unequip - } else if is_long_press && settings.long_press_to_dual_wield() { - RequestedAction::Match - } else if match settings.cycle_advance_method() { + return RequestedAction::Unequip; + } + + if is_long_press && options.long_press_to_dual_wield() { + return RequestedAction::Match; + } + + let advance_requested = match options.cycle_advance_method() { ActivationMethod::Hotkey => true, ActivationMethod::LongPress => is_long_press, ActivationMethod::Modifier => { - let cyclemod = self.get_tracked_key(&Hotkey::CycleModifier); + let cyclemod = self.tracked_modifier(&Modifier::Cycle); cyclemod.is_pressed() } - } { + }; + + if advance_requested { RequestedAction::Advance } else { RequestedAction::None @@ -522,17 +522,17 @@ impl Controller { } /// Handle the right hand hotkey. - fn handle_cycle_right(&mut self, hotkey: &Hotkey) -> KeyEventResponse { - let requested_action = self.requested_keyup_action(hotkey); + fn handle_cycle_right(&mut self, tracked: TrackedKey) -> KeyEventResponse { + let requested_action = self.requested_keyup_action(tracked); // The left hand needs these two steps separated. See next function. self.do_hand_action(requested_action, Action::Right, CycleSlot::Right) } /// Hande the left hand hotkey. - fn handle_cycle_left(&mut self, hotkey: &Hotkey) -> KeyEventResponse { + fn handle_cycle_left(&mut self, tracked: TrackedKey) -> KeyEventResponse { let settings = settings(); let cycle_ammo = settings.cycle_ammo(); - let requested_action = self.requested_keyup_action(hotkey); + let requested_action = self.requested_keyup_action(tracked); // Here's our different left-hand decision. // Do we have a bow equipped, and if so, is the "handle ammo" boolean set? @@ -843,7 +843,7 @@ impl Controller { return; } - let tracked = self.get_tracked_key(&Hotkey::from(&which)); + let tracked = self.tracked_key(&Hotkey::from(&which)); if tracked.is_pressed() { // Here's the reasoning. The player might be mid-long-press, in // which case we do not want to interrupt by equipping. The player @@ -1488,21 +1488,11 @@ impl Controller { // Much simpler than the cycle loop. We care if the cycle modifier key // is down (if one is set), and we care if the cycle button itself has // been pressed. - let hotkey = Hotkey::from(key); - if matches!(hotkey, Hotkey::None) { - return false; - } - // You want a fun bug? I'll give you a fun bug. If these two keys are the - // same, which they might be, we suddenly have to become context-aware. - let hotkey = if matches!(hotkey, Hotkey::ActivateModifier) { - Hotkey::MenuModifier - } else { - hotkey + let Some(tracked) = self.create_or_update_tracked(key, button, false) else { + return false; }; - - self.update_tracked_key(&hotkey, button, true); - if !hotkey.is_cycle_key() || !button.IsDown() { + if !tracked.is_cycle_key() || !button.IsDown() { return false; } @@ -1513,11 +1503,10 @@ impl Controller { ActivationMethod::Hotkey => true, ActivationMethod::LongPress => { log::debug!("checking for long press in menu"); - // if it's not found, will never be a long press - self.get_tracked_key(&hotkey).is_long_press() + tracked.is_long_press() } ActivationMethod::Modifier => { - let modkey = self.get_tracked_key(&Hotkey::MenuModifier); + let modkey = self.tracked_modifier(&Modifier::Menu); log::debug!( "checking for menu modifier key pressed in menu; {modkey:?} => {}", modkey.is_pressed() @@ -1575,88 +1564,108 @@ impl Controller { // Update the state of a tracked key so we can handle modifier keys and long-presses. // Returns whether the calling level should continue handling this key. - fn update_tracked_key(&mut self, hotkey: &Hotkey, button: &ButtonEvent, in_menu: bool) -> bool { - let mut retval = true; - let tracking_long_presses = !in_menu && settings().start_long_press_timer(hotkey); - let tracked = if let Some(previous) = self.tracked_keys.get_mut(hotkey) { + fn create_or_update_tracked( + &mut self, + key: u32, + button: &ButtonEvent, + in_menu: bool, + ) -> Option { + let mut return_the_key = true; + let should_start_timer = !in_menu && settings().should_start_long_press_timer(key); + + let tracked = if let Some(previous) = self.tracked_keys.get_mut(&key) { // We have seen this key before. // Did this key just have a long-press event? if so, ignore a key-up. // We ask this question before we update the tracking data. if matches!(previous.state, KeyState::Pressed) && previous.is_long_press() - && tracking_long_presses + && should_start_timer { - retval = false; + return_the_key = false; } previous.update(button); previous.clone() } else { - let mut tracked = TrackedKey { - key: hotkey.clone(), - state: KeyState::default(), - press_start: None, - }; - tracked.update(button); - self.tracked_keys.insert(hotkey.clone(), tracked.clone()); - tracked + let fresh = TrackedKey::new(key, button); + self.tracked_keys.insert(key, fresh.clone()); + fresh }; // long press timers; not started if we're in a menu - if tracking_long_presses { + if should_start_timer { + let action = tracked.action(); if matches!(tracked.state, KeyState::Down) { let duration = settings().long_press_ms(); - match tracked.key { - Hotkey::Left => startTimer(Action::LongPressLeft, duration), - Hotkey::Right => startTimer(Action::LongPressRight, duration), - Hotkey::Power => startTimer(Action::LongPressPower, duration), - Hotkey::Utility => startTimer(Action::LongPressUtility, duration), + match action { + Action::Power => startTimer(Action::LongPressPower, duration), + Action::Utility => startTimer(Action::LongPressUtility, duration), + Action::Left => startTimer(Action::LongPressLeft, duration), + Action::Right => startTimer(Action::LongPressRight, duration), _ => {} } } else if matches!(tracked.state, KeyState::Up) { - match tracked.key { - Hotkey::Left => stopTimer(Action::LongPressLeft), - Hotkey::Right => stopTimer(Action::LongPressRight), - Hotkey::Power => stopTimer(Action::LongPressPower), - Hotkey::Utility => stopTimer(Action::LongPressUtility), + match action { + Action::Power => stopTimer(Action::LongPressPower), + Action::Utility => stopTimer(Action::LongPressUtility), + Action::Left => stopTimer(Action::LongPressLeft), + Action::Right => stopTimer(Action::LongPressRight), _ => {} } } } - retval + + if return_the_key { + Some(tracked) + } else { + None + } } - fn get_tracked_key(&self, hotkey: &Hotkey) -> TrackedKey { - if let Some(tracked) = self.tracked_keys.get(hotkey) { + fn tracked_modifier(&self, modifier: &Modifier) -> TrackedKey { + let key = modifier.key_for(); + if key < 0 { + return TrackedKey::default(); + } + if let Some(tracked) = self.tracked_keys.get(&key.unsigned_abs()) { tracked.clone() } else { - TrackedKey { - key: Hotkey::None, - state: KeyState::Up, - press_start: None, - } + TrackedKey::default() + } + } + + fn tracked_key(&self, hotkey: &Hotkey) -> TrackedKey { + let key = hotkey.key_for(); + if key < 0 { + return TrackedKey::default(); + } + if let Some(tracked) = self.tracked_keys.get(&key.unsigned_abs()) { + tracked.clone() + } else { + TrackedKey::default() } } // ----------- equipment set functions /// Handle the power/shouts key being pressed. - fn handle_cycle_equipset(&mut self, _hotkey: &Hotkey) -> KeyEventResponse { - let le_options = settings(); - let cycle_method = le_options.cycle_advance_method(); + fn handle_cycle_equipset(&mut self, _tracked: TrackedKey) -> KeyEventResponse { + let options = settings(); + let cycle_method = options.cycle_advance_method(); - if matches!(cycle_method, ActivationMethod::Hotkey) { - self.advance_cycle_equipset() - } else if matches!(cycle_method, ActivationMethod::Modifier) { - let hotkey = self.get_tracked_key(&Hotkey::CycleModifier); - if hotkey.is_pressed() { - self.advance_cycle_equipset() - } else { - KeyEventResponse::default() + match cycle_method { + // handled with timers + ActivationMethod::LongPress => {} + ActivationMethod::Hotkey => return self.advance_cycle_equipset(), + ActivationMethod::Modifier => { + let modifier = self.tracked_modifier(&Modifier::Cycle); + if modifier.is_pressed() { + return self.advance_cycle_equipset(); + } } - } else { - KeyEventResponse::default() } + + KeyEventResponse::default() } /// Rotate to the next equipment set in the cycle and start the timer. diff --git a/src/controller/keys.rs b/src/controller/keys.rs index c00bf897..0aa14292 100644 --- a/src/controller/keys.rs +++ b/src/controller/keys.rs @@ -1,8 +1,10 @@ //! Structs and trait impls for considering keyboard/controller state. +//! There are too many enums here and a substantial rework is called for. use std::fmt::Display; use std::time::{Duration, Instant}; +use enumset::{EnumSet, EnumSetType}; use eyre::eyre; use strum::Display; @@ -18,7 +20,27 @@ pub enum CycleSlot { Utility, } -#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Display)] +#[derive(Debug, Hash, Display, EnumSetType)] +pub enum Modifier { + Unequip, + Cycle, + Activate, + Menu, +} + +impl Modifier { + pub fn key_for(&self) -> i32 { + let options = settings(); + match self { + Modifier::Unequip => options.unequip_modifier(), + Modifier::Cycle => options.cycle_modifier(), + Modifier::Activate => options.activate_modifier(), + Modifier::Menu => options.menu_modifier(), + } + } +} + +#[derive(Debug, Hash, Default, Display, Clone, PartialEq, Eq)] pub enum Hotkey { Power, Utility, @@ -29,30 +51,83 @@ pub enum Hotkey { UnequipHands, Refresh, ShowHide, - UnequipModifier, - CycleModifier, - ActivateModifier, - MenuModifier, + Modifier(EnumSet), // for overloaded modifiers #[default] None, } -impl Hotkey { - pub fn is_cycle_key(&self) -> bool { - matches!( - *self, - Hotkey::Left | Hotkey::Power | Hotkey::Right | Hotkey::Utility | Hotkey::Equipment - ) +impl From for Hotkey { + fn from(v: u32) -> Self { + let options = settings(); + let mut set: EnumSet = EnumSet::new(); + + if options.activate_modifier().is_positive() + && v == options.activate_modifier().unsigned_abs() + { + set.insert(Modifier::Activate); + } + if options.cycle_modifier().is_positive() && v == options.cycle_modifier().unsigned_abs() { + set.insert(Modifier::Cycle); + } + if options.unequip_modifier().is_positive() + && v == options.unequip_modifier().unsigned_abs() + { + set.insert(Modifier::Unequip); + } + if options.menu_modifier().is_positive() && v == options.menu_modifier().unsigned_abs() { + set.insert(Modifier::Menu); + } + + if !set.is_empty() { + Hotkey::Modifier(set) + } else if v == options.power() { + Hotkey::Power + } else if v == options.utility() { + Hotkey::Utility + } else if v == options.left() { + Hotkey::Left + } else if v == options.right() { + Hotkey::Right + } else if v == options.equipset() as u32 { + Hotkey::Equipment + } else if v == options.refresh_layout() { + Hotkey::Refresh + } else if v == options.showhide() { + Hotkey::ShowHide + } else if v == options.activate() { + Hotkey::Activate + } else if v == options.unequip_hotkey() as u32 { + Hotkey::UnequipHands + } else { + Hotkey::None + } } +} - pub fn is_modifier_key(&self) -> bool { - matches!( - *self, - Hotkey::ActivateModifier - | Hotkey::CycleModifier - | Hotkey::MenuModifier - | Hotkey::UnequipModifier - ) +impl Hotkey { + pub fn key_for(&self) -> i32 { + let options = settings(); + + match self { + Hotkey::Power => options.power() as i32, + Hotkey::Utility => options.utility() as i32, + Hotkey::Left => options.left() as i32, + Hotkey::Right => options.right() as i32, + Hotkey::Equipment => options.equipset() as i32, + Hotkey::Activate => options.activate() as i32, + Hotkey::UnequipHands => options.unequip_hotkey() as i32, + Hotkey::Refresh => options.refresh_layout() as i32, + Hotkey::ShowHide => options.showhide() as i32, + Hotkey::Modifier(meanings) => { + // This is going to map to a single re-used key. + if let Some(meaning) = meanings.iter().find_map(Some) { + meaning.key_for() + } else { + -1 + } + } + Hotkey::None => -1, + } } pub fn long_press_action(&self) -> RequestedAction { @@ -101,6 +176,7 @@ impl Hotkey { } } +// why does this exist? impl From<&CycleSlot> for Hotkey { fn from(value: &CycleSlot) -> Self { match *value { @@ -129,49 +205,6 @@ impl From<&Action> for Hotkey { } } -impl From for Hotkey { - fn from(v: u32) -> Self { - let settings = settings(); - if v == settings.power() { - Hotkey::Power - } else if v == settings.utility() { - Hotkey::Utility - } else if v == settings.left() { - Hotkey::Left - } else if v == settings.right() { - Hotkey::Right - } else if v == settings.equipset() as u32 { - Hotkey::Equipment - } else if v == settings.refresh_layout() { - Hotkey::Refresh - } else if v == settings.showhide() { - Hotkey::ShowHide - } else if v == settings.activate() { - Hotkey::Activate - } else if v == settings.unequip_hotkey() as u32 { - Hotkey::UnequipHands - } else if settings.activate_modifier().is_positive() - && v == settings.activate_modifier().unsigned_abs() - { - Hotkey::ActivateModifier - } else if settings.cycle_modifier().is_positive() - && v == settings.cycle_modifier().unsigned_abs() - { - Hotkey::CycleModifier - } else if settings.unequip_modifier().is_positive() - && v == settings.unequip_modifier().unsigned_abs() - { - Hotkey::UnequipModifier - } else if settings.menu_modifier().is_positive() - && v == settings.menu_modifier().unsigned_abs() - { - Hotkey::MenuModifier - } else { - Hotkey::None - } - } -} - #[derive(Debug, Default, Clone, Hash, PartialEq, Eq, Display)] pub enum KeyState { #[default] @@ -192,16 +225,50 @@ impl From<&ButtonEvent> for KeyState { } } +/// An input event tracked by the controller. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct TrackedKey { - pub key: Hotkey, + /// The code for the pressed input widget being tracked. + pub key: u32, + /// The UX-meaningful hotkey actions represented by this key event. + hotkey: Hotkey, + /// The current statue of the key. pub state: KeyState, + /// When we started tracking this key. pub press_start: Option, } impl TrackedKey { + pub fn new(key: u32, event: &ButtonEvent) -> Self { + let press_start = Some(Instant::now()); + let state = KeyState::from(event); + let hotkey = Hotkey::from(key); + + Self { + key, + hotkey, + state, + press_start, + } + } + + pub fn action(&self) -> Action { + Action::from(&self.hotkey) + } + pub fn ignore(&self) -> bool { - matches!(self.key, Hotkey::None) + matches!(self.hotkey, Hotkey::None) + } + + pub fn is_modifier(&self) -> bool { + matches!(self.hotkey, Hotkey::Modifier(_)) + } + + pub fn is_cycle_key(&self) -> bool { + matches!( + self.hotkey, + Hotkey::Left | Hotkey::Power | Hotkey::Right | Hotkey::Utility | Hotkey::Equipment + ) } pub fn update(&mut self, event: &ButtonEvent) { @@ -242,7 +309,8 @@ impl TrackedKey { impl Default for TrackedKey { fn default() -> Self { Self { - key: Hotkey::None, + key: 0, + hotkey: Hotkey::None, state: KeyState::Up, press_start: None, } @@ -265,6 +333,7 @@ impl From<&Hotkey> for Action { Hotkey::Activate => Action::Activate, Hotkey::Refresh => Action::RefreshLayout, Hotkey::ShowHide => Action::ShowHide, + Hotkey::Equipment => Action::Equipment, _ => Action::None, } } diff --git a/src/controller/settings.rs b/src/controller/settings.rs index c4a06b45..5cc0593e 100644 --- a/src/controller/settings.rs +++ b/src/controller/settings.rs @@ -318,9 +318,10 @@ impl UserSettings { } } - pub fn start_long_press_timer(&self, key: &Hotkey) -> bool { - let is_hand_cycle = matches!(key, Hotkey::Left | Hotkey::Right); - let can_be_unequipped = matches!(key, Hotkey::Left | Hotkey::Power | Hotkey::Right); + pub fn should_start_long_press_timer(&self, key: u32) -> bool { + let hotkey = Hotkey::from(key); + let is_hand_cycle = matches!(hotkey, Hotkey::Left | Hotkey::Right); + let can_be_unequipped = matches!(hotkey, Hotkey::Left | Hotkey::Power | Hotkey::Right); // These three should be mutually exclusive, so order shouldn't matter. // "should" ha ha ha @@ -329,7 +330,7 @@ impl UserSettings { } if matches!(self.how_to_activate, ActivationMethod::LongPress) && matches!( - key, + hotkey, Hotkey::Left | Hotkey::Power | Hotkey::Right | Hotkey::Utility ) { diff --git a/src/data/item_cache.rs b/src/data/item_cache.rs index 7d3bebda..8ed032d5 100644 --- a/src/data/item_cache.rs +++ b/src/data/item_cache.rs @@ -47,6 +47,10 @@ impl ItemCache { self.lru.len() } + pub fn is_empty(&self) -> bool { + self.lru.is_empty() + } + /// On load from save, we do not bother attempting to reconcile what /// we have cached with what the save state is. We merely enjoy the /// eternal sunshine of the spotless mind.