Skip to content

POC: event handling using await #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions examples/drag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
Noise
-----

Simple example that uses the bitmap-context to show images of noise.
"""

# run_example = true

import numpy as np
from rendercanvas.auto import RenderCanvas, loop


canvas = RenderCanvas(update_mode="continuous")
context = canvas.get_context("bitmap")


w, h = 12, 12
currentpos = [1, 1]

@canvas.request_draw
def animate():
x, y = currentpos

bitmap = np.zeros((h, w, 4), np.uint8)
bitmap[y, x] = 255

context.set_bitmap(bitmap)



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

# Wait for pointer down
event = await emitter.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 emitter.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


loop.run()
27 changes: 27 additions & 0 deletions rendercanvas/_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,30 @@ class EventEmitter:
def __init__(self):
self._pending_events = deque()
self._event_handlers = defaultdict(list)
self._signals = []
self._closed = False

def _release(self):
self._closed = True
self._pending_events.clear()
self._event_handlers.clear()

async def for_event(self, *event_types: str):
from .utils.asyncs import Event as AsyncSignal # rename to avoid confusion

for type in event_types:
if not (type == "*" or type in valid_event_types):
raise ValueError(f"Calling for_event with invalid event_type: '{type}'")

# Get signal. This is an asyncio, trio, rendercanvas.utils._async_adapter, or anything else from sniffio
# This is a low level asyncio construct that's actually called "Event", but let's call it signal here.
signal = AsyncSignal()
signal.event_types = set(event_types)
self._signals.append(signal)

await signal.wait()
return signal.event

def add_handler(self, *args, order: float = 0):
"""Register an event handler to receive events.

Expand Down Expand Up @@ -225,6 +242,16 @@ async def emit(self, event):
await callback(event)
else:
callback(event)
# Handle signals that ``await for_event(..)``.
# TODO: Use more performent data structure so we can avoid iterating over all waiting signals.
# TODO: maybe it makes more sense to think of the signals as tasks; each signal represents a waiting task.
# TODO: is there also a common construct that we can pass a value to?
signals_to_remove = []
for signal in self._signals:
if event_type in signal.event_types:
signals_to_remove.append(signal)
signal.event = event
signal.set()
# Close?
if event_type == "close":
self._release()
Expand Down
8 changes: 7 additions & 1 deletion rendercanvas/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ._coreutils import logger, log_exception, BaseEnum

if TYPE_CHECKING:
from typing import Callable, List, Optional, Tuple
from typing import Callable, Coroutine, List, Optional, Tuple

EventHandlerFunction = Callable[[dict], None]
DrawFunction = Callable[[], None]
Expand Down Expand Up @@ -309,6 +309,12 @@ def add_event_handler(
def remove_event_handler(self, callback: EventHandlerFunction, *types: str) -> None:
return self._events.remove_handler(callback, *types)

def add_event_task(
self, async_func: Callable[[], Coroutine], name: str = "unnamed"
):
loop = self._rc_canvas_group.get_loop()
loop.add_task(async_func, self._events)

def submit_event(self, event: dict) -> None:
# Not strictly necessary for normal use-cases, but this allows
# the ._event to be an implementation detail to subclasses, and it
Expand Down
Loading