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.