Introduction

Documentation Status Discord Build Status Code Style: Black

A sliding switch widget with a round shape.

Dependencies

This driver depends on:

Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading the Adafruit library and driver bundle or individual libraries can be installed using circup.

Installing from PyPI

On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally from PyPI. To install for current user:

pip3 install circuitpython-displayio-switchround

To install system-wide (this may be required in some cases):

sudo pip3 install circuitpython-displayio-switchround

To install in a virtual environment in your current project:

mkdir project-name && cd project-name
python3 -m venv .env
source .env/bin/activate
pip3 install circuitpython-displayio-switchround

Usage Example

See scripts in the examples directory of this repository.

Contributing

Contributions are welcome! Please read our Code of Conduct before contributing to help this project stay welcoming.

Documentation

For information on building library documentation, please check out this guide.

Table of Contents

Simple test

Create a single sliding switch.

examples/displayio_switchround_simpletest.py
 1# SPDX-FileCopyrightText: 2021 Kevin Matocha
 2#
 3# SPDX-License-Identifier: MIT
 4"""
 5Creates a single sliding switch widget.
 6"""
 7
 8import time
 9import board
10import displayio
11import adafruit_touchscreen
12from adafruit_displayio_layout.widgets.switch_round import SwitchRound as Switch
13
14display = board.DISPLAY
15
16ts = adafruit_touchscreen.Touchscreen(
17    board.TOUCH_XL,
18    board.TOUCH_XR,
19    board.TOUCH_YD,
20    board.TOUCH_YU,
21    calibration=((5200, 59000), (5800, 57000)),
22    size=(display.width, display.height),
23)
24
25# Create the switch
26my_switch = Switch(20, 30)
27
28
29my_group = displayio.Group()
30my_group.append(my_switch)
31
32# Add my_group to the display
33display.show(my_group)
34
35# Start the main loop
36while True:
37
38    p = ts.touch_point  # get any touches on the screen
39
40    if p:  # Check each switch if the touch point is within the switch touch area
41        # If touched, then flip the switch with .selected
42        if my_switch.contains(p):
43            my_switch.selected(p)
44
45    time.sleep(0.05)  # touch response on PyPortal is more accurate with a small delay

Switch test with multiple switches

Create multiple sliding switch with various sizes and orientations.

examples/displayio_switchround_multiple.py
  1# SPDX-FileCopyrightText: 2021 Kevin Matocha
  2#
  3# SPDX-License-Identifier: MIT
  4"""
  5Creates multiple sliding switch widgets of various size and orientations.
  6"""
  7
  8import time
  9import board
 10import displayio
 11import adafruit_touchscreen
 12from adafruit_displayio_layout.widgets.switch_round import SwitchRound as Switch
 13
 14display = board.DISPLAY
 15
 16# setup the touch screen
 17ts = adafruit_touchscreen.Touchscreen(
 18    board.TOUCH_XL,
 19    board.TOUCH_XR,
 20    board.TOUCH_YD,
 21    board.TOUCH_YU,
 22    calibration=((5200, 59000), (5800, 57000)),
 23    size=(display.width, display.height),
 24)
 25
 26
 27# Create the switches
 28
 29my_switch = Switch(20, 30)
 30
 31my_switch2 = Switch(
 32    x=120,
 33    y=35,
 34    height=30,  # Set height to 30 pixels.  If you do not specify width,
 35    # it is automatically set to a default aspect ratio
 36    touch_padding=10,  # add extra boundary for touch response
 37    value=True,
 38)  # initial value is set to True
 39
 40my_switch3 = Switch(
 41    x=20,
 42    y=85,
 43    height=40,
 44    fill_color_off=(255, 0, 0),  # Set off colorred, can use hex code (0xFF0000)
 45    outline_color_off=(80, 0, 0),
 46    background_color_off=(150, 0, 0),
 47    background_outline_color_off=(30, 0, 0),
 48)
 49
 50my_switch4 = Switch(
 51    x=120,
 52    y=85,
 53    height=40,
 54    width=110,  # you can set the width manually but it may look weird
 55)
 56
 57my_switch5 = Switch(
 58    x=20,
 59    y=140,
 60    height=40,
 61    display_button_text=False,  # do not show the 0/1 on the switch
 62)
 63
 64my_switch6 = Switch(
 65    x=120,
 66    y=140,
 67    height=40,
 68    horizontal=False,  # set orientation to vertical
 69)
 70
 71my_switch7 = Switch(
 72    x=180,
 73    y=140,
 74    height=40,
 75    horizontal=False,  # set orientation to vertical
 76    flip=True,  # swap the direction
 77)
 78
 79my_switch8 = Switch(
 80    x=0,
 81    y=0,  # this is a larger, vertical orientation switch
 82    height=60,
 83    horizontal=False,  # set orientation to vertical
 84    flip=True,  # swap the direction
 85)
 86# use anchor_point and anchored_position to set the my_switch8 position
 87# relative to the display size.
 88my_switch8.anchor_point = (1.0, 1.0)
 89# the switch anchor_point is the bottom right switch corner
 90my_switch8.anchored_position = (display.width - 10, display.height - 10)
 91# the switch anchored_position is 10 pixels from the display
 92# lower right corner
 93
 94my_group = displayio.Group()
 95my_group.append(my_switch)
 96my_group.append(my_switch2)
 97my_group.append(my_switch3)
 98my_group.append(my_switch4)
 99my_group.append(my_switch5)
