Skip to content

Add Annotation widget #27

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 16 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions adafruit_displayio_layout/widgets/annotation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# SPDX-FileCopyrightText: 2021 Kevin Matocha
#
# SPDX-License-Identifier: MIT
"""

`annotation`
================================================================================
A widget for annotating other widgets or freeform positions.

* Author(s): Kevin Matocha

Implementation Notes
--------------------

**Hardware:**

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases

"""

# pylint: disable=too-many-arguments, too-many-locals, unused-argument

from terminalio import FONT
from adafruit_display_text import bitmap_label
from adafruit_display_shapes.line import Line
from adafruit_displayio_layout.widgets.widget import Widget


class Annotation(Widget):
"""A widget to be used to annotate other widgets with text and lines, but can also
be used freeform by using ``(x,y)`` parameter.

:param int x: x-direction pixel position for the end of the annotation line for
freeform positioning, ``(x,y)`` will be ignored if a ``widget`` and ``anchor_point`` and/or
``anchored_position`` are provided.
:param int y: y-direction pixel position for the end of the annotation line for
freeform positioning.

:param Widget widget: the widget to be annotated, all dimensions are relative to
this widget. The annotation line position will be defined by either
the ``anchor_point`` (in relative dimensions of the size of the widget)
or the ``anchored_position`` (in raw pixel dimensions relative to the origin
of the widget).

:param str text: text to be displayed in the annotation.
:param Font font: font to be used for the text.

:param anchor_point: starting point for the annotation line, where ``anchor_point`` is an
(A,B) tuple in relative units of the size of the widget,
for example (0.0, 0.0) is the upper left corner, and (1.0, 1.0) is the lower
right corner of the widget. If ``anchor_point`` is `None`, then ``anchored_position``
is used to set the annotation line starting point, in widget size relative units
(default is (0.0, 0.0)).
:type anchor_point: Tuple[float, float]

:param anchored_position: pixel position starting point for the annotation line
where ``anchored_position`` is an (x,y) tuple in pixel units relative to the
upper left corner of the widget, in pixel units (default is None).
:type anchored_position: Tuple[int, int]

:param position_offset: Used to *nudge* the line position to where you want, this
is an (x,y) pixel offset added to the annotation line starting
point, either set by ``anchor_point`` or ``anchored_position`` (in pixel units).
:type position_offset: Tuple[int, int]

:param int delta_x: the pixel x-offset for the second end of the line where the text
will reside, in pixel units (default: -15).
:param int delta_y: the pixel y-offset for the second end of the line where the text
will reside, in pixel units (default: -10).

:param int stroke: the annotation line width (in pixels). [NOT currently implemented]

:param int line_color: the color of the annotation line (default: 0xFFFFFF).
:param int text_color: the color of the text, if set to `None` color will be
set to ``line_color`` (default: same as ``line_color``).

:param text_offset: a (x,y) pixel offset to adjust text position relative
to annotation line, in pixel units (default: (0,-1)).
:type text_offset: Tuple[int, int]

:param Boolean text_under: set `True` for text to be placed below the
annotation line (default: False).

.. figure:: annotation_example.png
:scale: 125 %
:align: center
:alt: Example of the annotation widget.

Example of the annotation widget showing two widget
annotations (using ``widget`` and ``anchor_point`` input parameters) and a
freeform annotation (using ``x`` and ``y`` input parameters).

File location: *examples/displayio_layout_annotation_simpletest.py*
"""

def __init__(
self,
x=None,
y=None,
text=None,
font=FONT,
delta_x=-15,
delta_y=-10,
widget=None,
anchor_point=(0.0, 0.0),
anchored_position=None,
position_offset=(0, 0),
stroke=3, # Not currently implemented in adafruit_display_shapes/line.py
line_color=0xFFFFFF,
text_color=None,
text_offset=(0, -1),
text_under=False,
):

if widget:
if (x is not None) or (y is not None):
print(
"Note: Overriding (x,y) values with widget, anchor_point"
" and/or anchored_position"
)
widget_width = widget.bounding_box[2]
widget_height = widget.bounding_box[3]
if anchor_point is not None:
line_x0 = (
widget.x
+ round(widget_width * anchor_point[0])
+ position_offset[0]
)
line_y0 = (
widget.y
+ round(widget_height * anchor_point[1])
+ position_offset[1]
)
elif anchored_position is not None:
line_x0 = widget.x + anchored_position[0] + position_offset[0]
line_y0 = widget.y + anchored_position[1] + position_offset[1]
else:
raise ValueError("Must supply either anchor_point or anchored_position")
elif (x is not None) and (y is not None):
line_x0 = x
line_y0 = y
else:
raise ValueError(
"Must supply either (x,y) or widget and anchor_point or anchored_position"
)

