/ adafruit_cursorcontrol / cursorcontrol_cursormanager.py
cursorcontrol_cursormanager.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2019 Brent Rubell for Adafruit Industries 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 23 """ 24 `adafruit_cursorcontrol_cursormanager` 25 ================================================================================ 26 Simple interaction user interface interaction for Adafruit_CursorControl. 27 * Author(s): Brent Rubell 28 """ 29 import board 30 import digitalio 31 from micropython import const 32 import analogio 33 from gamepadshift import GamePadShift 34 from adafruit_debouncer import Debouncer 35 36 # PyBadge 37 PYBADGE_BUTTON_LEFT = const(128) 38 PYBADGE_BUTTON_UP = const(64) 39 PYBADGE_BUTTON_DOWN = const(32) 40 PYBADGE_BUTTON_RIGHT = const(16) 41 # PyBadge & PyGamer 42 PYBADGE_BUTTON_A = const(2) 43 44 45 class CursorManager: 46 """Simple interaction user interface interaction for Adafruit_CursorControl. 47 48 :param adafruit_cursorcontrol cursor: The cursor object we are using. 49 """ 50 51 def __init__(self, cursor): 52 self._cursor = cursor 53 self._is_clicked = False 54 self._init_hardware() 55 56 def __enter__(self): 57 return self 58 59 def __exit__(self, exception_type, exception_value, traceback): 60 self.deinit() 61 62 def deinit(self): 63 """Deinitializes a CursorManager object.""" 64 self._is_deinited() 65 self._pad.deinit() 66 self._cursor.deinit() 67 self._cursor = None 68 69 def _is_deinited(self): 70 """Checks if CursorManager object has been deinitd.""" 71 if self._cursor is None: 72 raise ValueError( 73 "CursorManager object has been deinitialized and can no longer " 74 "be used. Create a new CursorManager object." 75 ) 76 77 def _init_hardware(self): 78 """Initializes PyBadge or PyGamer hardware.""" 79 if hasattr(board, "BUTTON_CLOCK") and not hasattr(board, "JOYSTICK_X"): 80 self._pad_btns = { 81 "btn_left": PYBADGE_BUTTON_LEFT, 82 "btn_right": PYBADGE_BUTTON_RIGHT, 83 "btn_up": PYBADGE_BUTTON_UP, 84 "btn_down": PYBADGE_BUTTON_DOWN, 85 "btn_a": PYBADGE_BUTTON_A, 86 } 87 elif hasattr(board, "JOYSTICK_X"): 88 self._joystick_x = analogio.AnalogIn(board.JOYSTICK_X) 89 self._joystick_y = analogio.AnalogIn(board.JOYSTICK_Y) 90 self._pad_btns = {"btn_a": PYBADGE_BUTTON_A} 91 # Sample the center points of the joystick 92 self._center_x = self._joystick_x.value 93 self._center_y = self._joystick_y.value 94 else: 95 raise AttributeError( 96 "Board must have a D-Pad or Joystick for use with CursorManager!" 97 ) 98 self._pad = GamePadShift( 99 digitalio.DigitalInOut(board.BUTTON_CLOCK), 100 digitalio.DigitalInOut(board.BUTTON_OUT), 101 digitalio.DigitalInOut(board.BUTTON_LATCH), 102 ) 103 104 @property 105 def is_clicked(self): 106 """Returns True if the cursor button was pressed 107 during previous call to update() 108 """ 109 return self._is_clicked 110 111 def update(self): 112 """Updates the cursor object.""" 113 pressed = self._pad.get_pressed() 114 self._check_cursor_movement(pressed) 115 if self._is_clicked: 116 self._is_clicked = False 117 elif pressed & self._pad_btns["btn_a"]: 118 self._is_clicked = True 119 120 def _read_joystick_x(self, samples=3): 121 """Read the X analog joystick on the PyGamer. 122 :param int samples: How many samples to read and average. 123 """ 124 reading = 0 125 # pylint: disable=unused-variable 126 if hasattr(board, "JOYSTICK_X"): 127 for sample in range(0, samples): 128 reading += self._joystick_x.value 129 reading /= samples 130 return reading 131 132 def _read_joystick_y(self, samples=3): 133 """Read the Y analog joystick on the PyGamer. 134 :param int samples: How many samples to read and average. 135 """ 136 reading = 0 137 # pylint: disable=unused-variable 138 if hasattr(board, "JOYSTICK_Y"): 139 for sample in range(0, samples): 140 reading += self._joystick_y.value 141 reading /= samples 142 return reading 143 144 def _check_cursor_movement(self, pressed=None): 145 """Checks the PyBadge D-Pad or the PyGamer's Joystick for movement. 146 :param int pressed: 8-bit number with bits that correspond to buttons 147 which have been pressed down since the last call to get_pressed(). 148 """ 149 if hasattr(board, "BUTTON_CLOCK") and not hasattr(board, "JOYSTICK_X"): 150 if pressed & self._pad_btns["btn_right"]: 151 self._cursor.x += self._cursor.speed 152 elif pressed & self._pad_btns["btn_left"]: 153 self._cursor.x -= self._cursor.speed 154 if pressed & self._pad_btns["btn_up"]: 155 self._cursor.y -= self._cursor.speed 156 elif pressed & self._pad_btns["btn_down"]: 157 self._cursor.y += self._cursor.speed 158 elif hasattr(board, "JOYSTICK_X"): 159 joy_x = self._read_joystick_x() 160 joy_y = self._read_joystick_y() 161 if joy_x > self._center_x + 1000: 162 self._cursor.x += self._cursor.speed 163 elif joy_x < self._center_x - 1000: 164 self._cursor.x -= self._cursor.speed 165 if joy_y > self._center_y + 1000: 166 self._cursor.y += self._cursor.speed 167 elif joy_y < self._center_y - 1000: 168 self._cursor.y -= self._cursor.speed 169 else: 170 raise AttributeError( 171 "Board must have a D-Pad or Joystick for use with CursorManager!" 172 ) 173 174 175 class DebouncedCursorManager(CursorManager): 176 """Simple interaction user interface interaction for Adafruit_CursorControl. 177 This subclass provide a debounced version on the A button and provides queries for when 178 the button is just pressed, and just released, as well it's current state. "Just" in this 179 context means "since the previous call to update." 180 181 :param adafruit_cursorcontrol cursor: The cursor object we are using. 182 """ 183 184 def __init__(self, cursor, debounce_interval=0.01): 185 CursorManager.__init__(self, cursor) 186 self._pressed = 0 187 self._debouncer = Debouncer( 188 lambda: bool(self._pressed & self._pad_btns["btn_a"]), 189 interval=debounce_interval, 190 ) 191 192 @property 193 def is_clicked(self): 194 """Returns True if the cursor button was pressed 195 during previous call to update() 196 """ 197 return self._debouncer.rose 198 199 pressed = is_clicked 200 201 @property 202 def released(self): 203 """Returns True if the cursor button was released 204 during previous call to update() 205 """ 206 return self._debouncer.fell 207 208 @property 209 def held(self): 210 """Returns True if the cursor button is currently being held 211 """ 212 return self._debouncer.value 213 214 def update(self): 215 """Updates the cursor object.""" 216 self._pressed = self._pad.get_pressed() 217 self._check_cursor_movement(self._pressed) 218 self._debouncer.update()