From 41b7dd6d85112ba0f9ef28b4ce1ef63bc881865e Mon Sep 17 00:00:00 2001 From: Serge Bazanski <q3k@q3k.org> Date: Fri, 11 Aug 2023 12:08:33 +0200 Subject: [PATCH] py: re-introduce os/app button naming This finally bakes in some UX assumptions about the meaning of each button. The app button (by default the left button) is for exclusive use by applications. In the menu, the app button is used to go left/right and to select an option. The os button (by default the right button) is for exclusive use by the OS. It always adjusts the volume and allows the user to navigate back (either exit the app or go back in menus - the latter not being implemented yet). --- python_payload/st3m/about.py | 8 +- python_payload/st3m/application.py | 2 +- python_payload/st3m/input.py | 124 ++++++++++++++++++++--------- python_payload/st3m/processors.py | 4 +- python_payload/st3m/ui/menu.py | 6 +- 5 files changed, 95 insertions(+), 49 deletions(-) diff --git a/python_payload/st3m/about.py b/python_payload/st3m/about.py index 3f0b03bbe0..c2058bcf11 100644 --- a/python_payload/st3m/about.py +++ b/python_payload/st3m/about.py @@ -101,14 +101,10 @@ class About(BaseView): super().think(ins, delta_ms) self.ts += delta_ms / 1000 - if self.input.left_shoulder.middle.pressed: - if self.vm is not None: - self.vm.pop(ViewTransitionSwipeRight()) - # Change target screen intent. - if self.input.left_shoulder.left.pressed and self._can_left(): + if self.input.buttons.app.left.pressed and self._can_left(): self.screen_ix -= 1 - if self.input.left_shoulder.right.pressed and self._can_right(): + if self.input.buttons.app.right.pressed and self._can_right(): self.screen_ix += 1 # Calculate animation/transitions. diff --git a/python_payload/st3m/application.py b/python_payload/st3m/application.py index fc3b515d09..673890cc91 100644 --- a/python_payload/st3m/application.py +++ b/python_payload/st3m/application.py @@ -43,7 +43,7 @@ class Application(BaseView): def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) - if self.input.left_shoulder.middle.pressed: + if self.input.buttons.os.middle.pressed: if self.vm is not None: self.on_exit() self.vm.pop(ViewTransitionSwipeRight()) diff --git a/python_payload/st3m/input.py b/python_payload/st3m/input.py index ea420b9a0a..f491c95fc8 100644 --- a/python_payload/st3m/input.py +++ b/python_payload/st3m/input.py @@ -28,6 +28,44 @@ class IMUState: self.pressure = pressure +class InputButtonState: + """ + State of the tri-state switches/buttons on the shoulders of the badge. + + If you want to detect edges, use the stateful InputController. + + By default, the left shoulder button is the 'app' button and the right + shoulder button is the 'os' button. The user can switch this behaviour in + the settings menu. + + The 'app' button can be freely used by applicaton code. The 'os' menu has + fixed functions: volume up/down and back. + + In cases you want to access left/right buttons independently of app/os + mapping (for example in applications where the handedness of the user + doesn't matter), then you can use _left and _right to access their state + directly. + + 'app_is_left' is provided to let you figure out on which side of the badge + the app button is, eg. for use when highlighting buttons on the screen or + with LEDs. + """ + + __slots__ = ("app", "os", "_left", "_right", "app_is_left") + + def __init__(self, left: int, right: int, swapped: bool): + app = left + os = right + if swapped: + app, os = os, app + + self.app = app + self.os = os + self._left = left + self._right = right + self.app_is_left = not swapped + + class InputState: """ Current state of inputs from badge user. Passed via think() to every @@ -39,16 +77,14 @@ class InputState: def __init__( self, captouch: captouch.CaptouchState, - left_button: int, - right_button: int, + buttons: InputButtonState, imu: IMUState, temperature: float, battery_voltage: float, ) -> None: # self.petal_pads = petal_pads self.captouch = captouch - self.left_button = left_button - self.right_button = right_button + self.buttons = buttons self.imu = imu self.temperature = temperature self.battery_voltage = battery_voltage @@ -60,10 +96,9 @@ class InputState: Reactor. """ cts = captouch.read() - left_button = hardware.left_button_get() - right_button = hardware.right_button_get() - if swapped_buttons: - left_button, right_button = right_button, left_button + left = hardware.left_button_get() + right = hardware.right_button_get() + buttons = InputButtonState(left, right, swapped_buttons) acc = imu.acc_read() gyro = imu.gyro_read() @@ -73,8 +108,7 @@ class InputState: battery_voltage = power.battery_voltage return InputState( cts, - left_button, - right_button, + buttons, imu_state, temperature, battery_voltage, @@ -461,35 +495,19 @@ class CaptouchState: petal.whole._ignore_pressed() -class TriSwitchHandedness(Enum): - """ - Left or right shoulder button. - """ - - left = "left" - right = "right" - - class TriSwitchState: """ State of a tri-stat shoulder button """ - __slots__ = ("left", "middle", "right", "handedness") - - def __init__(self, h: TriSwitchHandedness) -> None: - self.handedness = h + __slots__ = ("left", "middle", "right") + def __init__(self) -> None: self.left = Pressable(False) self.middle = Pressable(False) self.right = Pressable(False) - def _update(self, ts: int, hr: InputState) -> None: - st = ( - hr.left_button - if self.handedness == TriSwitchHandedness.left - else hr.right_button - ) + def _update(self, ts: int, st: int) -> None: self.left._update(ts, st == -1) self.middle._update(ts, st == 2) self.right._update(ts, st == 1) @@ -500,6 +518,42 @@ class TriSwitchState: self.right._ignore_pressed() +class ButtonsState: + """ + Edge-trigger detection for input button state. + + See InputButtonState for more information about the meaning of app, os, + _left, _right and app_is_left. + """ + + __slots__ = ("app", "os", "_left", "_right", "app_is_left") + + def __init__(self) -> None: + self.app = TriSwitchState() + self.os = TriSwitchState() + + # Defaults. Real data coming from _update will change this to the + # correct values from an InputState. + self._left = self.app + self._right = self.os + self.app_is_left = True + + def _update(self, ts: int, hr: InputState) -> None: + self.app._update(ts, hr.buttons.app) + self.os._update(ts, hr.buttons.os) + self.app_is_left = hr.buttons.app_is_left + if self.app_is_left: + self._left = self.app + self._right = self.os + else: + self._left = self.os + self._right = self.app + + def _ignore_pressed(self) -> None: + self.app._ignore_pressed() + self.os._ignore_pressed() + + class InputController: """ A stateful input controller. It accepts InputState updates from the Reactor @@ -514,22 +568,19 @@ class InputController: __slots__ = ( "captouch", - "left_shoulder", - "right_shoulder", + "buttons", "_ts", ) def __init__(self) -> None: self.captouch = CaptouchState() - self.left_shoulder = TriSwitchState(TriSwitchHandedness.left) - self.right_shoulder = TriSwitchState(TriSwitchHandedness.right) + self.buttons = ButtonsState() self._ts = 0 def think(self, hr: InputState, delta_ms: int) -> None: self._ts += delta_ms self.captouch._update(self._ts, hr) - self.left_shoulder._update(self._ts, hr) - self.right_shoulder._update(self._ts, hr) + self.buttons._update(self._ts, hr) def _ignore_pressed(self) -> None: """ @@ -538,5 +589,4 @@ class InputController: have just been foregrounded. """ self.captouch._ignore_pressed() - self.left_shoulder._ignore_pressed() - self.right_shoulder._ignore_pressed() + self.buttons._ignore_pressed() diff --git a/python_payload/st3m/processors.py b/python_payload/st3m/processors.py index 787a18fa17..6f7e78b40f 100644 --- a/python_payload/st3m/processors.py +++ b/python_payload/st3m/processors.py @@ -37,13 +37,13 @@ class AudioProcessor(Processor): adjusted = False # Whether the volume is so low that we should enable mute. should_mute = False - if self.input.right_shoulder.left.pressed: + if self.input.buttons.os.left.pressed: started_at = audio.get_volume_dB() if started_at <= -20: should_mute = True audio.adjust_volume_dB(-5) adjusted = True - if self.input.right_shoulder.right.pressed: + if self.input.buttons.os.right.pressed: if not audio.get_mute(): audio.adjust_volume_dB(5) adjusted = True diff --git a/python_payload/st3m/ui/menu.py b/python_payload/st3m/ui/menu.py index 36ec747e2c..66c82ca9db 100644 --- a/python_payload/st3m/ui/menu.py +++ b/python_payload/st3m/ui/menu.py @@ -172,8 +172,8 @@ class MenuController(BaseView): super().on_enter(vm) def _parse_state(self) -> None: - left = self.input.left_shoulder.left - right = self.input.left_shoulder.right + left = self.input.buttons.app.left + right = self.input.buttons.app.right if left.pressed: self._scroll_controller.scroll_left() @@ -197,7 +197,7 @@ class MenuController(BaseView): self._scroll_controller.think(ins, delta_ms) - if self.input.left_shoulder.middle.pressed: + if self.input.buttons.app.middle.pressed: self.select() self._parse_state() -- GitLab