Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4f1f480

Browse files
authoredJun 26, 2019
Merge pull request #1 from brentru/cursor-control
Cursor Control!
2 parents 8eda7d2 + 28a34db commit 4f1f480

20 files changed

+1538
-4
lines changed
 

‎.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
*.mpy
2+
.idea
3+
__pycache__
4+
_build
5+
*.pyc
6+
.env
7+
build*
8+
bundles
9+
*.DS_Store
10+
.eggs
11+
dist
12+
**/*.egg-info

‎.pylintrc

Lines changed: 433 additions & 0 deletions
Large diffs are not rendered by default.

‎.readthedocs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
python:
2+
version: 3
3+
requirements_file: requirements.txt

‎.travis.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# This is a common .travis.yml for generating library release zip files for
2+
# CircuitPython library releases using circuitpython-build-tools.
3+
# See https://github.com/adafruit/circuitpython-build-tools for detailed setup
4+
# instructions.
5+
6+
dist: xenial
7+
language: python
8+
python:
9+
- "3.6"
10+
11+
cache:
12+
pip: true
13+
14+
# TODO: if deployment to PyPi is desired, change 'DEPLOY_PYPI' to "true",
15+
# or remove the env block entirely and remove the condition in the
16+
# deploy block.
17+
env:
18+
- DEPLOY_PYPI="false"
19+
20+
deploy:
21+
- provider: releases
22+
api_key: "$GITHUB_TOKEN"
23+
file_glob: true
24+
file: "$TRAVIS_BUILD_DIR/bundles/*"
25+
skip_cleanup: true
26+
overwrite: true
27+
on:
28+
tags: true
29+
# TODO: Use 'travis encrypt --com -r adafruit/<repo slug>' to generate
30+
# the encrypted password for adafruit-travis. Paste result below.
31+
- provider: pypi
32+
user: adafruit-travis
33+
password:
34+
secure: #-- PASTE ENCRYPTED PASSWORD HERE --#
35+
on:
36+
tags: true
37+
condition: $DEPLOY_PYPI = "true"
38+
39+
install:
40+
- pip install -r requirements.txt
41+
- pip install circuitpython-build-tools Sphinx sphinx-rtd-theme
42+
- pip install --force-reinstall pylint==1.9.2
43+
44+
script:
45+
- pylint adafruit_cursorcontrol/*.py
46+
- ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace examples/*.py)
47+
- circuitpython-build-bundles --filename_prefix adafruit-circuitpython-cursorcontrol --library_location .
48+
- cd docs && sphinx-build -E -W -b html . _build/html && cd ..

‎CODE_OF_CONDUCT.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Adafruit Community Code of Conduct
2+
3+
## Our Pledge
4+
5+
In the interest of fostering an open and welcoming environment, we as
6+
contributors and leaders pledge to making participation in our project and
7+
our community a harassment-free experience for everyone, regardless of age, body
8+
size, disability, ethnicity, gender identity and expression, level or type of
9+
experience, education, socio-economic status, nationality, personal appearance,
10+
race, religion, or sexual identity and orientation.
11+
12+
## Our Standards
13+
14+
We are committed to providing a friendly, safe and welcoming environment for
15+
all.
16+
17+
Examples of behavior that contributes to creating a positive environment
18+
include:
19+
20+
* Be kind and courteous to others
21+
* Using welcoming and inclusive language
22+
* Being respectful of differing viewpoints and experiences
23+
* Collaborating with other community members
24+
* Gracefully accepting constructive criticism
25+
* Focusing on what is best for the community
26+
* Showing empathy towards other community members
27+
28+
Examples of unacceptable behavior by participants include:
29+
30+
* The use of sexualized language or imagery and sexual attention or advances
31+
* The use of inappropriate images, including in a community member's avatar
32+
* The use of inappropriate language, including in a community member's nickname
33+
* Any spamming, flaming, baiting or other attention-stealing behavior
34+
* Excessive or unwelcome helping; answering outside the scope of the question
35+
asked
36+
* Trolling, insulting/derogatory comments, and personal or political attacks
37+
* Public or private harassment
38+
* Publishing others' private information, such as a physical or electronic
39+
address, without explicit permission
40+
* Other conduct which could reasonably be considered inappropriate
41+
42+
The goal of the standards and moderation guidelines outlined here is to build
43+
and maintain a respectful community. We ask that you don’t just aim to be
44+
"technically unimpeachable", but rather try to be your best self.
45+
46+
We value many things beyond technical expertise, including collaboration and
47+
supporting others within our community. Providing a positive experience for
48+
other community members can have a much more significant impact than simply
49+
providing the correct answer.
50+
51+
## Our Responsibilities
52+
53+
Project leaders are responsible for clarifying the standards of acceptable
54+
behavior and are expected to take appropriate and fair corrective action in
55+
response to any instances of unacceptable behavior.
56+
57+
Project leaders have the right and responsibility to remove, edit, or
58+
reject messages, comments, commits, code, issues, and other contributions
59+
that are not aligned to this Code of Conduct, or to ban temporarily or
60+
permanently any community member for other behaviors that they deem
61+
inappropriate, threatening, offensive, or harmful.
62+
63+
## Moderation
64+
65+
Instances of behaviors that violate the Adafruit Community Code of Conduct
66+
may be reported by any member of the community. Community members are
67+
encouraged to report these situations, including situations they witness
68+
involving other community members.
69+
70+
You may report in the following ways:
71+
72+
In any situation, you may send an email to <support@adafruit.com>.
73+
74+
On the Adafruit Discord, you may send an open message from any channel
75+
to all Community Helpers by tagging @community moderators. You may also send an
76+
open message from any channel, or a direct message to @kattni#1507,
77+
@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or
78+
@Andon#8175.
79+
80+
Email and direct message reports will be kept confidential.
81+
82+
In situations on Discord where the issue is particularly egregious, possibly
83+
illegal, requires immediate action, or violates the Discord terms of service,
84+
you should also report the message directly to Discord.
85+
86+
These are the steps for upholding our community’s standards of conduct.
87+
88+
1. Any member of the community may report any situation that violates the
89+
Adafruit Community Code of Conduct. All reports will be reviewed and
90+
investigated.
91+
2. If the behavior is an egregious violation, the community member who
92+
committed the violation may be banned immediately, without warning.
93+
3. Otherwise, moderators will first respond to such behavior with a warning.
94+
4. Moderators follow a soft "three strikes" policy - the community member may
95+
be given another chance, if they are receptive to the warning and change their
96+
behavior.
97+
5. If the community member is unreceptive or unreasonable when warned by a
98+
moderator, or the warning goes unheeded, they may be banned for a first or
99+
second offense. Repeated offenses will result in the community member being
100+
banned.
101+
102+
## Scope
103+
104+
This Code of Conduct and the enforcement policies listed above apply to all
105+
Adafruit Community venues. This includes but is not limited to any community
106+
spaces (both public and private), the entire Adafruit Discord server, and
107+
Adafruit GitHub repositories. Examples of Adafruit Community spaces include
108+
but are not limited to meet-ups, audio chats on the Adafruit Discord, or
109+
interaction at a conference.
110+
111+
This Code of Conduct applies both within project spaces and in public spaces
112+
when an individual is representing the project or its community. As a community
113+
member, you are representing our community, and are expected to behave
114+
accordingly.
115+
116+
## Attribution
117+
118+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119+
version 1.4, available at
120+
<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>,
121+
and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html).
122+
123+
For other projects adopting the Adafruit Community Code of
124+
Conduct, please contact the maintainers of those projects for enforcement.
125+
If you wish to use this code of conduct for your own project, consider
126+
explicitly mentioning your moderation policy or making a copy with your
127+
own moderation policy so as to avoid confusion.

‎LICENSE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
MIT License
1+
The MIT License (MIT)
22

3-
Copyright (c) 2019 Adafruit Industries
3+
Copyright (c) 2019 Brent Rubell for Adafruit Industries
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

‎README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

‎README.rst

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
Introduction
2+
============
3+
4+
.. image:: https://readthedocs.org/projects/adafruit-circuitpython-cursorcontrol/badge/?version=latest
5+
:target: https://circuitpython.readthedocs.io/projects/cursorcontrol/en/latest/
6+
:alt: Documentation Status
7+
8+
.. image:: https://img.shields.io/discord/327254708534116352.svg
9+
:target: https://discord.gg/nBQh6qu
10+
:alt: Discord
11+
12+
.. image:: https://travis-ci.com/adafruit/Adafruit_CircuitPython_CursorControl.svg?branch=master
13+
:target: https://travis-ci.com/adafruit/Adafruit_CircuitPython_CursorControl
14+
:alt: Build Status
15+
16+
Mouse cursor for interaction with CircuitPython UI elements such as
17+
`buttons <https://github.com/adafruit/Adafruit_CircuitPython_Display_Button>`_.
18+
19+
Dependencies
20+
=============
21+
This driver depends on:
22+
23+
* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
24+
25+
Please ensure all dependencies are available on the CircuitPython filesystem.
26+
This is easily achieved by downloading
27+
`the Adafruit library and driver bundle <https://github.com/adafruit/Adafruit_CircuitPython_Bundle>`_.
28+
29+
Installing from PyPI
30+
=====================
31+
.. note:: This library is not available on PyPI yet. Install documentation is included
32+
as a standard element. Stay tuned for PyPI availability!
33+
34+
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
35+
PyPI <https://pypi.org/project/adafruit-circuitpython-cursorcontrol/>`_. To install for current user:
36+
37+
.. code-block:: shell
38+
39+
pip3 install adafruit-circuitpython-cursorcontrol
40+
41+
To install system-wide (this may be required in some cases):
42+
43+
.. code-block:: shell
44+
45+
sudo pip3 install adafruit-circuitpython-cursorcontrol
46+
47+
To install in a virtual environment in your current project:
48+
49+
.. code-block:: shell
50+
51+
mkdir project-name && cd project-name
52+
python3 -m venv .env
53+
source .env/bin/activate
54+
pip3 install adafruit-circuitpython-cursorcontrol
55+
56+
Usage Example
57+
=============
58+
59+
See examples in examples/ folder.
60+
61+
Contributing
62+
============
63+
64+
Contributions are welcome! Please read our `Code of Conduct
65+
<https://github.com/adafruit/Adafruit_CircuitPython_CursorControl/blob/master/CODE_OF_CONDUCT.md>`_
66+
before contributing to help this project stay welcoming.
67+
68+
Building locally
69+
================
70+
71+
Zip release files
72+
-----------------
73+
74+
To build this library locally you'll need to install the
75+
`circuitpython-build-tools <https://github.com/adafruit/circuitpython-build-tools>`_ package.
76+
77+
.. code-block:: shell
78+
79+
python3 -m venv .env
80+
source .env/bin/activate
81+
pip install circuitpython-build-tools
82+
83+
Once installed, make sure you are in the virtual environment:
84+
85+
.. code-block:: shell
86+
87+
source .env/bin/activate
88+
89+
Then run the build:
90+
91+
.. code-block:: shell
92+
93+
circuitpython-build-bundles --filename_prefix adafruit-circuitpython-cursorcontrol --library_location .
94+
95+
Sphinx documentation
96+
-----------------------
97+
98+
Sphinx is used to build the documentation based on rST files and comments in the code. First,
99+
install dependencies (feel free to reuse the virtual environment from above):
100+
101+
.. code-block:: shell
102+
103+
python3 -m venv .env
104+
source .env/bin/activate
105+
pip install Sphinx sphinx-rtd-theme
106+
107+
Now, once you have the virtual environment activated:
108+
109+
.. code-block:: shell
110+
111+
cd docs
112+
sphinx-build -E -W -b html . _build/html
113+
114+
This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to
115+
view them. It will also (due to -W) error out on any warning like Travis will. This is a good way to
116+
locally verify it will pass.

‎adafruit_cursorcontrol/__init__.py

Whitespace-only changes.
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
`adafruit_cursorcontrol`
24+
================================================================================
25+
26+
Mouse cursor for interaction with CircuitPython UI elements.
27+
28+
29+
* Author(s): Brent Rubell
30+
31+
Implementation Notes
32+
--------------------
33+
34+
**Software and Dependencies:**
35+
36+
* Adafruit CircuitPython firmware for the supported boards:
37+
https://github.com/adafruit/circuitpython/releases
38+
39+
"""
40+
import displayio
41+
42+
__version__ = "0.0.0-auto.0"
43+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Cursor.git"
44+
45+
class Cursor:
46+
"""Mouse cursor interaction for CircuitPython.
47+
48+
:param ~displayio.Display display: CircuitPython display object.
49+
:param ~displayio.Group display_group: CircuitPython group object to append the cursor to.
50+
:param int cursor_speed: Speed of the cursor, in pixels.
51+
:param int scale: Scale amount for the cursor in both directions.
52+
:param bool is_hidden: Cursor is hidden on init.
53+
54+
Example for creating a cursor layer
55+
56+
.. code-block:: python
57+
58+
from adafruit_cursorcontrol import Cursor
59+
# Create the display
60+
display = board.DISPLAY
61+
62+
# Create the display context
63+
splash = displayio.Group(max_size=22)
64+
65+
# initialize the mouse cursor object
66+
mouse_cursor = Cursor(display, display_group=splash)
67+
"""
68+
# pylint: disable=too-many-arguments
69+
def __init__(self, display=None, display_group=None, is_hidden=False, cursor_speed=5, scale=1):
70+
self._display = display
71+
self._scale = scale
72+
self._speed = cursor_speed
73+
self._is_hidden = is_hidden
74+
self._display_grp = display_group
75+
self._disp_sz = display.height - 1, display.width - 1
76+
self.generate_cursor()
77+
78+
def __enter__(self):
79+
return self
80+
81+
def __exit__(self, exception_type, exception_value, traceback):
82+
self.deinit()
83+
84+
def deinit(self):
85+
"""deinitializes the cursor object."""
86+
self._is_deinited()
87+
self._scale = None
88+
self._display_grp.remove(self._cursor_grp)
89+
90+
def _is_deinited(self):
91+
"""checks cursor deinitialization"""
92+
if self._scale is None:
93+
raise ValueError("Cursor object has been deinitialized and can no longer "
94+
"be used. Create a new cursor object.")
95+
96+
@property
97+
def scale(self):
98+
"""Returns the cursor's scale amount as an integer."""
99+
return self._scale
100+
101+
@scale.setter
102+
def scale(self, scale_value):
103+
"""Scales the cursor by scale_value in both directions.
104+
:param int scale_value: Amount to scale the cursor by.
105+
"""
106+
self._is_deinited()
107+
if scale_value > 0:
108+
self._scale = scale_value
109+
self._cursor_grp.scale = scale_value
110+
111+
@property
112+
def speed(self):
113+
"""Returns the cursor's speed, in pixels."""
114+
return self._speed
115+
116+
@speed.setter
117+
def speed(self, speed):
118+
"""Sets the speed of the cursor.
119+
:param int speed: Cursor movement speed, in pixels.
120+
"""
121+
self._is_deinited()
122+
if speed > 0:
123+
self._speed = speed
124+
125+
@property
126+
def x(self):
127+
"""Returns the cursor's x-coordinate."""
128+
return self._cursor_grp.x
129+
130+
@x.setter
131+
def x(self, x_val):
132+
"""Sets the x-value of the cursor.
133+
:param int x_val: cursor x-position, in pixels.
134+
"""
135+
self._is_deinited()
136+
if x_val < 0 and not self._is_hidden:
137+
self._cursor_grp.x = self._cursor_grp.x
138+
elif x_val > self._disp_sz[1] and not self._is_hidden:
139+
self._cursor_grp.x = self._cursor_grp.x
140+
elif not self._is_hidden:
141+
self._cursor_grp.x = x_val
142+
143+
@property
144+
def y(self):
145+
"""Returns the cursor's y-coordinate."""
146+
return self._cursor_grp.y
147+
148+
@y.setter
149+
def y(self, y_val):
150+
"""Sets the y-value of the cursor.
151+
:param int y_val: cursor y-position, in pixels.
152+
"""
153+
self._is_deinited()
154+
if y_val < 0 and not self._is_hidden:
155+
self._cursor_grp.y = self._cursor_grp.y
156+
elif y_val > self._disp_sz[0] and not self._is_hidden:
157+
self._cursor_grp.y = self._cursor_grp.y
158+
elif not self._is_hidden:
159+
self._cursor_grp.y = y_val
160+
161+
@property
162+
def hide(self):
163+
"""Returns True if the cursor is hidden or visible on the display."""
164+
return self._is_hidden
165+
166+
@hide.setter
167+
def hide(self, is_hidden):
168+
self._is_deinited()
169+
if is_hidden:
170+
self._is_hidden = True
171+
self._display_grp.remove(self._cursor_grp)
172+
else:
173+
self._is_hidden = False
174+
self._display_grp.append(self._cursor_grp)
175+
176+
def generate_cursor(self):
177+
"""Generates a cursor icon"""
178+
self._is_deinited()
179+
self._cursor_grp = displayio.Group(max_size=1, scale=self._scale)
180+
self._cur_bmp = displayio.Bitmap(20, 20, 3)
181+
self._cur_palette = displayio.Palette(3)
182+
self._cur_palette.make_transparent(0)
183+
self._cur_palette[1] = 0xFFFFFF
184+
self._cur_palette[2] = 0x0000
185+
# left edge, outline
186+
for i in range(0, self._cur_bmp.height):
187+
self._cur_bmp[0, i] = 2
188+
# right diag outline, inside fill
189+
for j in range(1, 15):
190+
self._cur_bmp[j, j] = 2
191+
for i in range(j+1, self._cur_bmp.height - j):
192+
self._cur_bmp[j, i] = 1
193+
# bottom diag., outline
194+
for i in range(1, 5):
195+
self._cur_bmp[i, self._cur_bmp.height-i] = 2
196+
# bottom flat line, right side fill
197+
for i in range(5, 15):
198+
self._cur_bmp[i, 15] = 2
199+
self._cur_bmp[i-1, 14] = 1
200+
self._cur_bmp[i-2, 13] = 1
201+
self._cur_bmp[i-3, 12] = 1
202+
self._cur_bmp[i-4, 11] = 1
203+
self._cur_sprite = displayio.TileGrid(self._cur_bmp,
204+
pixel_shader=self._cur_palette)
205+
self._cursor_grp.append(self._cur_sprite)
206+
self._display_grp.append(self._cursor_grp)
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
35+
# PyBadge
36+
PYBADGE_BUTTON_LEFT = const(128)
37+
PYBADGE_BUTTON_UP = const(64)
38+
PYBADGE_BUTTON_DOWN = const(32)
39+
PYBADGE_BUTTON_RIGHT = const(16)
40+
# PyBadge & PyGamer
41+
PYBADGE_BUTTON_A = const(2)
42+
43+
JOY_X_CTR = 32767.5
44+
JOY_Y_CTR = 32767.5
45+
46+
class CursorManager:
47+
"""Simple interaction user interface interaction for Adafruit_CursorControl.
48+
49+
:param adafruit_cursorcontrol cursor: The cursor object we are using.
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("CursorManager object has been deinitialized and can no longer "
73+
"be used. Create a new CursorManager object.")
74+
75+
def _init_hardware(self):
76+
"""Initializes PyBadge or PyGamer hardware."""
77+
if hasattr(board, 'BUTTON_CLOCK') and not hasattr(board, 'JOYSTICK_X'):
78+
self._pad_btns = {'btn_left' : PYBADGE_BUTTON_LEFT,
79+
'btn_right' : PYBADGE_BUTTON_RIGHT,
80+
'btn_up' : PYBADGE_BUTTON_UP,
81+
'btn_down' : PYBADGE_BUTTON_DOWN,
82+
'btn_a' : PYBADGE_BUTTON_A}
83+
elif hasattr(board, 'JOYSTICK_X'):
84+
self._joystick_x = analogio.AnalogIn(board.JOYSTICK_X)
85+
self._joystick_y = analogio.AnalogIn(board.JOYSTICK_Y)
86+
self._pad_btns = {'btn_a' : PYBADGE_BUTTON_A}
87+
else:
88+
raise AttributeError('Board must have a D-Pad or Joystick for use with CursorManager!')
89+
self._pad = GamePadShift(digitalio.DigitalInOut(board.BUTTON_CLOCK),
90+
digitalio.DigitalInOut(board.BUTTON_OUT),
91+
digitalio.DigitalInOut(board.BUTTON_LATCH))
92+
93+
@property
94+
def is_clicked(self):
95+
"""Returns True if the cursor button was pressed
96+
during previous call to update()
97+
"""
98+
return self._is_clicked
99+
100+
def update(self):
101+
"""Updates the cursor object."""
102+
pressed = self._pad.get_pressed()
103+
self._check_cursor_movement(pressed)
104+
if self._is_clicked:
105+
self._is_clicked = False
106+
elif pressed & self._pad_btns['btn_a']:
107+
self._is_clicked = True
108+
109+
def _read_joystick_x(self, samples=3):
110+
"""Read the X analog joystick on the PyGamer.
111+
:param int samples: How many samples to read and average.
112+
"""
113+
reading = 0
114+
# pylint: disable=unused-variable
115+
if hasattr(board, 'JOYSTICK_X'):
116+
for sample in range(0, samples):
117+
reading += self._joystick_x.value
118+
reading /= samples
119+
reading -= JOY_X_CTR
120+
return reading
121+
122+
def _read_joystick_y(self, samples=3):
123+
"""Read the Y analog joystick on the PyGamer.
124+
:param int samples: How many samples to read and average.
125+
"""
126+
reading = 0
127+
# pylint: disable=unused-variable
128+
if hasattr(board, 'JOYSTICK_Y'):
129+
for sample in range(0, samples):
130+
reading += self._joystick_y.value
131+
reading /= samples
132+
reading -= JOY_Y_CTR
133+
return reading
134+
135+
def _check_cursor_movement(self, pressed=None):
136+
"""Checks the PyBadge D-Pad or the PyGamer's Joystick for movement.
137+
:param int pressed: 8-bit number with bits that correspond to buttons
138+
which have been pressed down since the last call to get_pressed().
139+
"""
140+
if hasattr(board, 'BUTTON_CLOCK') and not hasattr(board, 'JOYSTICK_X'):
141+
if pressed & self._pad_btns['btn_right']:
142+
self._cursor.x += self._cursor.speed
143+
elif pressed & self._pad_btns['btn_left']:
144+
self._cursor.x -= self._cursor.speed
145+
if pressed & self._pad_btns['btn_up']:
146+
self._cursor.y -= self._cursor.speed
147+
elif pressed & self._pad_btns['btn_down']:
148+
self._cursor.y += self._cursor.speed
149+
elif hasattr(board, 'JOYSTICK_X'):
150+
joy_x = self._read_joystick_x()
151+
joy_y = self._read_joystick_y()
152+
if joy_x > 700:
153+
self._cursor.x += self._cursor.speed
154+
elif joy_x < -700:
155+
self._cursor.x -= self._cursor.speed
156+
if joy_y > 700:
157+
self._cursor.y += self._cursor.speed
158+
elif joy_y < -700:
159+
self._cursor.y -= self._cursor.speed
160+
else:
161+
raise AttributeError('Board must have a D-Pad or Joystick for use with CursorManager!')

‎docs/_static/favicon.ico

4.31 KB
Binary file not shown.

‎docs/api.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
API
2+
====
3+
.. automodule:: adafruit_cursorcontrol
4+
:members:

‎docs/conf.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import os
4+
import sys
5+
sys.path.insert(0, os.path.abspath('..'))
6+
7+
# -- General configuration ------------------------------------------------
8+
9+
# Add any Sphinx extension module names here, as strings. They can be
10+
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
11+
# ones.
12+
extensions = [
13+
'sphinx.ext.autodoc',
14+
'sphinx.ext.intersphinx',
15+
'sphinx.ext.napoleon',
16+
'sphinx.ext.todo',
17+
]
18+
19+
# TODO: Please Read!
20+
# Uncomment the below if you use native CircuitPython modules such as
21+
# digitalio, micropython and busio. List the modules you use. Without it, the
22+
# autodoc module docs will fail to generate with a warning.
23+
autodoc_mock_imports = ["displayio"]
24+
25+
26+
intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)}
27+
28+
# Add any paths that contain templates here, relative to this directory.
29+
templates_path = ['_templates']
30+
31+
source_suffix = '.rst'
32+
33+
# The master toctree document.
34+
master_doc = 'index'
35+
36+
# General information about the project.
37+
project = u'Adafruit CursorControl Library'
38+
copyright = u'2019 Brent Rubell'
39+
author = u'Brent Rubell'
40+
41+
# The version info for the project you're documenting, acts as replacement for
42+
# |version| and |release|, also used in various other places throughout the
43+
# built documents.
44+
#
45+
# The short X.Y version.
46+
version = u'1.0'
47+
# The full version, including alpha/beta/rc tags.
48+
release = u'1.0'
49+
50+
# The language for content autogenerated by Sphinx. Refer to documentation
51+
# for a list of supported languages.
52+
#
53+
# This is also used if you do content translation via gettext catalogs.
54+
# Usually you set "language" from the command line for these cases.
55+
language = None
56+
57+
# List of patterns, relative to source directory, that match files and
58+
# directories to ignore when looking for source files.
59+
# This patterns also effect to html_static_path and html_extra_path
60+
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.env', 'CODE_OF_CONDUCT.md']
61+
62+
# The reST default role (used for this markup: `text`) to use for all
63+
# documents.
64+
#
65+
default_role = "any"
66+
67+
# If true, '()' will be appended to :func: etc. cross-reference text.
68+
#
69+
add_function_parentheses = True
70+
71+
# The name of the Pygments (syntax highlighting) style to use.
72+
pygments_style = 'sphinx'
73+
74+
# If true, `todo` and `todoList` produce output, else they produce nothing.
75+
todo_include_todos = False
76+
77+
# If this is True, todo emits a warning for each TODO entries. The default is False.
78+
todo_emit_warnings = True
79+
80+
napoleon_numpy_docstring = False
81+
82+
# -- Options for HTML output ----------------------------------------------
83+
84+
# The theme to use for HTML and HTML Help pages. See the documentation for
85+
# a list of builtin themes.
86+
#
87+
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
88+
89+
if not on_rtd: # only import and set the theme if we're building docs locally
90+
try:
91+
import sphinx_rtd_theme
92+
html_theme = 'sphinx_rtd_theme'
93+
html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.']
94+
except:
95+
html_theme = 'default'
96+
html_theme_path = ['.']
97+
else:
98+
html_theme_path = ['.']
99+
100+
# Add any paths that contain custom static files (such as style sheets) here,
101+
# relative to this directory. They are copied after the builtin static files,
102+
# so a file named "default.css" will overwrite the builtin "default.css".
103+
html_static_path = ['_static']
104+
105+
# The name of an image file (relative to this directory) to use as a favicon of
106+
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
107+
# pixels large.
108+
#
109+
html_favicon = '_static/favicon.ico'
110+
111+
# Output file base name for HTML help builder.
112+
htmlhelp_basename = 'AdafruitCursorcontrolLibrarydoc'
113+
114+
# -- Options for LaTeX output ---------------------------------------------
115+
116+
latex_elements = {
117+
# The paper size ('letterpaper' or 'a4paper').
118+
#
119+
# 'papersize': 'letterpaper',
120+
121+
# The font size ('10pt', '11pt' or '12pt').
122+
#
123+
# 'pointsize': '10pt',
124+
125+
# Additional stuff for the LaTeX preamble.
126+
#
127+
# 'preamble': '',
128+
129+
# Latex figure (float) alignment
130+
#
131+
# 'figure_align': 'htbp',
132+
}
133+
134+
# Grouping the document tree into LaTeX files. List of tuples
135+
# (source start file, target name, title,
136+
# author, documentclass [howto, manual, or own class]).
137+
latex_documents = [
138+
(master_doc, 'AdafruitCursorControlLibrary.tex', u'AdafruitCursorControl Library Documentation',
139+
author, 'manual'),
140+
]
141+
142+
# -- Options for manual page output ---------------------------------------
143+
144+
# One entry per manual page. List of tuples
145+
# (source start file, name, description, authors, manual section).
146+
man_pages = [
147+
(master_doc, 'AdafruitCursorControllibrary', u'Adafruit CursorControl Library Documentation',
148+
[author], 1)
149+
]
150+
151+
# -- Options for Texinfo output -------------------------------------------
152+
153+
# Grouping the document tree into Texinfo files. List of tuples
154+
# (source start file, target name, title, author,
155+
# dir menu entry, description, category)
156+
texinfo_documents = [
157+
(master_doc, 'AdafruitCursorControlLibrary', u'Adafruit CursorControl Library Documentation',
158+
author, 'AdafruitCursorControlLibrary', 'One line description of project.',
159+
'Miscellaneous'),
160+
]

‎docs/examples.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Simple test
2+
------------
3+
4+
Ensure your device works with this simple test.
5+
6+
.. literalinclude:: ../examples/cursorcontrol_simpletest.py
7+
:caption: examples/cursorcontrol_simpletest.py
8+
:linenos:

‎docs/index.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
.. include:: ../README.rst
2+
3+
Table of Contents
4+
=================
5+
6+
.. toctree::
7+
:maxdepth: 4
8+
:hidden:
9+
10+
self
11+
12+
.. toctree::
13+
:caption: Examples
14+
15+
examples
16+
17+
.. toctree::
18+
:caption: API Reference
19+
:maxdepth: 3
20+
21+
api
22+
23+
.. toctree::
24+
:caption: Tutorials
25+
26+
.. toctree::
27+
:caption: Related Products
28+
29+
.. toctree::
30+
:caption: Other Links
31+
32+
Download <https://github.com/adafruit/Adafruit_CircuitPython_CursorControl/releases/latest>
33+
CircuitPython Reference Documentation <https://circuitpython.readthedocs.io>
34+
CircuitPython Support Forum <https://forums.adafruit.com/viewforum.php?f=60>
35+
Discord Chat <https://adafru.it/discord>
36+
Adafruit Learning System <https://learn.adafruit.com>
37+
Adafruit Blog <https://blog.adafruit.com>
38+
Adafruit Store <https://www.adafruit.com>
39+
40+
Indices and tables
41+
==================
42+
43+
* :ref:`genindex`
44+
* :ref:`modindex`
45+
* :ref:`search`
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import time
2+
import board
3+
import displayio
4+
from adafruit_button import Button
5+
from adafruit_cursorcontrol.cursorcontrol import Cursor
6+
from adafruit_cursorcontrol.cursorcontrol_cursormanager import CursorManager
7+
from adafruit_display_text import label
8+
import terminalio
9+
10+
# Create the display
11+
display = board.DISPLAY
12+
13+
# Create the display context
14+
splash = displayio.Group(max_size=22)
15+
16+
# Use the built-in system font
17+
font = terminalio.FONT
18+
19+
##########################################################################
20+
# Make a background color fill
21+
22+
color_bitmap = displayio.Bitmap(display.width, display.height, 1)
23+
color_palette = displayio.Palette(1)
24+
color_palette[0] = 0x404040
25+
bg_sprite = displayio.TileGrid(color_bitmap,
26+
pixel_shader=color_palette,
27+
x=0, y=0)
28+
splash.append(bg_sprite)
29+
30+
##########################################################################
31+
32+
# Set up button/label properties
33+
BUTTON_WIDTH = 80
34+
BUTTON_HEIGHT = 40
35+
BUTTON_MARGIN = 20
36+
LBL_HEADER = [100, 20]
37+
LBL_TEXT = [120, 40]
38+
39+
# Resize buttons for small display (PyGamer)
40+
if display.width < 240:
41+
BUTTON_WIDTH = int(BUTTON_WIDTH / 2)
42+
BUTTON_HEIGHT = int(BUTTON_HEIGHT / 2)
43+
BUTTON_MARGIN = int(BUTTON_MARGIN / 2)
44+
LBL_HEADER[0] -= 75
45+
LBL_HEADER[1] -= 10
46+
LBL_TEXT[0] -= 70
47+
LBL_TEXT[1] += 55
48+
49+
# Create the buttons
50+
buttons = []
51+
52+
button_speed_inc = Button(x=BUTTON_MARGIN, y=BUTTON_MARGIN+BUTTON_HEIGHT,
53+
width=BUTTON_WIDTH, height=BUTTON_HEIGHT,
54+
label="Speed+", label_font=font)
55+
buttons.append(button_speed_inc)
56+
57+
button_speed_dec = Button(x=BUTTON_MARGIN, y=BUTTON_MARGIN*4+BUTTON_HEIGHT,
58+
width=BUTTON_WIDTH, height=BUTTON_HEIGHT,
59+
label="Speed-", label_font=font)
60+
buttons.append(button_speed_dec)
61+
62+
button_scale_pos = Button(x=BUTTON_MARGIN*3+2*BUTTON_WIDTH, y=BUTTON_MARGIN+BUTTON_HEIGHT,
63+
width=BUTTON_WIDTH, height=BUTTON_HEIGHT,
64+
label="Scale+", label_font=font, style=Button.SHADOWRECT)
65+
buttons.append(button_scale_pos)
66+
67+
button_scale_neg = Button(x=BUTTON_MARGIN*3+2*BUTTON_WIDTH, y=BUTTON_MARGIN*4+BUTTON_HEIGHT,
68+
width=BUTTON_WIDTH, height=BUTTON_HEIGHT,
69+
label="Scale-", label_font=font, style=Button.SHADOWRECT)
70+
buttons.append(button_scale_neg)
71+
72+
# Show the button
73+
for b in buttons:
74+
splash.append(b.group)
75+
76+
# Create a text label
77+
text_label = label.Label(font, text="CircuitPython Cursor!", color=0x00FF00,
78+
x = LBL_HEADER[0], y = LBL_HEADER[1])
79+
splash.append(text_label)
80+
81+
text_speed = label.Label(font, max_glyphs = 15, color=0x00FF00,
82+
x = LBL_TEXT[0], y = LBL_TEXT[1])
83+
splash.append(text_speed)
84+
85+
text_scale = label.Label(font, max_glyphs = 15, color=0x00FF00,
86+
x = LBL_TEXT[0], y = LBL_TEXT[1]+20)
87+
splash.append(text_scale)
88+
89+
# initialize the mouse cursor object
90+
mouse_cursor = Cursor(display, display_group=splash)
91+
92+
# initialize the cursormanager
93+
cursor = CursorManager(mouse_cursor)
94+
95+
# show displayio group
96+
display.show(splash)
97+
98+
prev_btn = None
99+
while True:
100+
cursor.update()
101+
if cursor.is_clicked is True:
102+
for i, b in enumerate(buttons):
103+
if b.contains((mouse_cursor.x, mouse_cursor.y)):
104+
b.selected=True
105+
print("Button %d pressed"%i)
106+
if i == 0: # Increase the cursor speed
107+
mouse_cursor.speed += 1
108+
elif i == 1: # Decrease the cursor speed
109+
mouse_cursor.speed -= 1
110+
if i == 2: # Increase the cursor scale
111+
mouse_cursor.scale += 1
112+
elif i == 3: # Decrease the cursor scale
113+
mouse_cursor.scale -= 1
114+
prev_btn = b
115+
elif prev_btn is not None:
116+
prev_btn.selected = False
117+
text_speed.text = 'Speed: {0}px'.format(mouse_cursor.speed)
118+
text_scale.text = 'Scale: {0}px'.format(mouse_cursor.scale)
119+
time.sleep(0.1)

‎examples/cursorcontrol_simpletest.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import time
2+
import board
3+
import displayio
4+
from adafruit_cursorcontrol.cursorcontrol import Cursor
5+
from adafruit_cursorcontrol.cursorcontrol_cursormanager import CursorManager
6+
7+
# Create the display
8+
display = board.DISPLAY
9+
10+
# Create the display context
11+
splash = displayio.Group(max_size=5)
12+
13+
# initialize the mouse cursor object
14+
mouse_cursor = Cursor(display, display_group=splash)
15+
16+
# initialize the cursormanager
17+
cursor = CursorManager(mouse_cursor)
18+
19+
# show displayio group
20+
display.show(splash)
21+
22+
while True:
23+
cursor.update()
24+
if cursor.is_clicked:
25+
if mouse_cursor.hide:
26+
mouse_cursor.hide = False
27+
else:
28+
mouse_cursor.hide = True
29+
time.sleep(0.01)

‎requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Adafruit-Blinka

‎setup.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""A setuptools based setup module.
2+
3+
See:
4+
https://packaging.python.org/en/latest/distributing.html
5+
https://github.com/pypa/sampleproject
6+
"""
7+
8+
from setuptools import setup, find_packages
9+
# To use a consistent encoding
10+
from codecs import open
11+
from os import path
12+
13+
here = path.abspath(path.dirname(__file__))
14+
15+
# Get the long description from the README file
16+
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
17+
long_description = f.read()
18+
19+
setup(
20+
name='adafruit-circuitpython-cursorcontrol',
21+
22+
use_scm_version=True,
23+
setup_requires=['setuptools_scm'],
24+
25+
description='Mouse cursor for interaction with CircuitPython UI elements.',
26+
long_description=long_description,
27+
long_description_content_type='text/x-rst',
28+
29+
# The project's main homepage.
30+
url='https://github.com/adafruit/Adafruit_CircuitPython_CursorControl',
31+
32+
# Author details
33+
author='Adafruit Industries',
34+
author_email='circuitpython@adafruit.com',
35+
36+
install_requires=[
37+
'Adafruit-Blinka',
38+
'no'
39+
],
40+
41+
# Choose your license
42+
license='MIT',
43+
44+
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
45+
classifiers=[
46+
'Development Status :: 3 - Alpha',
47+
'Intended Audience :: Developers',
48+
'Topic :: Software Development :: Libraries',
49+
'Topic :: System :: Hardware',
50+
'License :: OSI Approved :: MIT License',
51+
'Programming Language :: Python :: 3',
52+
'Programming Language :: Python :: 3.4',
53+
'Programming Language :: Python :: 3.5',
54+
],
55+
56+
# What does your project relate to?
57+
keywords='adafruit blinka circuitpython micropython cursorcontrol mouse, cursor, ui',
58+
59+
# You can just specify the packages manually here if your project is
60+
# simple. Or you can use find_packages().
61+
# TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER,
62+
# CHANGE `py_modules=['...']` TO `packages=['...']`
63+
py_modules=['adafruit_cursorcontrol'],
64+
)

0 commit comments

Comments
 (0)
Please sign in to comment.