Skip to content

Add type hints, documentation fixes #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 16, 2021
73 changes: 44 additions & 29 deletions adafruit_cursorcontrol/cursorcontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
"""
import displayio

try:
from typing import Optional, Type
from types import TracebackType
except ImportError:
pass

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_CursorControl.git"

Expand All @@ -31,9 +37,10 @@ class Cursor:

:param ~displayio.Display display: CircuitPython display object.
:param ~displayio.Group display_group: CircuitPython group object to append the cursor to.
:param ~displayio.Bitmap bmp: CircuitPython bitmap object to use as the cursor
:param bool is_hidden: Cursor is hidden on init.
:param int cursor_speed: Speed of the cursor, in pixels.
:param int scale: Scale amount for the cursor in both directions.
:param bool is_hidden: Cursor is hidden on init.

Example for creating a cursor layer

Expand All @@ -53,12 +60,12 @@ class Cursor:
# pylint: disable=too-many-arguments,line-too-long
def __init__(
self,
display=None,
display_group=None,
bmp=None,
is_hidden=False,
cursor_speed=5,
scale=1,
display: Optional[displayio.Display] = None,
display_group: Optional[displayio.Group] = None,
bmp: Optional[displayio.Bitmap] = None,
is_hidden: bool = False,
cursor_speed: int = 5,
scale: int = 1,
):
self._display = display
self._scale = scale
Expand All @@ -75,19 +82,24 @@ def __init__(

# pylint: enable=too-many-arguments,line-too-long

def __enter__(self):
def __enter__(self) -> "Cursor":
return self

def __exit__(self, exception_type, exception_value, traceback):
def __exit__(
self,
exception_type: Optional[Type[type]],
exception_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
self.deinit()

def deinit(self):
def deinit(self) -> None:
"""deinitializes the cursor object."""
self._is_deinited()
self._scale = None
self._display_grp.remove(self._cursor_grp)

def _is_deinited(self):
def _is_deinited(self) -> None:
"""checks cursor deinitialization"""
if self._scale is None:
raise ValueError(
Expand All @@ -96,12 +108,12 @@ def _is_deinited(self):
)

@property
def scale(self):
def scale(self) -> int:
"""Returns the cursor's scale amount as an integer."""
return self._scale

@scale.setter
def scale(self, scale_value):
def scale(self, scale_value: int) -> None:
"""Scales the cursor by scale_value in both directions.
:param int scale_value: Amount to scale the cursor by.
"""
Expand All @@ -111,12 +123,12 @@ def scale(self, scale_value):
self._cursor_grp.scale = scale_value

@property
def speed(self):
def speed(self) -> int:
"""Returns the cursor's speed, in pixels."""
return self._speed

@speed.setter
def speed(self, speed):
def speed(self, speed: int) -> None:
"""Sets the speed of the cursor.
:param int speed: Cursor movement speed, in pixels.
"""
Expand All @@ -125,12 +137,12 @@ def speed(self, speed):
self._speed = speed

@property
def x(self):
def x(self) -> int:
"""Returns the cursor's x-coordinate."""
return self._cursor_grp.x

@x.setter
def x(self, x_val):
def x(self, x_val: int) -> None:
"""Sets the x-value of the cursor.
:param int x_val: cursor x-position, in pixels.
"""
Expand All @@ -143,12 +155,12 @@ def x(self, x_val):
self._cursor_grp.x = x_val

@property
def y(self):
def y(self) -> int:
"""Returns the cursor's y-coordinate."""
return self._cursor_grp.y

@y.setter
def y(self, y_val):
def y(self, y_val: int) -> None:
"""Sets the y-value of the cursor.
:param int y_val: cursor y-position, in pixels.
"""
Expand All @@ -161,12 +173,12 @@ def y(self, y_val):
self._cursor_grp.y = y_val

@property
def hidden(self):
def hidden(self) -> bool:
"""Returns True if the cursor is hidden or visible on the display."""
return self._is_hidden

@hidden.setter
def hidden(self, is_hidden):
def hidden(self, is_hidden: bool) -> None:
self._is_deinited()
if is_hidden:
self._is_hidden = True
Expand All @@ -175,16 +187,16 @@ def hidden(self, is_hidden):
self._is_hidden = False
self._display_grp.append(self._cursor_grp)

def hide(self):
def hide(self) -> None:
"""Hide the cursor."""
self.hidden = True

def show(self):
def show(self) -> None:
"""Show the cursor."""
self.hidden = False

# pylint:disable=no-self-use
def _default_cursor_bitmap(self):
def _default_cursor_bitmap(self) -> displayio.Bitmap:
bmp = displayio.Bitmap(20, 20, 3)
# left edge, outline
for i in range(0, bmp.height):
Expand All @@ -209,23 +221,26 @@ def _default_cursor_bitmap(self):
# pylint:enable=no-self-use

@property
def cursor_bitmap(self):
def cursor_bitmap(self) -> displayio.Bitmap:
"""Return the cursor bitmap."""
return self._cursor_bitmap

@cursor_bitmap.setter
def cursor_bitmap(self, bmp):
def cursor_bitmap(self, bmp: displayio.Bitmap) -> None:
"""Set a new cursor bitmap.

:param bmp: A Bitmap to use for the cursor
:param ~displayio.Bitmap bmp: A Bitmap to use for the cursor
"""
self._cursor_bitmap = bmp
self._cursor_grp.remove(self._cur_sprite)
self._cur_sprite = displayio.TileGrid(bmp, pixel_shader=self._cur_palette)
self._cursor_grp.append(self._cur_sprite)

