asyncio_displayio_button.py
1 # SPDX-FileCopyrightText: 2022 Tim Cocks for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 """ 5 Example that illustrates how to use Displayio Buttons to modify 6 some blinking circles. One button inverts colors, the others change 7 the interval length of the blink for one of the circles. 8 """ 9 10 import asyncio 11 import adafruit_touchscreen 12 import displayio 13 import terminalio 14 import vectorio 15 import board 16 from adafruit_button import Button 17 18 # use built-in display 19 display = board.DISPLAY 20 21 # explicitly set the display to default orientation in-case it was changed 22 display.rotation = 0 23 24 # --| Button Config |------------------------------------------------- 25 # invert color Button 26 BUTTON_1_X = 10 27 BUTTON_1_Y = 80 28 BUTTON_1_LABEL = "Invert Color" 29 30 # slower interval Button 31 BUTTON_2_X = 200 32 BUTTON_2_Y = 160 33 BUTTON_2_LABEL = "Slower" 34 35 # faster interval Button 36 BUTTON_3_X = 200 37 BUTTON_3_Y = 80 38 BUTTON_3_LABEL = "Faster" 39 40 # shared button configurations 41 BUTTON_WIDTH = 100 42 BUTTON_HEIGHT = 50 43 BUTTON_STYLE = Button.ROUNDRECT 44 BUTTON_FILL_COLOR = 0x00FFFF 45 BUTTON_OUTLINE_COLOR = 0xFF00FF 46 BUTTON_LABEL_COLOR = 0x000000 47 # --| Button Config |------------------------------------------------- 48 49 # Setup touchscreen (PyPortal) 50 ts = adafruit_touchscreen.Touchscreen( 51 board.TOUCH_XL, 52 board.TOUCH_XR, 53 board.TOUCH_YD, 54 board.TOUCH_YU, 55 calibration=((5200, 59000), (5800, 57000)), 56 size=(display.width, display.height), 57 ) 58 59 # initialize color button 60 invert_color_btn = Button( 61 x=BUTTON_1_X, 62 y=BUTTON_1_Y, 63 width=BUTTON_WIDTH, 64 height=BUTTON_HEIGHT, 65 style=BUTTON_STYLE, 66 fill_color=BUTTON_FILL_COLOR, 67 outline_color=BUTTON_OUTLINE_COLOR, 68 label=BUTTON_1_LABEL, 69 label_font=terminalio.FONT, 70 label_color=BUTTON_LABEL_COLOR, 71 ) 72 73 # initialize interval slower button 74 interval_slower_btn = Button( 75 x=BUTTON_2_X, 76 y=BUTTON_2_Y, 77 width=BUTTON_WIDTH, 78 height=BUTTON_HEIGHT, 79 style=BUTTON_STYLE, 80 fill_color=BUTTON_FILL_COLOR, 81 outline_color=BUTTON_OUTLINE_COLOR, 82 label=BUTTON_2_LABEL, 83 label_font=terminalio.FONT, 84 label_color=BUTTON_LABEL_COLOR, 85 ) 86 87 # initialize interval faster button 88 interval_faster_btn = Button( 89 x=BUTTON_3_X, 90 y=BUTTON_3_Y, 91 width=BUTTON_WIDTH, 92 height=BUTTON_HEIGHT, 93 style=BUTTON_STYLE, 94 fill_color=BUTTON_FILL_COLOR, 95 outline_color=BUTTON_OUTLINE_COLOR, 96 label=BUTTON_3_LABEL, 97 label_font=terminalio.FONT, 98 label_color=BUTTON_LABEL_COLOR, 99 ) 100 101 # Button state data object. Will hold either true of false whether button is currently pressed 102 class ButtonState: 103 # pylint: disable=too-few-public-methods 104 def __init__(self, initial_state): 105 self.state = initial_state 106 107 108 # Interval length data object. Holds the amount of time in ms the interval should last for 109 class Interval: 110 # pylint: disable=too-few-public-methods 111 def __init__(self, initial_value): 112 self.value = initial_value 113 114 115 # main group to show things on the display 116 main_group = displayio.Group() 117 118 # Initialize first circle 119 palette_1 = displayio.Palette(2) 120 palette_1[0] = 0x125690 121 palette_1[1] = 0x125690 122 circle_1 = vectorio.Circle(pixel_shader=palette_1, radius=15, x=20, y=20) 123 124 # Initialize second circle 125 palette_2 = displayio.Palette(2) 126 palette_2[0] = 0x12FF30 127 palette_2[1] = 0x12FF30 128 circle_2 = vectorio.Circle(pixel_shader=palette_2, radius=15, x=60, y=20) 129 130 # add everything to the group, so it gets displayed 131 main_group.append(circle_1) 132 main_group.append(circle_2) 133 main_group.append(invert_color_btn) 134 main_group.append(interval_slower_btn) 135 main_group.append(interval_faster_btn) 136 137 138 async def blink(palette, interval, count, button_state): # Don't forget the async! 139 """ 140 blink coroutine. Hides and shows a vectorio shape by 141 using make_transparent() and make_opaque() on it's palette. 142 143 :param displayio.Palette palette: The palette to change colors on for blinking 144 :param Interval interval: The Interval data object containing the interval length to use 145 :param int count: The number of times to repeat the blink. -1 for indefinite loop 146 :param ButtonState button_state: The ButtonState data object for the invert color button 147 """ 148 while count < 0 or count > 0: 149 150 # if the color button is pressed 151 if button_state.state: 152 # if the color is still on default 153 if palette[0] == palette[1]: 154 # invert the color by subtracting from white 155 palette[0] = 0xFFFFFF - palette[0] 156 157 # if the color button is not pressed 158 else: 159 # set the color back to default 160 palette[0] = palette[1] 161 162 # hide the circle 163 palette.make_opaque(0) 164 # wait interval length 165 await asyncio.sleep(interval.value / 1000) # Don't forget the await! 166 167 # show the circle 168 palette.make_transparent(0) 169 # wait interval length 170 await asyncio.sleep(interval.value / 1000) # Don't forget the await! 171 172 # decrement count if it's positive 173 if count > 0: 174 count -= 1 175 176 177 def handle_color_button(touch_event, color_button, button_state): 178 """ 179 Check if the color button is pressed, and updates 180 the ButtonState data object as appropriate 181 182 :param touch_event: The touch point object from touchscreen 183 :param Button color_button: The button to check for presses on 184 :param ButtonState button_state: ButtonState data object to set 185 the current value into 186 """ 187 188 # if there is a touch event 189 if touch_event: 190 191 # if the color button is being touched 192 if color_button.contains(touch_event): 193 # set selected to change button color 194 color_button.selected = True 195 # set button_state so other coroutines can access it 196 button_state.state = True 197 198 # the color button is not being touched 199 else: 200 # set selected to change button color back to default 201 color_button.selected = False # if touch is dragged outside of button 202 # set button_state so other coroutines can access it. 203 button_state.state = False 204 205 # there are no touch events 206 else: 207 # if the color button is currently the pressed color 208 if color_button.selected: 209 # set selected back to false to change button back to default color 210 color_button.selected = False 211 # set button_state so other coroutines can access it 212 button_state.state = False 213 214 215 def handle_interval_buttons(touch_event, button_slower, button_faster, interval): 216 """ 217 Will check for presses on 218 the faster and slower buttons and updated the data in the 219 Interval data object as appropriate 220 221 :param touch_event: Touch point object from touchscreen 222 :param Button button_slower: The slower button object 223 :param Button button_faster: The faster button object 224 :param Interval interval: The Interval data object to store state 225 """ 226 # if there are any touch events 227 if touch_event: 228 # if the slower button is being touched 229 if button_slower.contains(touch_event): 230 # if it just became pressed. i.e. was not pressed last frame 231 if not button_slower.selected: 232 # set selected to change the button color 233 button_slower.selected = True 234 235 # increment the interval length and store it on the data object 236 interval.value += 100 237 print("new interval val: {}".format(interval.value)) 238 239 # if the slower button is not being touched 240 else: 241 # set selected to put the slower button back to default color 242 button_slower.selected = False 243 244 # if the faster button is being touched 245 if button_faster.contains(touch_event): 246 # if it just became pressed. i.e. was not pressed last frame 247 if not button_faster.selected: 248 # set selected to change the button color 249 button_faster.selected = True 250 # if the interval is large enough to decrement 251 if interval.value >= 100: 252 # decrement interval value and store it on the data object 253 interval.value -= 100 254 print("new interval val: {}".format(interval.value)) 255 256 # if the faster button is not being touched 257 else: 258 # set selected back to false to change color back to default 259 button_faster.selected = False 260 261 # there are no touch events 262 else: 263 # if slower button is the pressed color 264 if button_slower.selected: 265 # set it back to default color 266 button_slower.selected = False 267 268 # if the faster button is pressed color 269 if button_faster.selected: 270 # set it back to default color 271 button_faster.selected = False 272 273 274 async def monitor_buttons( 275 button_slower, button_faster, color_button, interval, button_state 276 ): 277 """ 278 monitor_buttons coroutine. 279 280 :param Button button_slower: The slower button object 281 :param Button button_faster: The faster button object 282 :param Button color_button: The invert color button object 283 :param Interval interval: The Interval data object to store state 284 :param ButtonState button_state: The ButtonState data object to 285 store color button state 286 """ 287 while True: 288 # get current touch data from overlay 289 p = ts.touch_point 290 291 # handle touch event data 292 handle_color_button(p, color_button, button_state) 293 handle_interval_buttons(p, button_slower, button_faster, interval) 294 295 # allow other tasks to do work 296 await asyncio.sleep(0) 297 298 299 # main coroutine 300 async def main(): # Don't forget the async! 301 # create data objects 302 color_btn_state = ButtonState(False) 303 interval_1 = Interval(550) 304 interval_2 = Interval(350) 305 306 # create circle blink tasks 307 circle_1_task = asyncio.create_task( 308 blink(palette_1, interval_1, -1, color_btn_state) 309 ) 310 circle_2_task = asyncio.create_task( 311 blink(palette_2, interval_2, 20, color_btn_state) 312 ) 313 314 # create buttons task 315 button_task = asyncio.create_task( 316 monitor_buttons( 317 interval_slower_btn, 318 interval_faster_btn, 319 invert_color_btn, 320 interval_1, 321 color_btn_state, 322 ) 323 ) 324 325 # start all of the tasks 326 await asyncio.gather( 327 circle_1_task, circle_2_task, button_task 328 ) # Don't forget the await! 329 330 331 # show main_group so it's visible on the display 332 display.show(main_group) 333 334 # start the main coroutine 335 asyncio.run(main())