|
| 1 | +# SPDX-FileCopyrightText: 2021 Kevin Matocha |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | +""" |
| 5 | +
|
| 6 | +`annotation` |
| 7 | +================================================================================ |
| 8 | +A widget for annotating other widgets or freeform positions. |
| 9 | +
|
| 10 | +* Author(s): Kevin Matocha |
| 11 | +
|
| 12 | +Implementation Notes |
| 13 | +-------------------- |
| 14 | +
|
| 15 | +**Hardware:** |
| 16 | +
|
| 17 | +**Software and Dependencies:** |
| 18 | +
|
| 19 | +* Adafruit CircuitPython firmware for the supported boards: |
| 20 | + https://github.com/adafruit/circuitpython/releases |
| 21 | +
|
| 22 | +""" |
| 23 | + |
| 24 | +# pylint: disable=too-many-arguments, too-many-locals, unused-argument |
| 25 | + |
| 26 | +from terminalio import FONT |
| 27 | +from adafruit_display_text import bitmap_label |
| 28 | +from adafruit_display_shapes.line import Line |
| 29 | +from adafruit_displayio_layout.widgets.widget import Widget |
| 30 | + |
| 31 | + |
| 32 | +class Annotation(Widget): |
| 33 | + """A widget to be used to annotate other widgets with text and lines, but can also |
| 34 | + be used freeform by using ``(x,y)`` parameter. |
| 35 | +
|
| 36 | + :param int x: x-direction pixel position for the end of the annotation line for |
| 37 | + freeform positioning, ``(x,y)`` will be ignored if a ``widget`` and ``anchor_point`` and/or |
| 38 | + ``anchored_position`` are provided. |
| 39 | + :param int y: y-direction pixel position for the end of the annotation line for |
| 40 | + freeform positioning. |
| 41 | +
|
| 42 | + :param Widget widget: the widget to be annotated, all dimensions are relative to |
| 43 | + this widget. The annotation line position will be defined by either |
| 44 | + the ``anchor_point`` (in relative dimensions of the size of the widget) |
| 45 | + or the ``anchored_position`` (in raw pixel dimensions relative to the origin |
| 46 | + of the widget). |
| 47 | +
|
| 48 | + :param str text: text to be displayed in the annotation. |
| 49 | + :param Font font: font to be used for the text. |
| 50 | +
|
| 51 | + :param anchor_point: starting point for the annotation line, where ``anchor_point`` is an |
| 52 | + (A,B) tuple in relative units of the size of the widget, |
| 53 | + for example (0.0, 0.0) is the upper left corner, and (1.0, 1.0) is the lower |
| 54 | + right corner of the widget. If ``anchor_point`` is `None`, then ``anchored_position`` |
| 55 | + is used to set the annotation line starting point, in widget size relative units |
| 56 | + (default is (0.0, 0.0)). |
| 57 | + :type anchor_point: Tuple[float, float] |
| 58 | +
|
| 59 | + :param anchored_position: pixel position starting point for the annotation line |
| 60 | + where ``anchored_position`` is an (x,y) tuple in pixel units relative to the |
| 61 | + upper left corner of the widget, in pixel units (default is None). |
| 62 | + :type anchored_position: Tuple[int, int] |
| 63 | +
|
| 64 | + :param position_offset: Used to *nudge* the line position to where you want, this |
| 65 | + is an (x,y) pixel offset added to the annotation line starting |
| 66 | + point, either set by ``anchor_point`` or ``anchored_position`` (in pixel units). |
| 67 | + :type position_offset: Tuple[int, int] |
| 68 | +
|
| 69 | + :param int delta_x: the pixel x-offset for the second end of the line where the text |
| 70 | + will reside, in pixel units (default: -15). |
| 71 | + :param int delta_y: the pixel y-offset for the second end of the line where the text |
| 72 | + will reside, in pixel units (default: -10). |
| 73 | +
|
| 74 | + :param int stroke: the annotation line width (in pixels). [NOT currently implemented] |
| 75 | +
|
| 76 | + :param int line_color: the color of the annotation line (default: 0xFFFFFF). |
| 77 | + :param int text_color: the color of the text, if set to `None` color will be |
| 78 | + set to ``line_color`` (default: same as ``line_color``). |
| 79 | +
|
| 80 | + :param text_offset: a (x,y) pixel offset to adjust text position relative |
| 81 | + to annotation line, in pixel units (default: (0,-1)). |
| 82 | + :type text_offset: Tuple[int, int] |
| 83 | +
|
| 84 | + :param Boolean text_under: set `True` for text to be placed below the |
| 85 | + annotation line (default: False). |
| 86 | +
|
| 87 | + .. figure:: annotation_example.png |
| 88 | + :scale: 125 % |
| 89 | + :align: center |
| 90 | + :alt: Example of the annotation widget. |
| 91 | +
|
| 92 | + Example of the annotation widget showing two widget |
| 93 | + annotations (using ``widget`` and ``anchor_point`` input parameters) and a |
| 94 | + freeform annotation (using ``x`` and ``y`` input parameters). |
| 95 | +
|
| 96 | + File location: *examples/displayio_layout_annotation_simpletest.py* |
| 97 | + """ |
| 98 | + |
| 99 | + def __init__( |
| 100 | + self, |
| 101 | + x=None, |
| 102 | + y=None, |
| 103 | + text=None, |
| 104 | + font=FONT, |
| 105 | + delta_x=-15, |
| 106 | + delta_y=-10, |
| 107 | + widget=None, |
| 108 | + anchor_point=(0.0, 0.0), |
| 109 | + anchored_position=None, |
| 110 | + position_offset=(0, 0), |
| 111 | + stroke=3, # Not currently implemented in adafruit_display_shapes/line.py |
| 112 | + line_color=0xFFFFFF, |
| 113 | + text_color=None, |
| 114 | + text_offset=(0, -1), |
| 115 | + text_under=False, |
| 116 | + ): |
| 117 | + |
| 118 | + if widget: |
| 119 | + if (x is not None) or (y is not None): |
| 120 | + print( |
| 121 | + "Note: Overriding (x,y) values with widget, anchor_point" |
| 122 | + " and/or anchored_position" |
| 123 | + ) |
| 124 | + widget_width = widget.bounding_box[2] |
| 125 | + widget_height = widget.bounding_box[3] |
| 126 | + if anchor_point is not None: |
| 127 | + line_x0 = ( |
| 128 | + widget.x |
| 129 | + + round(widget_width * anchor_point[0]) |
| 130 | + + position_offset[0] |
| 131 | + ) |
| 132 | + line_y0 = ( |
| 133 | + widget.y |
| 134 | + + round(widget_height * anchor_point[1]) |
| 135 | + + position_offset[1] |
| 136 | + ) |
| 137 | + elif anchored_position is not None: |
| 138 | + line_x0 = widget.x + anchored_position[0] + position_offset[0] |
| 139 | + line_y0 = widget.y + anchored_position[1] + position_offset[1] |
| 140 | + else: |
| 141 | + raise ValueError("Must supply either anchor_point or anchored_position") |
| 142 | + elif (x is not None) and (y is not None): |
| 143 | + line_x0 = x |
| 144 | + line_y0 = y |
| 145 | + else: |
| 146 | + raise ValueError( |
| 147 | + "Must supply either (x,y) or widget and anchor_point or anchored_position" |
| 148 | + ) |
| 149 | + |
| 150 | + line_x1 = line_x0 + delta_x |
| 151 | + line_y1 = line_y0 + delta_y |
| 152 | + |
| 153 | + text_anchor_point = (0.0, 1.0) # default: set text anchor to left corner |
| 154 | + underline_x_multiplier = 1 |
| 155 | + |
| 156 | + if delta_x < 0: # line is heading to the left, set text anchor to right corner |
| 157 | + text_anchor_point = (1.0, 1.0) |
| 158 | + underline_x_multiplier = -1 |
| 159 | + |
| 160 | + if ( |
| 161 | + text_under |
| 162 | + ): # if text is under the line, set to text_anchor_point to upper edge |
| 163 | + text_anchor_point = (text_anchor_point[0], 0.0) |
| 164 | + |
| 165 | + if text_color is None: |
| 166 | + text_color = line_color |
| 167 | + |
| 168 | + self._label = bitmap_label.Label( |
| 169 | + text=text, |
| 170 | + font=font, |
| 171 | + color=text_color, |
| 172 | + anchor_point=text_anchor_point, |
| 173 | + anchored_position=(line_x1 + text_offset[0], line_y1 + text_offset[1]), |
| 174 | + ) |
| 175 | + |
| 176 | + label_width = self._label.bounding_box[2] |
| 177 | + line_x2 = line_x1 + label_width * underline_x_multiplier + text_offset[0] |
| 178 | + # lengthen the line if the text is offset |
| 179 | + line_y2 = line_y1 |
| 180 | + |
| 181 | + self._line0 = Line(line_x0, line_y0, line_x1, line_y1, color=line_color) |
| 182 | + self._line1 = Line(line_x1, line_y1, line_x2, line_y2, color=line_color) |
| 183 | + |
| 184 | + super().__init__(max_size=3) |
| 185 | + # Group elements: |
| 186 | + # 0. Line0 - from (x,y) to (x+delta_x, y+delta_y) |
| 187 | + # 1. Line1 - horizontal line for text |
| 188 | + # 2. Label |
| 189 | + |
| 190 | + self.append(self._line0) |
| 191 | + self.append(self._line1) |
| 192 | + self.append(self._label) |
0 commit comments