100my_group.append(my_switch6)
101my_group.append(my_switch7)
102my_group.append(my_switch8)
103
104# Add my_group to the display
105display.show(my_group)
106
107
108# Start the main loop
109while True:
110
111    p = ts.touch_point  # get any touches on the screen
112
113    if p:  # Check each switch if the touch point is within the switch touch area
114        # If touched, then flip the switch with .selected
115        if my_switch.contains(p):
116            my_switch.selected(p)
117
118        elif my_switch2.contains(p):
119            my_switch2.selected(p)
120
121        elif my_switch3.contains(p):
122            my_switch3.selected(p)
123
124        elif my_switch4.contains(p):
125            my_switch4.selected(p)
126
127        elif my_switch5.contains(p):
128            my_switch5.selected(p)
129
130        elif my_switch6.contains(p):
131            my_switch6.selected(p)
132
133        elif my_switch7.contains(p):
134            my_switch7.selected(p)
135
136        elif my_switch8.contains(p):
137            my_switch8.selected(p)
138
139    time.sleep(0.05)  # touch response on PyPortal is more accurate with a small delay

Overview of SwitchRound

Quickstart: Importing and using SwitchRound

Here is one way of importing the SwitchRound class so you can use it as the name Switch:

from displayio_switchround import SwitchRound as Switch

Now you can create a switch at pixel position x=20, y=30 using:

my_switch = Switch(20, 30) # create the switch at x=20, y=30

Once you setup your display, you can now add my_switch to your display using:

display.show(my_switch)  # add the group to the display

If you want to have multiple display elements, you can create a group and then append the switch and the other elements to the group. Then, you can add the full group to the display as in this example:

my_switch = Switch(20, 30)  # create the switch at x=20, y=30
my_group = displayio.Group()  # make a group
my_group.append(my_switch)  # Add my_switch to the group

#
# Append other display elements to the group
#

display.show(my_group)  # add the group to the display

For a full example, including how to respond to screen touches, check out the following examples in the Adafruit_CircuitPython_DisplayIO_Layout library:

Summary: SwitchRound Features and input variables

The SwitchRound widget has numerous options for controlling its position, visible appearance, orientation, animation speed and value through a collection of input variables:

  • position:

    x and y or anchor_point and anchored_position

  • size:

    width and height

    It is recommended to leave width = None to use the preferred aspect ratio.

  • orientation and movement direction (on vs. off):

    horizontal and flip

  • switch color:

    fill_color_off, fill_color_on, outline_color_off and outline_color_on

  • background color:

    background_color_off, background_color_on, background_outline_color_off and background_outline_color_on

  • linewidths:

    switch_stroke and text_stroke

  • 0/1 display:

    display_button_text

    Set to True if you want the 0/1 shapes to show on the switch

  • animation:

    animation_time

    Set the duration (in seconds) it will take to transition the switch, use 0 if you want it to snap into position immediately. The default value of 0.2 seconds is a good starting point, and larger values for bigger switches.

  • value:

    value

    Set to the initial value (True or False)

  • touch boundaries:

    touch_padding

    This defines the number of additional pixels surrounding the switch that should respond to a touch. (Note: The touch_padding variable updates the touch_boundary Control class variable. The definition of the touch_boundary is used to determine the region on the Widget that returns True in the contains() method.)

Description of features

The SwitchRound widget is a sliding switch that changes state whenever it is touched. The color gradually changes from the off-state color scheme to the on-state color scheme as the switch transfers from off to the on position. The switch has an optional display of “0” and “1” on the sliding switch. The switch can be oriented using the horizontal input variable, and the sliding direction can be changed using the flip input variable.

