Bit Garden
================================================================================
Practical async in Python UI - 19/11/02
----------------------------------------
Async has a lot of hype, but there are some blank spots in terms of usability.
One spot that I find could use some help is async with UI elements.
[Brython](https://brython.info) has an implementation of html functions that
feel pythonic as well as some basic async ways of waiting for events. While
this is a good start, it has some limitations like waiting for a specific
element to fire it's event, but only that element and not being cancellable.
[Kivy](https://kivy.org) has recently made the step into the async world, but
also seems to lack the async events to working with UI elements.
Async event handling can sometimes provide a more natural flow to how the code
is read.
---
Enter `with` and `async`
---
Let's take an example of a number guessing game. You would make
an input of some sort, wait for the person to make a guess, then reply of they
guessed correctly.
Traditionally you would make textbox and a button to submit. The button would
be bound to a function and that function would be called when clicked.
```
# python 3.8 (click to run)
import random
def on_click(ev):
picked_num = str(random.randint(1, 10))
if guess.text == str(random.randint(1, 10)):
snack('You won!')
else:
snack(f'You lost :( I picked {picked_num}')
p = card((guess := textbox(placeholder='Guess 1-10', multiline=False))
+ (guess_button := button(icon('play_arrow'),
color=(32, 96, 32, 1),
raised=True)))
guess_button.bind('click', on_click)
await popup(p)
```
This works but doesn't read naturally. `Make function on_click. Make texbox.`
`Make button. Bind button click to on_click.`
This could be redone in a more straight forward fashion with async and a
context manager to handle the binding and unbinding.
```
tb = textbox(placeholder='Make a guess')
submit = button('Submit')
with UIEventStream([submit, 'click']) as stream:
await _any(stream)
# check guess
```
Now we aren't making once off functions and it flows top to bottom instead of
jumping to another spot logically.
In this case `UIEventStream` just does binding and unbinding in the background
and doesn't actually invent the wheel, but allows the events to be streamed
inline and manipulated(like throttle or debouncing).
---
Here's the result.
```
# python 3.8 (click to run)
import random
from browser import aio
async with Popup(doc) as p:
p <= card((guess := textbox(placeholder='Guess 1-10', multiline=False))
+ (guess_button := button(icon('play_arrow'),
color=(32, 96, 32, 1),
raised=True)))
center(p)
with UIEventStream([guess_button, 'click']) as stream:
await _any(stream)
picked_num = str(random.randint(1, 10))
if guess.text == str(random.randint(1, 10)):
snack('You won!')
else:
snack(f'You lost :( I picked {picked_num}')
```
--------------------------------------------------------------------------------
Restarting again x.x - 19/10/27
-------------------------------
Restarting with Markdown. Hopefully this will be _much_ easier to maintain than
my last endeavours.
About Me
================================================================================
I haven't finished this bit yet.