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.