Skip to content

Commit 25c4dee

Browse files
Add cross-reference links to adafruit_displayio_layout
Extract the guide out into a new file - overview.rst
1 parent 6aacbea commit 25c4dee

File tree

6 files changed

+406
-360
lines changed

6 files changed

+406
-360
lines changed

displayio_switchround.py

Lines changed: 53 additions & 358 deletions
Large diffs are not rendered by default.

docs/api.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,3 @@
88
:members:
99
:member-order: bysource
1010
:inherited-members:
11-
12-
.. inheritance-diagram:: adafruit_displayio_layout.widgets.switch_round

docs/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
intersphinx_mapping = {
3636
"python": ("https://docs.python.org/3.4", None),
3737
"CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None),
38+
"Layout": (
39+
"https://circuitpython.readthedocs.io/projects/displayio-layout/en/latest/",
40+
None,
41+
),
3842
}
3943

4044
# Show the docstring from both the class and its __init__() method.

docs/index.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ Table of Contents
1515

1616
examples
1717

18+
.. toctree::
19+
:caption: Overview
20+
21+
overview
22+
1823
.. toctree::
1924
:caption: API Reference
2025
:maxdepth: 3

docs/overview.rst

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
.. _overview:
2+
3+
Overview of SwitchRound
4+
=======================
5+
6+
Quickstart: Importing and using SwitchRound
7+
-------------------------------------------
8+
9+
Here is one way of importing the `SwitchRound` class so you can use it as
10+
the name :data:`Switch`:
11+
12+
.. code-block:: python
13+
14+
from displayio_switchround import SwitchRound as Switch
15+
16+
Now you can create a switch at pixel position x=20, y=30 using:
17+
18+
.. code-block:: python
19+
20+
my_switch = Switch(20, 30) # create the switch at x=20, y=30
21+
22+
Once you setup your display, you can now add :data:`my_switch` to your display using:
23+
24+
.. code-block:: python
25+
26+
display.show(my_switch) # add the group to the display
27+
28+
If you want to have multiple display elements, you can create a group and then
29+
append the switch and the other elements to the group. Then, you can add the full
30+
group to the display as in this example:
31+
32+
.. code-block:: python
33+
34+
my_switch = Switch(20, 30) # create the switch at x=20, y=30
35+
my_group = displayio.Group(max_size = 10) # make a group that can hold 10 items
36+
my_group.append(my_switch) # Add my_switch to the group
37+
38+
#
39+
# Append other display elements to the group
40+
#
41+
42+
display.show(my_group) # add the group to the display
43+
44+
For a full example, including how to respond to screen touches, check out the
45+
following examples in the `Adafruit_CircuitPython_DisplayIO_Layout
46+
<https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout>`_ library:
47+
48+
- `examples/displayio_layout_switch_simpletest.py
49+
<https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout/blob/main/examples/displayio_layout_switch_simpletest.py>`_
50+
- `examples/displayio_layout_switch_multiple.py
51+
<https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout/blob/main/examples/displayio_layout_switch_multiple.py>`_
52+
53+
Summary: SwitchRound Features and input variables
54+
-------------------------------------------------
55+
56+
The `SwitchRound` widget has numerous options for controlling its position, visible appearance,
57+
orientation, animation speed and value through a collection of input variables:
58+
59+
- **position**:
60+
:attr:`~displayio_switchround.SwitchRound.x` and
61+
:attr:`~displayio_switchround.SwitchRound.y` or
62+
:attr:`~displayio_switchround.SwitchRound.anchor_point` and
63+
:attr:`~displayio_switchround.SwitchRound.anchored_position`
64+
65+
66+
- **size**:
67+
:attr:`~displayio_switchround.SwitchRound.width` and
68+
:attr:`~displayio_switchround.SwitchRound.height`
69+
70+
It is recommended to leave :data:`width = None` to use the preferred aspect
71+
ratio.
72+
73+
- **orientation and movement direction (on vs. off)**:
74+
:attr:`~displayio_switchround.SwitchRound.horizontal` and
75+
:attr:`~displayio_switchround.SwitchRound.flip`
76+
77+
- **switch color**:
78+
:attr:`~displayio_switchround.SwitchRound.fill_color_off`,
79+
:attr:`~displayio_switchround.SwitchRound.fill_color_on`,
80+
:attr:`~displayio_switchround.SwitchRound.outline_color_off` and
81+
:attr:`~displayio_switchround.SwitchRound.outline_color_on`
82+
83+
- **background color**:
84+
:attr:`~displayio_switchround.SwitchRound.background_color_off`,
85+
:attr:`~displayio_switchround.SwitchRound.background_color_on`,
86+
:attr:`~displayio_switchround.SwitchRound.background_outline_color_off` and
87+
:attr:`~displayio_switchround.SwitchRound.background_outline_color_on`
88+
89+
- **linewidths**:
90+
:attr:`~displayio_switchround.SwitchRound.switch_stroke` and
91+
:attr:`~displayio_switchround.SwitchRound.text_stroke`
92+
93+
- **0/1 display**:
94+
:attr:`~displayio_switchround.SwitchRound.display_button_text`
95+
96+
Set to `True` if you want the 0/1 shapes
97+
to show on the switch
98+
99+
- **animation**:
100+
:attr:`~displayio_switchround.SwitchRound.animation_time`
101+
102+
Set the duration (in seconds) it will take to transition the switch, use
103+
:data:`0` if you want it to snap into position immediately. The default value
104+
of :data:`0.2` seconds is a good starting point, and larger values for bigger
105+
switches.
106+
107+
- **value**:
108+
:attr:`~displayio_switchround.SwitchRound.value`
109+
110+
Set to the initial value (`True` or `False`)
111+
112+
- **touch boundaries**:
113+
:attr:`~displayio_switchround.SwitchRound.touch_padding`
114+
115+
This defines the number of additional pixels surrounding the switch that should
116+
respond to a touch. (Note: The :attr:`touch_padding` variable updates the
117+
:attr:`touch_boundary` Control class variable. The definition of the
118+
:attr:`touch_boundary` is used to determine the region on the Widget that returns
119+
`True` in the :meth:`~displayio_switchround.SwitchRound.contains` method.)
120+
121+
Description of features
122+
-----------------------
123+
124+
The `SwitchRound` widget is a sliding switch that changes state whenever it is touched.
125+
The color gradually changes from the off-state color scheme to the on-state color
126+
scheme as the switch transfers from off to the on position. The switch has an optional
127+
display of "0" and "1" on the sliding switch. The switch can be oriented using the
128+
:attr:`~displayio_switchround.SwitchRound.horizontal` input variable, and the sliding
129+
direction can be changed using the :attr:`~displayio_switchround.SwitchRound.flip`
130+
input variable.
131+
132+
Regarding switch sizing, it is recommended to set the height dimension but to leave the
133+
:data:`width = None`. Setting :data:`width = None` will allow the width to resize to
134+
maintain a recommended aspect ratio of width/height. Alternately, the switch can be
135+
resized using the :meth:`~displayio_switchround.SwitchRound.resize` method, and it will
136+
adjust the width and height to the maximum size that will fit inside the requested
137+
width and height dimensions, while keeping the preferred aspect ratio. To make the
138+
switch easier to be selected, additional padding around the switch can be defined using
139+
the :attr:`~displayio_switchround.SwitchRound.touch_padding` input variable to increase
140+
the touch-responsive area. The duration of animation between on/off can be set using
141+
the :attr:`~displayio_switchround.SwitchRound.animation_time` input variable.
142+
143+
Internal details: How the SwitchRound widget works
144+
--------------------------------------------------
145+
146+
The `SwitchRound` widget is a graphical element that responds to touch elements to
147+
provide sliding switch on/off behavior. Whenever touched, the switch toggles to its
148+
alternate value. The following sections describe the construction of the `SwitchRound`
149+
widget, in the hopes that it will serve as a first example of the key properties and
150+
responses for widgets.
151+
152+
.. inheritance-diagram:: adafruit_displayio_layout.widgets.switch_round
153+
154+
|
155+
156+
The `SwitchRound` widget inherits from two classes, it is a subclass of
157+
:class:`~adafruit_displayio_layout.widgets.widget.Widget`, which itself is a subclass
158+
of `displayio.Group`, and a subclass of
159+
:class:`~adafruit_displayio_layout.widgets.control.Control`. The
160+
:class:`~adafruit_displayio_layout.widgets.widget.Widget` class helps define the
161+
positioning and sizing of the switch, while th
162+
:class:`~adafruit_displayio_layout.widgets.control.Control` class helps define the
163+
touch-response behavior.
164+
165+
The following sections describe the structure and inner workings of `SwitchRound`.
166+
167+
Group structure: Display elements that make up SwitchRound
168+
----------------------------------------------------------
169+
170+
The :class:`~adafruit_displayio_layout.widgets.widget.Widget`
171+
class is a subclass of `displayio.Group`, thus we can append graphical
172+
elements to the Widget for displaying on the screen. The switch consists of the
173+
following graphical elements:
174+
175+
0. switch_roundrect: The switch background
176+
1. switch_circle: The switch button that slides back and forth
177+
2. text_0 [Optional]: The "0" circle shape on the switch button
178+
3. text_1 [Optional]: The "1" rectangle shape on the switch button
179+
180+
The optional text items can be displayed or hidden using the
181+
:attr:`~displayio_switchround.SwitchRound.display_button_text` input variable.
182+
183+
Coordinate systems and use of anchor_point and anchored_position
184+
----------------------------------------------------------------
185+
186+
See the :class:`~adafruit_displayio_layout.widgets.widget.Widget` class definition for
187+
clarification on the methods for positioning the switch, including the difference in
188+
the display coordinate system and the Widget's local coordinate system.
189+
190+
The Widget construction sequence
191+
--------------------------------
192+
193+
Here is the set of steps used to define this sliding switch widget.
194+
195+
1. Initialize the stationary display items
196+
2. Initialize the moving display elements
197+
3. Store initial position of the moving display elements
198+
4. Define "keyframes" to determine the translation vector
199+
5. Define the :meth:`SwitchRound._draw_position` method between 0.0 to 1.0 (and
200+
slightly beyond)
201+
6. Select the motion "easing" function
202+
7. **Extra**. Go check out the :meth:`SwitchRound._animate_switch` method
203+
204+
First, the stationary background rounded rectangle (RoundRect is created). Second, the
205+
moving display elements are created, the circle for the switch, the circle for the text
206+
"0" and the rectangle for the text "1". Note that either the "0" or "1" is set as
207+
hidden, depending upon the switch value. Third, we store away the initial position of
208+
the three moving elements, these initial values will be used in the functions that move
209+
these display elements. Next, we define the motion of the moving element, by setting
210+
the :data:`self._x_motion` and :data:`self._y_motion` values that depending upon the
211+
:attr:`~SwitchRound.horizontal` and :attr:`~SwitchRound.flip` variables. These motion
212+
variables set the two "keyframes" for the moving elements, basically the endpoints of
213+
the switch motion. (Note: other widgets may need an :data:`_angle_motion` variable if
214+
they require some form of rotation.) Next, we define the
215+
:meth:`SwitchRound._draw_function` method. This method takes an input between 0.0 and
216+
1.0 and adjusts the position relative to the motion variables, where 0.0 is the initial
217+
position and 1.0 represents the final position (as defined by the :data:`_x_motion` and
218+
:data:`_y_motion` values). In the case of the sliding switch, we also use this
219+
:attr:`SwitchRound.position` value (0.0 to 1.0) to gradually grade the color of the
220+
components between their "on" and "off" colors.
221+
222+
Making it move
223+
--------------
224+
225+
Everything above has set the ground rules for motion, but doesn't cause it to move.
226+
However, you have set almost all the pieces in place to respond to requests to change
227+
the position. All that is left is the **Extra** method that performs the animation,
228+
called :meth:`SwitchRound._animate_switch`. The :meth:`SwitchRound._animate_switch`
229+
method is triggered by a touch event through the
230+
:meth:`~adafruit_displayio_layout.widgets.control.Control.selected` Control class
231+
method. Once triggered, this method
232+
checks how much time has elapsed. Based on the elapsed time and the
233+
:attr:`SwitchRound.animation_time` input variable, the
234+
:meth:`SwitchRound._animate_switch` method calculates the :attr:`SwitchRound.position`
235+
where the switch should be. Then, it takes this :attr:`SwitchRound.position` to call
236+
the :meth:`SwitchRound._draw_position` method that will update the display elements
237+
based on the requested position.
238+
239+
But there's even one more trick to the animation. The
240+
:meth:`SwitchRound._animate_switch` calculates the target position based on a linear
241+
relationship between the time and the position. However, to give the animation a better
242+
"feel", it is desirable to tweak the motion function depending upon how this widget
243+
should behave or what suits your fancy. To do this we can use an *"easing"* function.
244+
In short, this adjusts the constant speed (linear) movement to a variable speed during
245+
the movement. Said another way, it changes the position versus time function according
246+
to a specific waveform equation. There are a lot of different "easing" functions that
247+
folks have used or you can make up your own. Some common easing functions are provided
248+
in the :mod:`adafruit_displayio_layout.widgets.easing` module. You can change the
249+
easing function based on changing which function is imported at the top of this file.
250+
You can see where the position is tweaked by the easing function in the line in the
251+
:meth:`SwitchRound._animate_switch` method:
252+
253+
.. code-block:: python
254+
255+
self._draw_position(easing(position)) # update the switch position
256+
257+
Go play around with the different easing functions and observe how the motion
258+
behavior changes. You can use these functions in multiple dimensions to get all
259+
varieties of behavior that you can take advantage of. The website
260+
`easings.net <https://easings.net>`_ can help you
261+
visualize some of the behavior of the easing functions.
262+
263+
.. note:: Some of the "springy" easing functions require position values
264+
slightly below 0.0 and slightly above 1.0, so if you want to use these, be sure
265+
to check that your :meth:`_draw_position` method behaves itself for that range
266+
of position inputs.
267+
268+
Orientation and a peculiarity of width and height definitions for SwitchRound
269+
-----------------------------------------------------------------------------
270+
271+
In setting the switch sizing, use height and width to set the narrow and wide dimension
272+
of the switch. To try and reduce confusion, the orientation is modified after the
273+
height and width are selected. That is, if the switch is set to vertical, the height
274+
and still mean the "narrow" and the width will still mean the dimensions
275+
in the direction of the sliding.
276+
277+
If you need the switch to fit within a specific bounding box, it's preferred to use
278+
the :meth:`~displayio_switchround.SwitchRound.resize` function. This will put the switch (in whatever
279+
orientation) at the maximum size where it can fit within the bounding box that you
280+
specified. The Switch aspect ratio will remain at the "preferred" aspect ratio of 2:1
281+
(width:height) after the resizing.
282+
283+
Setting the touch response boundary
284+
-----------------------------------
285+
286+
The touch response area is defined by the Control class variable called
287+
:data:`touch_boundary`. In the case of the `SwitchRound` widget, we provide an
288+
:attr:`SwitchRound.touch_padding` input variable. The use of
289+
:attr:`SwitchRound.touch_padding` defines an additional number of pixels surrounding
290+
the display elements that respond to touch events. To achieve this additional space,
291+
the :data:`touch_boundary` increases in size in all dimensions by the number of pixels
292+
specified in the :attr:`SwitchRound.touch_padding` parameter.
293+
294+
The :data:`touch_boundary` is used in the Control function
295+
:meth:`~displayio_switchround.SwitchRound.contains` that checks whether any
296+
touch_points are within the boundary. Please pay particular attention to the
297+
`SwitchRound` :meth:`~displayio_switchround.SwitchRound.contains` method, since it
298+
calls the :meth:`~adafruit_displayio_layout.widgets.control.Control.contains`
299+
superclass method with the touch_point value adjusted for the switch's
300+
:attr:`~displayio_switchround.SwitchRound.x` and
301+
:attr:`~displayio_switchround.SwitchRound.y` values. This offset adjustment is
302+
required since the :meth:`~adafruit_displayio_layout.widgets.control.Control.contains`
303+
function operates only on the widget's local coordinate system. It's good to keep in
304+
mind which coordinate system you are working in, to ensure your code responds to the
305+
right inputs!
306+
307+
Summary
308+
-------
309+
310+
The `SwitchRound` widget is an example to explain the use of the
311+
:class:`~adafruit_displayio_layout.widgets.widget.Widget` and
312+
:class:`~adafruit_displayio_layout.widgets.control.Control` class methods. The
313+
:class:`~adafruit_displayio_layout.widgets.widget.Widget` class handles the overall
314+
sizing and positioning function and is the group that holds all the graphical elements.
315+
The :class:`~adafruit_displayio_layout.widgets.control.Control` class is used to define
316+
the response of the widget to touch events (or could be generalized to other inputs).
317+
Anything that only displays (such as a graph or an indicator light) won't need to
318+
inherit the :class:`~adafruit_displayio_layout.widgets.control.Control` class. But
319+
anything that responds to touch inputs should inherit the
320+
:class:`~adafruit_displayio_layout.widgets.control.Control` class to define the
321+
:data:`touch_boundary` and the touch response functions.
322+
323+
I hope this `SwitchRound` widget will help turn on some new ideas and highlight some
324+
of the new capabilities of the :class:`~adafruit_displayio_layout.widgets.widget.Widget`
325+
and :class:`~adafruit_displayio_layout.widgets.control.Control` classes. Now go see
326+
what else you can create and extend from here!
327+
328+
A Final Word
329+
------------
330+
331+
The design of the Widget and Control classes are open for inputs. If you think any
332+
additions or changes are useful, add it and please submit a pull request so others can
333+
use it too! Also, keep in mind you don't even need to follow these classes to get the
334+
job done. The Widget and Class definitions are designed to give guidance about one way
335+
to make things work, and to try to share some code. If it's standing in your way, do
336+
something else! If you want to use the ``grid_layout`` or other layout tools in this
337+
library, you only *really* need to have methods for positioning and resizing.
338+
339+
.. note:: **Never let any of these class definitions hold you back, let your imagination
340+
run wild and make some cool widgets!**

docs/overview.rst.license

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
2+
SPDX-FileCopyrightText: Copyright (c) 2021 Kevin Matocha for circuitpython
3+
4+
SPDX-License-Identifier: MIT

0 commit comments

Comments
 (0)