%gui asyncio

"""
Explanation: Index - Back
Asynchronous Widgets
This notebook covers two scenarios where we'd like widget-related code to run without blocking the kernel from acting on other execution requests:

Pausing code to wait for user interaction with a widget in the frontend
Updating a widget in the background

Waiting for user interaction
You may want to pause your Python code to wait for some user interaction with a widget from the frontend. Typically this would be hard to do since running Python code blocks any widget messages from the frontend until the Python code is done.
We'll do this in two approaches: using the event loop integration, and using plain generator functions.
Event loop integration
If we take advantage of the event loop integration IPython offers, we can have a nice solution using the async/await syntax in Python 3.
First we invoke our asyncio event loop. This requires ipykernel 4.7 or later.
End of explanation
"""


import asyncio
def wait_for_change(widget, value):
    future = asyncio.Future()
    def getvalue(change):
        # make the new value available
        future.set_result(change.new)
        widget.unobserve(getvalue, value)
    widget.observe(getvalue, value)
    return future

"""
Explanation: We define a new function that returns a future for when a widget attribute changes.
End of explanation
"""


from ipywidgets import IntSlider, Output
slider = IntSlider()
out = Output()

async def f():
    for i in range(10):
        out.append_stdout('did work ' + str(i) + '\n')
        x = await wait_for_change(slider, 'value')
        out.append_stdout('async function continued with value ' + str(x) + '\n')
asyncio.ensure_future(f())

slider

out

"""
Explanation: And we finally get to our function where we will wait for widget changes. We'll do 10 units of work, and pause after each one until we observe a change in the widget. Notice that the widget's value is available to us, since it is what the wait_for_change future has as a result.
Run this function, and change the slider 10 times.
End of explanation
"""


from functools import wraps
def yield_for_change(widget, attribute):
    """Pause a generator to wait for a widget change event.
        
    This is a decorator for a generator function which pauses the generator on yield
    until the given widget attribute changes. The new value of the attribute is
    sent to the generator and is the value of the yield.
    """
    def f(iterator):
        @wraps(iterator)
        def inner():
            i = iterator()
            def next_i(change):
                try:
                    i.send(change.new)
                except StopIteration as e:
                    widget.unobserve(next_i, attribute)
            widget.observe(next_i, attribute)
            # start the generator
            next(i)
        return inner
    return f

"""
Explanation: Generator approach
If you can't take advantage of the async/await syntax, or you don't want to modify the event loop, you can also do this with generator functions.
First, we define a decorator which hooks a generator function up to widget change events.
End of explanation
"""


from ipywidgets import IntSlider, VBox, HTML
slider2=IntSlider()

@yield_for_change(slider2, 'value')
def f():
    for i in range(10):
        print('did work %s'%i)
        x = yield
        print('generator function continued with value %s'%x)
f()

slider2

"""
Explanation: Then we set up our generator.
End of explanation
"""


import threading
from IPython.display import display
import ipywidgets as widgets
import time
progress = widgets.FloatProgress(value=0.0, min=0.0, max=1.0)

def work(progress):
    total = 100
    for i in range(total):
        time.sleep(0.2)
        progress.value = float(i+1)/total

thread = threading.Thread(target=work, args=(progress,))
display(progress)
thread.start()

"""
Explanation: Modifications
The above two approaches both waited on widget change events, but can be modified to wait for other things, such as button event messages (as in a "Continue" button), etc.
Updating a widget in the background
Sometimes you'd like to update a widget in the background, allowing the kernel to also process other execute requests. We can do this with threads. In the example below, the progress bar will update in the background and will allow the main kernel to do other computations.
End of explanation
"""