def generate_cursor(self, bmp):
"""Generates a cursor icon"""
def generate_cursor(self, bmp: displayio.Bitmap) -> None:
"""Generates a cursor icon

:param ~displayio.Bitmap bmp: A Bitmap to use for the cursor
"""
self._is_deinited()
self._cursor_grp = displayio.Group(scale=self._scale)
self._cur_palette = displayio.Palette(3)
Expand Down
52 changes: 31 additions & 21 deletions adafruit_cursorcontrol/cursorcontrol_cursormanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
from keypad import ShiftRegisterKeys, Event
from adafruit_debouncer import Debouncer

try:
from typing import Optional, Type
from types import TracebackType
from adafruit_cursorcontrol.cursorcontrol import Cursor
except ImportError:
pass

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_CursorControl.git"

Expand All @@ -30,39 +37,44 @@
class CursorManager:
"""Simple interaction user interface interaction for Adafruit_CursorControl.

:param adafruit_cursorcontrol cursor: The cursor object we are using.
:param Cursor cursor: The cursor object we are using.
"""

def __init__(self, cursor):
def __init__(self, cursor: Cursor) -> None:
self._cursor = cursor
self._is_clicked = False
self._pad_states = 0
self._event = Event()
self._init_hardware()

def __enter__(self):
def __enter__(self) -> "CursorManager":
return self

def __exit__(self, exception_type, exception_value, traceback):
def __exit__(
self,
exception_type: Optional[Type[type]],
exception_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
self.deinit()

def deinit(self):
def deinit(self) -> None:
"""Deinitializes a CursorManager object."""
self._is_deinited()
self._pad.deinit()
self._cursor.deinit()
self._cursor = None
self._event = None

def _is_deinited(self):
def _is_deinited(self) -> None:
"""Checks if CursorManager object has been deinitd."""
if self._cursor is None:
raise ValueError(
"CursorManager object has been deinitialized and can no longer "
"be used. Create a new CursorManager object."
)

def _init_hardware(self):
def _init_hardware(self) -> None:
"""Initializes PyBadge or PyGamer hardware."""
if hasattr(board, "BUTTON_CLOCK") and not hasattr(board, "JOYSTICK_X"):
self._pad_btns = {
Expand Down Expand Up @@ -93,13 +105,13 @@ def _init_hardware(self):
)

@property
def is_clicked(self):
def is_clicked(self) -> bool:
"""Returns True if the cursor button was pressed
during previous call to update()
"""
return self._is_clicked

def update(self):
def update(self) -> None:
"""Updates the cursor object."""
if self._pad.events.get_into(self._event):
self._store_button_states()
Expand All @@ -109,7 +121,7 @@ def update(self):
elif self._pad_states & (1 << self._pad_btns["btn_a"]):
self._is_clicked = True

def _read_joystick_x(self, samples=3):
def _read_joystick_x(self, samples: int = 3) -> float:
"""Read the X analog joystick on the PyGamer.
:param int samples: How many samples to read and average.
"""
Expand All @@ -121,7 +133,7 @@ def _read_joystick_x(self, samples=3):
reading /= samples
return reading

def _read_joystick_y(self, samples=3):
def _read_joystick_y(self, samples: int = 3) -> float:
"""Read the Y analog joystick on the PyGamer.
:param int samples: How many samples to read and average.
"""
Expand All @@ -133,18 +145,16 @@ def _read_joystick_y(self, samples=3):
reading /= samples
return reading

def _store_button_states(self):
def _store_button_states(self) -> None:
"""Stores the state of the PyBadge's D-Pad or the PyGamer's Joystick
into a byte

:param Event event: The latest button press transition event detected.
"""
bit_index = self._event.key_number
current_state = (self._pad_states >> bit_index) & 1
if current_state != self._event.pressed:
self._pad_states = (1 << bit_index) ^ self._pad_states

def _check_cursor_movement(self):
def _check_cursor_movement(self) -> None:
"""Checks the PyBadge D-Pad or the PyGamer's Joystick for movement."""
if hasattr(board, "BUTTON_CLOCK") and not hasattr(board, "JOYSTICK_X"):
if self._pad_states & (1 << self._pad_btns["btn_right"]):
Expand Down Expand Up @@ -179,18 +189,18 @@ class DebouncedCursorManager(CursorManager):
the button is just pressed, and just released, as well it's current state. "Just" in this
context means "since the previous call to update."

:param adafruit_cursorcontrol cursor: The cursor object we are using.
:param Cursor cursor: The cursor object we are using.
"""

def __init__(self, cursor, debounce_interval=0.01):
def __init__(self, cursor: Cursor, debounce_interval: float = 0.01) -> None:
CursorManager.__init__(self, cursor)
self._debouncer = Debouncer(
lambda: bool(self._pad_states & (1 << self._pad_btns["btn_a"])),
interval=debounce_interval,
)

@property
def is_clicked(self):
def is_clicked(self) -> bool:
"""Returns True if the cursor button was pressed
during previous call to update()
"""
Expand All @@ -199,18 +209,18 @@ def is_clicked(self):
pressed = is_clicked

@property
def released(self):
def released(self) -> bool:
"""Returns True if the cursor button was released
during previous call to update()
"""
return self._debouncer.fell

@property
def held(self):
def held(self) -> bool:
"""Returns True if the cursor button is currently being held"""
return self._debouncer.value

def update(self):
def update(self) -> None:
"""Updates the cursor object."""
if self._pad.events.get_into(self._event):
self._store_button_states()
Expand Down