/ examples / asyncio_displayio_button.py
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())