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

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

@ -394,6 +394,13 @@ impl Component for ButtonController {
// State machine for the ButtonController
// We are matching event with `Event::Button` for a button action
// 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 {
Event::Button(button_event) => {
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,
// 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),
match which {
@ -432,33 +436,16 @@ impl Component for ButtonController {
// ▲ * | * ▲
ButtonEvent::ButtonReleased(b) if b == which_down => match which_down {
// ▲ *
// Trigger the button and make the second one clickable in all cases
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))
}
// * ▲
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))
}
},
// * ▼ | ▼ *
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 {
self.got_pressed(ctx, ButtonPos::Middle);
self.middle_hold_started(ctx);
@ -505,10 +492,7 @@ impl Component for ButtonController {
ButtonEvent::ButtonReleased(b) if b != which_up => {
// _ _
// Both buttons need to be clickable now
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
}
self.ignore_btn_delay.as_mut().map(IgnoreButtonDelay::reset);
if self.handle_middle_button {
(ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx))
} else {
@ -556,9 +540,6 @@ impl Component for ButtonController {
}
// Timer - handle clickable properties and HoldToConfirm expiration
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) {
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
/// small time period. This is to prevent accidental clicks when user is holding
/// the button to automatically move through items.
struct IgnoreButtonDelay {
/// How big is the delay after the button is inactive
delay: Duration,
/// Whether left button is currently clickable
left_clickable: bool,
/// Whether right button is currently clickable
right_clickable: bool,
/// Timer for setting the left_clickable
left_clickable_timer: Option<TimerToken>,
/// Timer for setting the right_clickable
right_clickable_timer: Option<TimerToken>,
/// Lock state
state: IgnoreButtonDelayState,
/// State of an ignored button that was pressed down while another button
/// was locked. We need to filter out its release event even after the
/// lock expires, otherwise a spurious Up event with no corresponding
/// Down event will escape.
ignored_button_needs_release: Option<PhysicalButton>,
}
impl IgnoreButtonDelay {
pub fn new(delay_ms: u32) -> Self {
Self {
delay: Duration::from_millis(delay_ms),
left_clickable: true,
right_clickable: true,
left_clickable_timer: None,
right_clickable_timer: None,
state: IgnoreButtonDelayState::Unlocked,
ignored_button_needs_release: None,
}
}
pub fn make_button_clickable(&mut self, pos: ButtonPos) {
match pos {
ButtonPos::Left => {
self.left_clickable = true;
self.left_clickable_timer = None;
/// Filter a ButtonPressed event.
fn filter_pressed(&mut self, button: PhysicalButton) -> bool {
match self.state {
IgnoreButtonDelayState::Unlocked => {
// A new button was pressed. Register ButtonDown, do not filter.
self.state = IgnoreButtonDelayState::ButtonDown(button, Instant::now());
false
}
ButtonPos::Right => {
self.right_clickable = true;
self.right_clickable_timer = None;
IgnoreButtonDelayState::ButtonDown(b, ..) if b != button => {
// A different button was pressed before locking the first one.
// Unlock, do not filter.
self.state = IgnoreButtonDelayState::Unlocked;
false
}
ButtonPos::Middle => {}
}
}
pub fn handle_button_press(&mut self, ctx: &mut EventCtx, button: PhysicalButton) {
if matches!(button, PhysicalButton::Left) {
self.right_clickable_timer = Some(ctx.request_timer(self.delay));
}
if matches!(button, PhysicalButton::Right) {
self.left_clickable_timer = Some(ctx.request_timer(self.delay));
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);
true
}
// Allow all other events.
_ => false,
}
}
pub fn ignore_button(&self, button: PhysicalButton) -> bool {
if matches!(button, PhysicalButton::Left) && !self.left_clickable {
return true;
}
if matches!(button, PhysicalButton::Right) && !self.right_clickable {
/// Filter a ButtonReleased event.
fn filter_released(&mut self, button: PhysicalButton) -> bool {
if matches!(self.ignored_button_needs_release, Some(b) if b == button) {
// Regardless of lock state, the ignored button that was previously pressed
// is now released. Clear the needs_release state and filter the event.
self.ignored_button_needs_release = None;
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
}
pub fn handle_timer_token(&mut self, token: TimerToken) {
if self.left_clickable_timer == Some(token) {
self.left_clickable = false;
self.left_clickable_timer = None;
/// Filter an incoming event
///
/// If the return value is `true`, the event is filtered and should not be
/// 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;
self.right_clickable_timer = None;
match event {
Event::Button(ButtonEvent::ButtonPressed(button)) => self.filter_pressed(button),
Event::Button(ButtonEvent::ButtonReleased(button)) => self.filter_released(button),
_ => false,
}
}
pub fn reset(&mut self) {
self.left_clickable = true;
self.right_clickable = true;
self.left_clickable_timer = None;
self.right_clickable_timer = None;
self.state = IgnoreButtonDelayState::Unlocked;
self.ignored_button_needs_release = None;
}
}

Loading…
Cancel
Save