line_x1 = line_x0 + delta_x
line_y1 = line_y0 + delta_y

text_anchor_point = (0.0, 1.0) # default: set text anchor to left corner
underline_x_multiplier = 1

if delta_x < 0: # line is heading to the left, set text anchor to right corner
text_anchor_point = (1.0, 1.0)
underline_x_multiplier = -1

if (
text_under
): # if text is under the line, set to text_anchor_point to upper edge
text_anchor_point = (text_anchor_point[0], 0.0)

if text_color is None:
text_color = line_color

self._label = bitmap_label.Label(
text=text,
font=font,
color=text_color,
anchor_point=text_anchor_point,
anchored_position=(line_x1 + text_offset[0], line_y1 + text_offset[1]),
)

label_width = self._label.bounding_box[2]
line_x2 = line_x1 + label_width * underline_x_multiplier + text_offset[0]
# lengthen the line if the text is offset
line_y2 = line_y1

self._line0 = Line(line_x0, line_y0, line_x1, line_y1, color=line_color)
self._line1 = Line(line_x1, line_y1, line_x2, line_y2, color=line_color)

super().__init__(max_size=3)
# Group elements:
# 0. Line0 - from (x,y) to (x+delta_x, y+delta_y)
# 1. Line1 - horizontal line for text
# 2. Label

self.append(self._line0)
self.append(self._line1)
self.append(self._label)
Binary file added docs/annotation_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/annotation_example.png.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Kevin Matocha

SPDX-License-Identifier: MIT
4 changes: 4 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
:members:
:member-order: bysource

.. automodule:: adafruit_displayio_layout.widgets.annotation
:members:
:member-order: bysource

.. automodule:: adafruit_displayio_layout.widgets.icon_animated
:members:
:member-order: bysource
9 changes: 9 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ Create multiple sliding switch with various sizes and orientations.
:caption: examples/displayio_layout_switch_multiple.py
:linenos:

Annotation example
------------------

Displays annotations, examples relative to SwitchRound widget or as freeform.

.. literalinclude:: ../examples/displayio_layout_annotation_simpletest.py
:caption: examples/displayio_layout_annotation_simpletest.py
:linenos:

Dial simple test
----------------

Expand Down
81 changes: 81 additions & 0 deletions examples/displayio_layout_annotation_simpletest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# SPDX-FileCopyrightText: 2021 Kevin Matocha
#
# SPDX-License-Identifier: MIT
"""
Example of the Annotation widget to annotate a Switch widget or
for freeform annotation.
"""

import time
import board
import displayio
import adafruit_touchscreen
from adafruit_displayio_layout.widgets.switch_round import SwitchRound as Switch
from adafruit_displayio_layout.widgets.annotation import Annotation

display = board.DISPLAY

ts = adafruit_touchscreen.Touchscreen(
board.TOUCH_XL,
board.TOUCH_XR,
board.TOUCH_YD,
board.TOUCH_YU,
calibration=((5200, 59000), (5800, 57000)),
size=(display.width, display.height),
)

# Create the switch widget
my_switch = Switch(190, 50)

# Create several annotations

# This annotation is positioned relative to the switch widget, with default values.
switch_annotation = Annotation(
widget=my_switch, # positions are relative to the switch
text="Widget Annotation: Switch",
)

# This annotation is positioned relative to the switch widget, with the line
# going in the downard direction and anchored at the middle bottom of the switch.
# The position is "nudged" downward using ``position_offset`` to create a 1 pixel
# gap between the end of the line and the switch.
# The text is positioned under the line by setting ``text_under`` to True.
switch_annotation_under = Annotation(
widget=my_switch, # positions are relative to the switch
text="Annotation with: text_under = True",
delta_x=-10,
delta_y=15, # line will go in downward direction (positive y)
anchor_point=(0.5, 1.0), # middle, bottom of switch
position_offset=(0, 1), # nudge downward by one pixel
text_under=True,
)

# This is a freeform annotation that is positioned using (x,y) values at the bottom, right
# corner of the display (display.width, display.height).
# The line direction is
freeform_annotation = Annotation(
x=display.width, # uses freeform (x,y) position
y=display.height,
text="Freeform annotation (display.width, height)",
)

my_group = displayio.Group(max_size=4)
my_group.append(my_switch)
my_group.append(switch_annotation)
my_group.append(switch_annotation_under)
my_group.append(freeform_annotation)

# Add my_group to the display
display.show(my_group)

# Start the main loop
while True:

p = ts.touch_point # get any touches on the screen

if p: # Check each switch if the touch point is within the switch touch area
# If touched, then flip the switch with .selected
if my_switch.contains(p):
my_switch.selected(p)

time.sleep(0.05) # touch response on PyPortal is more accurate with a small delay