refactor(core/t2b1): cleaner factoring of ignore-button logic

also does not require timer
matejcik/button-fixes
matejcik 1 month ago
parent 88a76b3063
commit ab436bfbc8

@ -394,6 +394,13 @@ impl Component for ButtonController {
// State machine for the ButtonController // State machine for the ButtonController
// We are matching event with `Event::Button` for a button action // We are matching event with `Event::Button` for a button action
// and `Event::Timer` for getting the expiration of HTC. // and `Event::Timer` for getting the expiration of HTC.
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
// Filter out opposite buttons after some delay
if ignore_btn_delay.filter_event(event) {
return None;
}
}
match event { match event {
Event::Button(button_event) => { Event::Button(button_event) => {
let (new_state, event) = match self.state { let (new_state, event) = match self.state {
@ -404,9 +411,6 @@ impl Component for ButtonController {
// ↓ _ | _ ↓ // ↓ _ | _ ↓
// Initial button press will set the timer for second button, // Initial button press will set the timer for second button,
// and after some delay, it will become un-clickable // and after some delay, it will become un-clickable
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.handle_button_press(ctx, which);
}
( (
ButtonState::OneDown(which), ButtonState::OneDown(which),
match which { match which {
@ -432,33 +436,16 @@ impl Component for ButtonController {
// ▲ * | * ▲ // ▲ * | * ▲
ButtonEvent::ButtonReleased(b) if b == which_down => match which_down { ButtonEvent::ButtonReleased(b) if b == which_down => match which_down {
// ▲ * // ▲ *
// Trigger the button and make the second one clickable in all cases
PhysicalButton::Left => { PhysicalButton::Left => {
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
}
// _ _
(ButtonState::Nothing, self.left_btn.maybe_trigger(ctx)) (ButtonState::Nothing, self.left_btn.maybe_trigger(ctx))
} }
// * ▲ // * ▲
PhysicalButton::Right => { PhysicalButton::Right => {
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
}
// _ _
(ButtonState::Nothing, self.right_btn.maybe_trigger(ctx)) (ButtonState::Nothing, self.right_btn.maybe_trigger(ctx))
} }
}, },
// * ▼ | ▼ * // * ▼ | ▼ *
ButtonEvent::ButtonPressed(b) if b != which_down => { ButtonEvent::ButtonPressed(b) if b != which_down => {
// Buttons may be non-clickable (caused by long-holding the other
// button)
if let Some(ignore_btn_delay) = &self.ignore_btn_delay {
if ignore_btn_delay.ignore_button(b) {
return None;
}
}
// ↓ ↓
if self.handle_middle_button { if self.handle_middle_button {
self.got_pressed(ctx, ButtonPos::Middle); self.got_pressed(ctx, ButtonPos::Middle);
self.middle_hold_started(ctx); self.middle_hold_started(ctx);
@ -505,10 +492,7 @@ impl Component for ButtonController {
ButtonEvent::ButtonReleased(b) if b != which_up => { ButtonEvent::ButtonReleased(b) if b != which_up => {
// _ _ // _ _
// Both buttons need to be clickable now // Both buttons need to be clickable now
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay { self.ignore_btn_delay.as_mut().map(IgnoreButtonDelay::reset);
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
}
if self.handle_middle_button { if self.handle_middle_button {
(ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx)) (ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx))
} else { } else {
@ -556,9 +540,6 @@ impl Component for ButtonController {
} }
// Timer - handle clickable properties and HoldToConfirm expiration // Timer - handle clickable properties and HoldToConfirm expiration
Event::Timer(token) => { Event::Timer(token) => {
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.handle_timer_token(token);
}
if let Some(pos) = self.handle_long_press_timer_token(token) { if let Some(pos) = self.handle_long_press_timer_token(token) {
return Some(ButtonControllerMsg::LongPressed(pos)); return Some(ButtonControllerMsg::LongPressed(pos));
} }
@ -589,82 +570,110 @@ impl Component for ButtonController {
} }
} }
#[derive(Copy, Clone)]
enum IgnoreButtonDelayState {
// All buttons are clickable.
Unlocked,
// Button was pressed down at a given time.
ButtonDown(PhysicalButton, Instant),
// Button is locked, events for all other buttons are ignored.
ButtonLocked(PhysicalButton),
}
/// When one button is pressed, the other one becomes un-clickable after some /// When one button is pressed, the other one becomes un-clickable after some
/// small time period. This is to prevent accidental clicks when user is holding /// small time period. This is to prevent accidental clicks when user is holding
/// the button to automatically move through items. /// the button to automatically move through items.
struct IgnoreButtonDelay { struct IgnoreButtonDelay {
/// How big is the delay after the button is inactive /// How big is the delay after the button is inactive
delay: Duration, delay: Duration,
/// Whether left button is currently clickable /// Lock state
left_clickable: bool, state: IgnoreButtonDelayState,
/// Whether right button is currently clickable /// State of an ignored button that was pressed down while another button
right_clickable: bool, /// was locked. We need to filter out its release event even after the
/// Timer for setting the left_clickable /// lock expires, otherwise a spurious Up event with no corresponding
left_clickable_timer: Option<TimerToken>, /// Down event will escape.
/// Timer for setting the right_clickable ignored_button_needs_release: Option<PhysicalButton>,
right_clickable_timer: Option<TimerToken>,
} }
impl IgnoreButtonDelay { impl IgnoreButtonDelay {
pub fn new(delay_ms: u32) -> Self { pub fn new(delay_ms: u32) -> Self {
Self { Self {
delay: Duration::from_millis(delay_ms), delay: Duration::from_millis(delay_ms),
left_clickable: true, state: IgnoreButtonDelayState::Unlocked,
right_clickable: true, ignored_button_needs_release: None,
left_clickable_timer: None,
right_clickable_timer: None,
} }
} }
pub fn make_button_clickable(&mut self, pos: ButtonPos) { /// Filter a ButtonPressed event.
match pos { fn filter_pressed(&mut self, button: PhysicalButton) -> bool {
ButtonPos::Left => { match self.state {
self.left_clickable = true; IgnoreButtonDelayState::Unlocked => {
self.left_clickable_timer = None; // A new button was pressed. Register ButtonDown, do not filter.
self.state = IgnoreButtonDelayState::ButtonDown(button, Instant::now());
false
} }
ButtonPos::Right => { IgnoreButtonDelayState::ButtonDown(b, ..) if b != button => {
self.right_clickable = true; // A different button was pressed before locking the first one.
self.right_clickable_timer = None; // Unlock, do not filter.
self.state = IgnoreButtonDelayState::Unlocked;
false
} }
ButtonPos::Middle => {} IgnoreButtonDelayState::ButtonLocked(b) if b != button => {
} // A button is already locked, and another button was pressed.
} // Track that this new button needs release, and filter the event.
self.ignored_button_needs_release = Some(button);
pub fn handle_button_press(&mut self, ctx: &mut EventCtx, button: PhysicalButton) { true
if matches!(button, PhysicalButton::Left) { }
self.right_clickable_timer = Some(ctx.request_timer(self.delay)); // Allow all other events.
} _ => false,
if matches!(button, PhysicalButton::Right) {
self.left_clickable_timer = Some(ctx.request_timer(self.delay));
} }
} }
pub fn ignore_button(&self, button: PhysicalButton) -> bool { /// Filter a ButtonReleased event.
if matches!(button, PhysicalButton::Left) && !self.left_clickable { fn filter_released(&mut self, button: PhysicalButton) -> bool {
return true; if matches!(self.ignored_button_needs_release, Some(b) if b == button) {
} // Regardless of lock state, the ignored button that was previously pressed
if matches!(button, PhysicalButton::Right) && !self.right_clickable { // is now released. Clear the needs_release state and filter the event.
self.ignored_button_needs_release = None;
return true; return true;
} }
match self.state {
IgnoreButtonDelayState::ButtonDown(b, ..) if b == button => {
// The button which was down was released. Unlock.
self.state = IgnoreButtonDelayState::Unlocked;
}
IgnoreButtonDelayState::ButtonLocked(b) if b == button => {
// The locked button was released. Unlock.
self.state = IgnoreButtonDelayState::Unlocked;
}
_ => {}
};
false false
} }
pub fn handle_timer_token(&mut self, token: TimerToken) { /// Filter an incoming event
if self.left_clickable_timer == Some(token) { ///
self.left_clickable = false; /// If the return value is `true`, the event is filtered and should not be
self.left_clickable_timer = None; /// processed further. Otherwise, the event should be processed as usual.
pub fn filter_event(&mut self, event: Event) -> bool {
// resolve delay elapsed event first
if let IgnoreButtonDelayState::ButtonDown(button, last_ms) = self.state {
let elapsed = Instant::now().saturating_duration_since(last_ms);
if elapsed >= self.delay {
self.state = IgnoreButtonDelayState::ButtonLocked(button);
}
} }
if self.right_clickable_timer == Some(token) {
self.right_clickable = false; match event {
self.right_clickable_timer = None; Event::Button(ButtonEvent::ButtonPressed(button)) => self.filter_pressed(button),
Event::Button(ButtonEvent::ButtonReleased(button)) => self.filter_released(button),
_ => false,
} }
} }
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.left_clickable = true; self.state = IgnoreButtonDelayState::Unlocked;
self.right_clickable = true; self.ignored_button_needs_release = None;
self.left_clickable_timer = None;
self.right_clickable_timer = None;
} }
} }

Loading…
Cancel
Save