Regarding switch sizing, it is recommended to set the height dimension but to leave the width = None. Setting width = None will allow the width to resize to maintain a recommended aspect ratio of width/height. Alternately, the switch can be resized using the resize() method, and it will adjust the width and height to the maximum size that will fit inside the requested width and height dimensions, while keeping the preferred aspect ratio. To make the switch easier to be selected, additional padding around the switch can be defined using the touch_padding input variable to increase the touch-responsive area. The duration of animation between on/off can be set using the animation_time input variable.

Internal details: How the SwitchRound widget works

The SwitchRound widget is a graphical element that responds to touch elements to provide sliding switch on/off behavior. Whenever touched, the switch toggles to its alternate value. The following sections describe the construction of the SwitchRound widget, in the hopes that it will serve as a first example of the key properties and responses for widgets.

Inheritance diagram of displayio_switchround

The SwitchRound widget inherits from two classes, it is a subclass of Widget, which itself is a subclass of displayio.Group, and a subclass of Control. The Widget class helps define the positioning and sizing of the switch, while th Control class helps define the touch-response behavior.

The following sections describe the structure and inner workings of SwitchRound.

Group structure: Display elements that make up SwitchRound

The Widget class is a subclass of displayio.Group, thus we can append graphical elements to the Widget for displaying on the screen. The switch consists of the following graphical elements:

  1. switch_roundrect: The switch background

  2. switch_circle: The switch button that slides back and forth

  3. text_0 [Optional]: The “0” circle shape on the switch button

  4. text_1 [Optional]: The “1” rectangle shape on the switch button

The optional text items can be displayed or hidden using the display_button_text input variable.

Coordinate systems and use of anchor_point and anchored_position

See the Widget class definition for clarification on the methods for positioning the switch, including the difference in the display coordinate system and the Widget’s local coordinate system.

The Widget construction sequence

Here is the set of steps used to define this sliding switch widget.

  1. Initialize the stationary display items

  2. Initialize the moving display elements

  3. Store initial position of the moving display elements

  4. Define “keyframes” to determine the translation vector

  5. Define the SwitchRound._draw_position() method between 0.0 to 1.0 (and slightly beyond)

  6. Select the motion “easing” function

  7. Extra. Go check out the SwitchRound._animate_switch() method

First, the stationary background rounded rectangle (RoundRect is created). Second, the moving display elements are created, the circle for the switch, the circle for the text “0” and the rectangle for the text “1”. Note that either the “0” or “1” is set as hidden, depending upon the switch value. Third, we store away the initial position of the three moving elements, these initial values will be used in the functions that move these display elements. Next, we define the motion of the moving element, by setting the self._x_motion and self._y_motion values that depending upon the horizontal and flip variables. These motion variables set the two “keyframes” for the moving elements, basically the endpoints of the switch motion. (Note: other widgets may need an _angle_motion variable if they require some form of rotation.) Next, we define the SwitchRound._draw_function() method. This method takes an input between 0.0 and 1.0 and adjusts the position relative to the motion variables, where 0.0 is the initial position and 1.0 represents the final position (as defined by the _x_motion and _y_motion values). In the case of the sliding switch, we also use this SwitchRound.position value (0.0 to 1.0) to gradually grade the color of the components between their “on” and “off” colors.

Making it move

Everything above has set the ground rules for motion, but doesn’t cause it to move. However, you have set almost all the pieces in place to respond to requests to change the position. All that is left is the Extra method that performs the animation, called SwitchRound._animate_switch(). The SwitchRound._animate_switch() method is triggered by a touch event through the selected() Control class method. Once triggered, this method checks how much time has elapsed. Based on the elapsed time and the SwitchRound.animation_time input variable, the SwitchRound._animate_switch() method calculates the SwitchRound.position where the switch should be. Then, it takes this SwitchRound.position to call the SwitchRound._draw_position() method that will update the display elements based on the requested position.

But there’s even one more trick to the animation. The SwitchRound._animate_switch() calculates the target position based on a linear relationship between the time and the position. However, to give the animation a better “feel”, it is desirable to tweak the motion function depending upon how this widget should behave or what suits your fancy. To do this we can use an “easing” function. In short, this adjusts the constant speed (linear) movement to a variable speed during the movement. Said another way, it changes the position versus time function according to a specific waveform equation. There are a lot of different “easing” functions that folks have used or you can make up your own. Some common easing functions are provided in the adafruit_displayio_layout.widgets.easing module. You can change the easing function based on changing which function is imported at the top of this file. You can see where the position is tweaked by the easing function in the line in the SwitchRound._animate_switch() method:

