Skip to content

Conversation

almarklein
Copy link
Member

@almarklein almarklein commented Jun 29, 2025

This is a proof of concept for an idea to allow users to handle events using await. An event handling workflow can then be written as a single async function, which can be much easier than listening to multiple events and maintaining state across the handlers.

Example implementing drag:

@canvas.add_event_task
async def foo(for_event):
    while True:

        # Wait for pointer down
        event = await for_event("pointer_down")

        # Does this select the current position of the active block?
        width, height = canvas.get_logical_size()
        x = int(w * event["x"] / width)
        y = int(h * event["y"] / height)
        if [x, y] != currentpos:
            print("nope", x, y)
            continue

        # Move until pointer up
        while True:
            event = await for_event("pointer_move", "pointer_up")
            if event["event_type"] == "pointer_up":
                break

            width, height = canvas.get_logical_size()
            x = int(w * event["x"] / width)
            y = int(h * event["y"] / height)
            print(x, y)
            currentpos[:] = x, y

The use-case that triggered me to think about this more is the lasso tool in FPL: fastplotlib/fastplotlib#837

I'm curious to what @Korijn thinks of this idea from a design/API perspective, and what users like @kushalkolar, @hmaarrfk, @claydugo, @kingbedjed think of writing handlers this way. I think it should be an alternative to add_event_handler, not replace it.

Some notes regarding async:

  • Yes, this works with any backend.
  • Yes, you can also await sleep(), but you should from rendercanvas.asyncs import sleep to make it portable.
  • Although, if you know the underlying loop is asyncio, you can do any asyncio stuff :)

Some caveats:

  • If an exception occurs in the task, it stops the task completely, unless you add a try except.

Some ideas:

  • The argument into the function can be the for_event() instead of the emitted object (see example above).
  • Use canvas.add_event_task("pointer_down", func) so the task is initiated for every mouse down, avoiding one while loop.
  • Maybe could write this as a generator instead of a an async function to make it feel more 'confined' to events.

@almarklein
Copy link
Member Author

I've looked at using this approach while implementing the interactions of the lasso selector in Fastplotlib. It's not as useful as I hoped, and having separate callbacks while you maintain an explicit state does have advantages (e.g. the state being available for debugging).

So cool experiment, but not very practical. Closing.

@almarklein almarklein closed this Sep 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant