Skip to content

Commit bac3f94

Browse files
authored
Add event pubsub for glfw and jupyter canvas (#224)
* add event pubsub for glfw and jupyter canvas * support multiple type registration and decorator style * reverse decorating flag so variable names make more sense * Allow docs to be built on Windows, and add docstrings for add_event_handler and remove_event_handler * correct typo
1 parent 451a7bd commit bac3f94

File tree

3 files changed

+118
-3
lines changed

3 files changed

+118
-3
lines changed

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
with open(os.path.join(ROOT_DIR, "docs", "reference_wgpu.rst"), "rb") as f:
2727
wgpu_api_docs_text = f.read().decode()
2828
for cls_name in wgpu.base.__all__:
29-
expected_line = f".. autoclass:: wgpu.{cls_name}\n"
29+
expected_line = f".. autoclass:: wgpu.{cls_name}"
3030
assert expected_line in wgpu_api_docs_text, f"Missing docs for {cls_name}"
3131

3232
# Make flags and enum appear better in docs

wgpu/gui/glfw.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
or ``sudo apt install libglfw3-wayland`` when using Wayland.
88
"""
99

10+
from collections import defaultdict
1011
import os
1112
import sys
1213
import time
@@ -138,6 +139,7 @@ def __init__(self, *, size=None, title=None, **kwargs):
138139
self._need_draw = False
139140
self._request_draw_timer_running = False
140141
self._changing_pixel_ratio = False
142+
self._event_handlers = defaultdict(set)
141143

142144
# Register ourselves
143145
all_glfw_canvases.add(self)
@@ -308,7 +310,62 @@ def handle_event(self, event):
308310
is a dict with at least the key event_type. For details, see
309311
https://jupyter-rfb.readthedocs.io/en/latest/events.html
310312
"""
311-
pass
313+
event_type = event.get("event_type")
314+
for callback in self._event_handlers[event_type]:
315+
callback(event)
316+
317+
def add_event_handler(self, *args):
318+
"""Register an event handler.
319+
320+
Arguments:
321+
callback (callable): The event handler. Must accept a
322+
single event argument.
323+
*types (list of strings): A list of event types.
324+
325+
For the available events, see
326+
https://jupyter-rfb.readthedocs.io/en/latest/events.html
327+
328+
Can also be used as a decorator.
329+
330+
Example:
331+
332+
.. code-block:: py
333+
334+
def my_handler(event):
335+
print(event)
336+
337+
canvas.add_event_handler(my_handler, "pointer_up", "pointer_down")
338+
339+
Decorator usage example:
340+
341+
.. code-block:: py
342+
343+
@canvas.add_event_handler("pointer_up", "pointer_down")
344+
def my_handler(event):
345+
print(event)
346+
"""
347+
decorating = not callable(args[0])
348+
callback = None if decorating else args[0]
349+
types = args if decorating else args[1:]
350+
351+
def decorator(_callback):
352+
for type in types:
353+
self._event_handlers[type].add(_callback)
354+
return _callback
355+
356+
if decorating:
357+
return decorator
358+
return decorator(callback)
359+
360+
def remove_event_handler(self, callback, *types):
361+
"""Unregister an event handler.
362+
363+
Arguments:
364+
callback (callable): The event handler.
365+
*types (list of strings): A list of event types.
366+
"""
367+
for type in types:
368+
self._event_handlers[type].remove(callback)
312369

313370
# User events
314371

wgpu/gui/jupyter.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
can be used as cell output, or embedded in a ipywidgets gui.
44
"""
55

6+
from collections import defaultdict
67
import weakref
78
import asyncio
89

@@ -27,6 +28,7 @@ def __init__(self, *, size=None, title=None, **kwargs):
2728
self._logical_size = 0, 0
2829
self._is_closed = False
2930
self._request_draw_timer_running = False
31+
self._event_handlers = defaultdict(set)
3032

3133
# Register so this can be display'ed when run() is called
3234
pending_jupyter_canvases.append(weakref.ref(self))
@@ -38,13 +40,69 @@ def __init__(self, *, size=None, title=None, **kwargs):
3840
# Implementation needed for RemoteFrameBuffer
3941

4042
def handle_event(self, event):
41-
event_type = event.get("event_type", "")
43+
event_type = event.get("event_type")
4244
if event_type == "close":
4345
self._is_closed = True
4446
elif event_type == "resize":
4547
self._pixel_ratio = event["pixel_ratio"]
4648
self._logical_size = event["width"], event["height"]
4749

50+
for callback in self._event_handlers[event_type]:
51+
callback(event)
52+
53+
def add_event_handler(self, *args):
54+
"""Register an event handler.
55+
56+
Arguments:
57+
callback (callable): The event handler. Must accept a
58+
single event argument.
59+
*types (list of strings): A list of event types.
60+
61+
For the available events, see
62+
https://jupyter-rfb.readthedocs.io/en/latest/events.html
63+
64+
Can also be used as a decorator.
65+
66+
Example:
67+
68+
.. code-block:: py
69+
70+
def my_handler(event):
71+
print(event)
72+
73+
canvas.add_event_handler(my_handler, "pointer_up", "pointer_down")
74+
75+
Decorator usage example:
76+
77+
.. code-block:: py
78+
79+
@canvas.add_event_handler("pointer_up", "pointer_down")
80+
def my_handler(event):
81+
print(event)
82+
"""
83+
decorating = not callable(args[0])
84+
callback = None if decorating else args[0]
85+
types = args if decorating else args[1:]
86+
87+
def decorator(_callback):
88+
for type in types:
89+
self._event_handlers[type].add(_callback)
90+
return _callback
91+
92+
if decorating:
93+
return decorator
94+
return decorator(callback)
95+
96+
def remove_event_handler(self, callback, *types):
97+
"""Unregister an event handler.
98+
99+
Arguments:
100+
callback (callable): The event handler.
101+
*types (list of strings): A list of event types.
102+
"""
103+
for type in types:
104+
self._event_handlers[type].remove(callback)
105+
48106
def get_frame(self):
49107
self._request_draw_timer_running = False
50108
# The _draw_frame_and_present() does the drawing and then calls

0 commit comments

Comments
 (0)