self._draw_position(easing(position))  # update the switch position

Go play around with the different easing functions and observe how the motion behavior changes. You can use these functions in multiple dimensions to get all varieties of behavior that you can take advantage of. The website easings.net can help you visualize some of the behavior of the easing functions.

Note

Some of the “springy” easing functions require position values slightly below 0.0 and slightly above 1.0, so if you want to use these, be sure to check that your _draw_position() method behaves itself for that range of position inputs.

Orientation and a peculiarity of width and height definitions for SwitchRound

In setting the switch sizing, use height and width to set the narrow and wide dimension of the switch. To try and reduce confusion, the orientation is modified after the height and width are selected. That is, if the switch is set to vertical, the height and still mean the “narrow” and the width will still mean the dimensions in the direction of the sliding.

If you need the switch to fit within a specific bounding box, it’s preferred to use the resize() function. This will put the switch (in whatever orientation) at the maximum size where it can fit within the bounding box that you specified. The Switch aspect ratio will remain at the “preferred” aspect ratio of 2:1 (width:height) after the resizing.

Setting the touch response boundary

The touch response area is defined by the Control class variable called touch_boundary. In the case of the SwitchRound widget, we provide an SwitchRound.touch_padding input variable. The use of SwitchRound.touch_padding defines an additional number of pixels surrounding the display elements that respond to touch events. To achieve this additional space, the touch_boundary increases in size in all dimensions by the number of pixels specified in the SwitchRound.touch_padding parameter.

The touch_boundary is used in the Control function contains() that checks whether any touch_points are within the boundary. Please pay particular attention to the SwitchRound contains() method, since it calls the contains() superclass method with the touch_point value adjusted for the switch’s x and y values. This offset adjustment is required since the contains() function operates only on the widget’s local coordinate system. It’s good to keep in mind which coordinate system you are working in, to ensure your code responds to the right inputs!

Summary

The SwitchRound widget is an example to explain the use of the Widget and Control class methods. The Widget class handles the overall sizing and positioning function and is the group that holds all the graphical elements. The Control class is used to define the response of the widget to touch events (or could be generalized to other inputs). Anything that only displays (such as a graph or an indicator light) won’t need to inherit the Control class. But anything that responds to touch inputs should inherit the Control class to define the touch_boundary and the touch response functions.

I hope this SwitchRound widget will help turn on some new ideas and highlight some of the new capabilities of the Widget and Control classes. Now go see what else you can create and extend from here!

A Final Word

The design of the Widget and Control classes are open for inputs. If you think any additions or changes are useful, add it and please submit a pull request so others can use it too! Also, keep in mind you don’t even need to follow these classes to get the job done. The Widget and Class definitions are designed to give guidance about one way to make things work, and to try to share some code. If it’s standing in your way, do something else! If you want to use the grid_layout or other layout tools in this library, you only really need to have methods for positioning and resizing.

Note

Never let any of these class definitions hold you back, let your imagination run wild and make some cool widgets!

displayio_switchround

A sliding switch widget with a round shape.

  • Author(s): Kevin Matocha

Implementation Notes

Hardware:

Software and Dependencies:

Inheritance

Inheritance diagram of displayio_switchround

class displayio_switchround.SwitchRound(x=0, y=0, width=None, height=40, touch_padding=0, horizontal=True, flip=False, anchor_point=None, anchored_position=None, fill_color_off=(66, 44, 66), fill_color_on=(0, 100, 0), outline_color_off=(30, 30, 30), outline_color_on=(0, 60, 0), background_color_off=(255, 255, 255), background_color_on=(0, 60, 0), background_outline_color_off=None, background_outline_color_on=None, switch_stroke=2, text_stroke=None, display_button_text=True, animation_time=0.2, value=False, **kwargs)

Create a SwitchRound. See Overview for more details.

