|
| 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!** |
0 commit comments