/ 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()