Parameters
  • x (int) – pixel position, defaults to 0

  • y (int) – pixel position, defaults to 0

  • width (None,int) – width of the switch in pixels, if set to None (recommended) the width will auto-size relative to the height, defaults to None

  • height (int) – height of the switch in pixels, defaults to 40 pixels

  • touch_padding (int) – the width of an additional border surrounding the switch that extends the touch response boundary, defaults to 0

  • horizontal (bool) – To set vertical orientation, set horizontal to False, defaults to True

  • flip (bool) – If True the on and off direction will be flipped, default is True

  • anchor_point (None,tuple(float,float)) – (X,Y) values from 0.0 to 1.0 to define the anchor point relative to the switch bounding box, default is None

  • anchored_position (None,tuple(int,int)) – (x,y) pixel value for the location of the anchor_point, default is None

  • fill_color_off (tuple(int,int,int),int) – switch off-state fill color, as an RGB-tuple or 24-bit hex, default is (66, 44, 66) gray.

  • fill_color_on (tuple(int,int,int),int) – switch on-state fill color, as an RGB-tuple or 24-bit hex, default is (0, 100, 0) green.

  • outline_color_off (tuple(int,int,int),int) – switch off-state outline color, as an RGB-tuple or 24-bit hex, default is (30, 30, 30) dark gray.

  • outline_color_on (tuple(int,int,int),int) – switch on-state outline color, as an RGB-tuple or 24-bit hex, default is (0, 60, 0) green

  • background_color_off (tuple(int,int,int),int) – background off-state color, as an RGB-tuple or 24-bit hex, default is (255, 255, 255) white

  • background_color_on (tuple(int,int,int),int) – background on-state color, as an RGB-tuple or 24-bit hex, default is (0, 60, 0) dark green

  • background_outline_color_off (None,tuple(int,int,int),int) – background outline color in off-state, as an RGB-tuple or 24-bit hex or `None`. If set to None this will default to background_color_off, default is None

  • background_outline_color_on (None,tuple(int,int,int),int) – background outline color in on-state, as an RGB-tuple or 24-bit hex or `None`. If set to None this will default to background_color_on, default is None

  • switch_stroke (int) – outline stroke width for the switch and background, in pixels, default is 2

  • text_stroke (None,int) – outline stroke width (in pixels) for the 0/1 text shape outlines, if set to None it will use the value for switch_stroke, default value is None

  • display_button_text (bool) – If True display the 0/1 text shapes on the sliding switch. If False hide the 0/1 text shapes, default value is True

  • animation_time (float) – time for the switching animation, in seconds, default value is 0.2 seconds.

  • value (bool) – the initial value for the switch, default is False

selected(touch_point)

Response function when Switch is selected. When selected, the switch position and value is changed with an animation.

Parameters

touch_point (tuple(int,int)) – x,y location of the screen, in absolute display coordinates.

Returns

None

contains(touch_point)

Checks if the Widget was touched. Returns True if the touch_point is within the Control’s touch_boundary.

Parameters

touch_point (tuple(int,int)) – x,y location of the screen, in absolute display coordinates.

Returns

bool

property value

The current Switch value (Boolean).

Returns

bool

property width

The width of the Switch (int).

Returns

int

property anchor_point

The anchor point for positioning the widget, works in concert with anchored_position The relative (X,Y) position of the widget where the anchored_position is placed. For example (0.0, 0.0) is the Widget’s upper left corner, (0.5, 0.5) is the Widget’s center point, and (1.0, 1.0) is the Widget’s lower right corner.

Parameters

anchor_point (Tuple[float, float]) – In relative units of the Widget size.

property anchored_position

The anchored position (in pixels) for positioning the widget, works in concert with anchor_point. The anchored_position is the x,y pixel position for the placement of the Widget’s anchor_point.

Parameters

anchored_position (Tuple[int, int]) – The (x,y) pixel position for the anchored_position (in pixels).

append(layer)

Append a layer to the group. It will be drawn above other layers.

property bounding_box

The boundary of the widget. [x, y, width, height] in Widget’s local coordinates (in pixels). (getter only)

Returns

Tuple[int, int, int, int]

property height

The height of the Switch (int).

Returns

int

property hidden

True when the Group and all of it’s layers are not visible. When False, the Group’s layers are visible if they haven’t been hidden.

index(layer)

Returns the index of the first copy of layer. Raises ValueError if not found.

insert(index, layer)

Insert a layer into the group.

pop(index=- 1)

Remove the ith item and return it.

remove(layer)

Remove the first copy of layer. Raises ValueError if it is not present.

property scale

Scales each pixel within the Group in both directions. For example, when scale=2 each pixel will be represented by 2x2 pixels.

update_transform(parent_transform)

Update the parent transform and child transforms

property x

X position of the Group in the parent.

property y

Y position of the Group in the parent.

resize(new_width, new_height)

Resize the Switch to a new requested width and height.

Parameters
  • new_width (int) – requested maximum width

  • new_height (int) – requested maximum height

Returns

None

Indices and tables