rendercanvas 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/PKG-INFO +3 -4
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/README.md +1 -2
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/pyproject.toml +1 -1
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/__init__.py +2 -3
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/_coreutils.py +23 -80
- rendercanvas-2.3.0/rendercanvas/_enums.py +176 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/_events.py +23 -53
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/_loop.py +9 -4
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/_scheduler.py +46 -40
- rendercanvas-2.3.0/rendercanvas/_version.py +194 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/auto.py +3 -1
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/base.py +139 -77
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/glfw.py +16 -43
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/jupyter.py +11 -18
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/offscreen.py +26 -13
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/qt.py +20 -36
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/raw.py +1 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/stub.py +5 -11
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/utils/bitmappresentadapter.py +1 -1
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/utils/cube.py +6 -3
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/wx.py +9 -26
- rendercanvas-2.2.0/rendercanvas/_version.py +0 -113
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/LICENSE +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/__pyinstaller/__init__.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/__pyinstaller/conftest.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/__pyinstaller/hook-rendercanvas.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/__pyinstaller/test_rendercanvas.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/_context.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/asyncio.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/pyqt5.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/pyqt6.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/pyside2.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/pyside6.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/trio.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/utils/__init__.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/utils/asyncadapter.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/utils/asyncs.py +0 -0
- {rendercanvas-2.2.0 → rendercanvas-2.3.0}/rendercanvas/utils/bitmaprenderingcontext.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rendercanvas
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: One canvas API, multiple backends
|
|
5
5
|
Keywords: canvas,rendering,graphics,wgpu,qt,wx,glfw,jupyter
|
|
6
6
|
Author: Almar Klein, Korijn van Golen
|
|
7
|
-
Requires-Python: >= 3.
|
|
7
|
+
Requires-Python: >= 3.10
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: sniffio
|
|
@@ -45,8 +45,7 @@ Provides-Extra: tests
|
|
|
45
45
|
[](https://badge.fury.io/py/rendercanvas)
|
|
46
46
|
[](https://jacobtomlinson.dev/effver)
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
# rendercanvas
|
|
48
|
+
<h1 align="center"><img src="https://github.com/user-attachments/assets/74ddfc1e-03ae-4965-afe9-8697ec4614b5" height="128"><br>rendercanvas</h1>
|
|
50
49
|
|
|
51
50
|
One canvas API, multiple backends 🚀
|
|
52
51
|
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
[](https://badge.fury.io/py/rendercanvas)
|
|
4
4
|
[](https://jacobtomlinson.dev/effver)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
# rendercanvas
|
|
6
|
+
<h1 align="center"><img src="https://github.com/user-attachments/assets/74ddfc1e-03ae-4965-afe9-8697ec4614b5" height="128"><br>rendercanvas</h1>
|
|
8
7
|
|
|
9
8
|
One canvas API, multiple backends 🚀
|
|
10
9
|
|
|
@@ -6,8 +6,7 @@ RenderCanvas: one canvas API, multiple backends.
|
|
|
6
6
|
|
|
7
7
|
from ._version import __version__, version_info
|
|
8
8
|
from . import _coreutils
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
11
|
-
from .base import BaseRenderCanvas, BaseLoop, CursorShape
|
|
9
|
+
from ._enums import CursorShape, EventType, UpdateMode
|
|
10
|
+
from .base import BaseRenderCanvas, BaseLoop
|
|
12
11
|
|
|
13
12
|
__all__ = ["BaseLoop", "BaseRenderCanvas", "CursorShape", "EventType", "UpdateMode"]
|
|
@@ -5,7 +5,7 @@ Core utilities that are loaded into the root namespace or used internally.
|
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
7
|
import sys
|
|
8
|
-
import
|
|
8
|
+
import time
|
|
9
9
|
import weakref
|
|
10
10
|
import logging
|
|
11
11
|
import ctypes.util
|
|
@@ -19,7 +19,7 @@ logger = logging.getLogger("rendercanvas")
|
|
|
19
19
|
logger.setLevel(logging.WARNING)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
err_hashes = {}
|
|
22
|
+
err_hashes = {} # hash -> [short-message, count, next-time]
|
|
23
23
|
|
|
24
24
|
_re_wgpu_ob = re.compile(r"`<[a-z|A-Z]+-\([0-9]+, [0-9]+, [a-z|A-Z]+\)>`")
|
|
25
25
|
|
|
@@ -42,22 +42,37 @@ def log_exception(kind):
|
|
|
42
42
|
except Exception as err:
|
|
43
43
|
# Store exc info for postmortem debugging
|
|
44
44
|
exc_info = list(sys.exc_info())
|
|
45
|
-
exc_info[2] = exc_info[2].tb_next # skip *this* function
|
|
45
|
+
exc_info[2] = exc_info[2].tb_next # type: ignore | skip *this* function
|
|
46
46
|
sys.last_type, sys.last_value, sys.last_traceback = exc_info
|
|
47
47
|
# Show traceback, or a one-line summary
|
|
48
48
|
msg = str(err)
|
|
49
49
|
msgh = error_message_hash(msg)
|
|
50
50
|
if msgh not in err_hashes:
|
|
51
|
+
# Prepare a short variant of the message for later use
|
|
52
|
+
short_msg = kind + ": " + msg.split("\n")[0].strip()
|
|
53
|
+
short_msg = short_msg if len(short_msg) <= 70 else short_msg[:69] + "…"
|
|
54
|
+
err_hashes[msgh] = [short_msg, 1, 0]
|
|
51
55
|
# Provide the exception, so the default logger prints a stacktrace.
|
|
52
56
|
# IDE's can get the exception from the root logger for PM debugging.
|
|
53
|
-
err_hashes[msgh] = 1
|
|
54
57
|
logger.error(kind, exc_info=err)
|
|
55
58
|
else:
|
|
56
59
|
# We've seen this message before, return a one-liner instead.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
short_count_tm = err_hashes[msgh]
|
|
61
|
+
short, count, tm = short_count_tm
|
|
62
|
+
short_count_tm[1] = count = count + 1
|
|
63
|
+
# Show the message now?
|
|
64
|
+
show_message = False
|
|
65
|
+
cur_time = time.perf_counter()
|
|
66
|
+
if count <= 5:
|
|
67
|
+
show_message = True
|
|
68
|
+
else:
|
|
69
|
+
if cur_time > tm:
|
|
70
|
+
show_message = True
|
|
71
|
+
# Log the messages and schedule when to show it next.
|
|
72
|
+
# Next message is after 1-3 seconds (3 when count reaches 300).
|
|
73
|
+
if show_message:
|
|
74
|
+
short_count_tm[2] = cur_time + min(max(count / 100, 1), 3)
|
|
75
|
+
logger.error(f"{short} ({count})")
|
|
61
76
|
|
|
62
77
|
|
|
63
78
|
# %% Weak bindings
|
|
@@ -78,78 +93,6 @@ def weakbind(method):
|
|
|
78
93
|
return proxy
|
|
79
94
|
|
|
80
95
|
|
|
81
|
-
# %% Enum
|
|
82
|
-
|
|
83
|
-
# We implement a custom enum class that's much simpler than Python's enum.Enum,
|
|
84
|
-
# and simply maps to strings or ints. The enums are classes, so IDE's provide
|
|
85
|
-
# autocompletion, and documenting with Sphinx is easy. That does mean we need a
|
|
86
|
-
# metaclass though.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class EnumType(type):
|
|
90
|
-
"""Metaclass for enums and flags."""
|
|
91
|
-
|
|
92
|
-
def __new__(cls, name, bases, dct):
|
|
93
|
-
# Collect and check fields
|
|
94
|
-
member_map = {}
|
|
95
|
-
for key, val in dct.items():
|
|
96
|
-
if not key.startswith("_"):
|
|
97
|
-
val = key if val is None else val
|
|
98
|
-
if not isinstance(val, (int, str)):
|
|
99
|
-
raise TypeError("Enum fields must be str or int.")
|
|
100
|
-
member_map[key] = val
|
|
101
|
-
# Some field values may have been updated
|
|
102
|
-
dct.update(member_map)
|
|
103
|
-
# Create class
|
|
104
|
-
klass = super().__new__(cls, name, bases, dct)
|
|
105
|
-
# Attach some fields
|
|
106
|
-
klass.__fields__ = tuple(member_map)
|
|
107
|
-
klass.__members__ = types.MappingProxyType(member_map) # enums.Enum compat
|
|
108
|
-
# Create bound methods
|
|
109
|
-
for name in ["__dir__", "__iter__", "__getitem__", "__setattr__", "__repr__"]:
|
|
110
|
-
setattr(klass, name, types.MethodType(getattr(cls, name), klass))
|
|
111
|
-
return klass
|
|
112
|
-
|
|
113
|
-
def __dir__(cls):
|
|
114
|
-
# Support dir(enum). Note that this order matches the definition, but dir() makes it alphabetic.
|
|
115
|
-
return cls.__fields__
|
|
116
|
-
|
|
117
|
-
def __iter__(cls):
|
|
118
|
-
# Support list(enum), iterating over the enum, and doing ``x in enum``.
|
|
119
|
-
return iter([getattr(cls, key) for key in cls.__fields__])
|
|
120
|
-
|
|
121
|
-
def __getitem__(cls, key):
|
|
122
|
-
# Support enum[key]
|
|
123
|
-
return cls.__dict__[key]
|
|
124
|
-
|
|
125
|
-
def __repr__(cls):
|
|
126
|
-
if cls is BaseEnum:
|
|
127
|
-
return "<rendercanvas.BaseEnum>"
|
|
128
|
-
pkg = cls.__module__.split(".")[0]
|
|
129
|
-
name = cls.__name__
|
|
130
|
-
options = []
|
|
131
|
-
for key in cls.__fields__:
|
|
132
|
-
val = cls[key]
|
|
133
|
-
options.append(f"'{key}' ({val})" if isinstance(val, int) else f"'{val}'")
|
|
134
|
-
return f"<{pkg}.{name} enum with options: {', '.join(options)}>"
|
|
135
|
-
|
|
136
|
-
def __setattr__(cls, name, value):
|
|
137
|
-
if name.startswith("_"):
|
|
138
|
-
super().__setattr__(name, value)
|
|
139
|
-
else:
|
|
140
|
-
raise RuntimeError("Cannot set values on an enum.")
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
class BaseEnum(metaclass=EnumType):
|
|
144
|
-
"""Base class for flags and enums.
|
|
145
|
-
|
|
146
|
-
Looks like Python's builtin Enum class, but is simpler; fields are simply ints or strings.
|
|
147
|
-
"""
|
|
148
|
-
|
|
149
|
-
def __init__(self):
|
|
150
|
-
raise RuntimeError("Cannot instantiate an enum.")
|
|
151
|
-
|
|
152
|
-
|
|
153
96
|
# %% lib support
|
|
154
97
|
|
|
155
98
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import types
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
# ----- Base enum class
|
|
5
|
+
|
|
6
|
+
# We implement a custom enum class that's much simpler than Python's enum.Enum,
|
|
7
|
+
# and simply maps to strings or ints. The enums are classes, so IDE's provide
|
|
8
|
+
# autocompletion, and documenting with Sphinx is easy. That does mean we need a
|
|
9
|
+
# metaclass though.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EnumType(type):
|
|
13
|
+
"""Metaclass for enums and flags."""
|
|
14
|
+
|
|
15
|
+
def __new__(cls, name, bases, dct):
|
|
16
|
+
# Collect and check fields
|
|
17
|
+
member_map = {}
|
|
18
|
+
for key, val in dct.items():
|
|
19
|
+
if not key.startswith("_"):
|
|
20
|
+
val = key if val is None else val
|
|
21
|
+
if not isinstance(val, (int, str)):
|
|
22
|
+
raise TypeError("Enum fields must be str or int.")
|
|
23
|
+
member_map[key] = val
|
|
24
|
+
# Some field values may have been updated
|
|
25
|
+
dct.update(member_map)
|
|
26
|
+
# Create class
|
|
27
|
+
klass = super().__new__(cls, name, bases, dct)
|
|
28
|
+
# Attach some fields
|
|
29
|
+
klass.__fields__ = tuple(member_map)
|
|
30
|
+
klass.__members__ = types.MappingProxyType(member_map) # enums.Enum compat
|
|
31
|
+
# Create bound methods
|
|
32
|
+
for name in ["__dir__", "__iter__", "__getitem__", "__setattr__", "__repr__"]:
|
|
33
|
+
setattr(klass, name, types.MethodType(getattr(cls, name), klass))
|
|
34
|
+
return klass
|
|
35
|
+
|
|
36
|
+
def __dir__(cls):
|
|
37
|
+
# Support dir(enum). Note that this order matches the definition, but dir() makes it alphabetic.
|
|
38
|
+
return cls.__fields__
|
|
39
|
+
|
|
40
|
+
def __iter__(cls):
|
|
41
|
+
# Support list(enum), iterating over the enum, and doing ``x in enum``.
|
|
42
|
+
return iter([getattr(cls, key) for key in cls.__fields__])
|
|
43
|
+
|
|
44
|
+
def __getitem__(cls, key):
|
|
45
|
+
# Support enum[key]
|
|
46
|
+
return cls.__dict__[key]
|
|
47
|
+
|
|
48
|
+
def __repr__(cls):
|
|
49
|
+
if cls is BaseEnum:
|
|
50
|
+
return "<rendercanvas.BaseEnum>"
|
|
51
|
+
pkg = cls.__module__.split(".")[0]
|
|
52
|
+
name = cls.__name__
|
|
53
|
+
options = []
|
|
54
|
+
for key in cls.__fields__:
|
|
55
|
+
val = cls[key]
|
|
56
|
+
options.append(f"'{key}' ({val})" if isinstance(val, int) else f"'{val}'")
|
|
57
|
+
return f"<{pkg}.{name} enum with options: {', '.join(options)}>"
|
|
58
|
+
|
|
59
|
+
def __setattr__(cls, name, value):
|
|
60
|
+
if name.startswith("_"):
|
|
61
|
+
super().__setattr__(name, value)
|
|
62
|
+
else:
|
|
63
|
+
raise RuntimeError("Cannot set values on an enum.")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class BaseEnum(metaclass=EnumType):
|
|
67
|
+
"""Base class for flags and enums.
|
|
68
|
+
|
|
69
|
+
Looks like Python's builtin Enum class, but is simpler; fields are simply ints or strings.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self):
|
|
73
|
+
raise RuntimeError("Cannot instantiate an enum.")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ----- The enums
|
|
77
|
+
|
|
78
|
+
# The Xxxx(BaseEnum) classes are for Sphynx docs, and maybe discovery in interactive sessions.
|
|
79
|
+
# The XxxxEnum Literals are for type checking, and static autocompletion of string args in funcs that accept an enum.
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
CursorShapeEnum = Literal[
|
|
83
|
+
"default",
|
|
84
|
+
"text",
|
|
85
|
+
"crosshair",
|
|
86
|
+
"pointer",
|
|
87
|
+
"ew_resize",
|
|
88
|
+
"ns_resize",
|
|
89
|
+
"nesw_resize",
|
|
90
|
+
"nwse_resize",
|
|
91
|
+
"not_allowed",
|
|
92
|
+
"none",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class CursorShape(BaseEnum):
|
|
97
|
+
"""The CursorShape enum specifies the suppported cursor shapes, following CSS cursor names."""
|
|
98
|
+
|
|
99
|
+
default = None #: The platform-dependent default cursor, typically an arrow.
|
|
100
|
+
text = None #: The text input I-beam cursor shape.
|
|
101
|
+
crosshair = None #:
|
|
102
|
+
pointer = None #: The pointing hand cursor shape.
|
|
103
|
+
ew_resize = "ew-resize" #: The horizontal resize/move arrow shape.
|
|
104
|
+
ns_resize = "ns-resize" #: The vertical resize/move arrow shape.
|
|
105
|
+
nesw_resize = (
|
|
106
|
+
"nesw-resize" #: The top-left to bottom-right diagonal resize/move arrow shape.
|
|
107
|
+
)
|
|
108
|
+
nwse_resize = (
|
|
109
|
+
"nwse-resize" #: The top-right to bottom-left diagonal resize/move arrow shape.
|
|
110
|
+
)
|
|
111
|
+
not_allowed = "not-allowed" #: The operation-not-allowed shape.
|
|
112
|
+
none = "none" #: The cursor is hidden.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
EventTypeEnum = Literal[
|
|
116
|
+
"*",
|
|
117
|
+
"resize",
|
|
118
|
+
"close",
|
|
119
|
+
"pointer_down",
|
|
120
|
+
"pointer_up",
|
|
121
|
+
"pointer_move",
|
|
122
|
+
"pointer_enter",
|
|
123
|
+
"pointer_leave",
|
|
124
|
+
"double_click",
|
|
125
|
+
"wheel",
|
|
126
|
+
"key_down",
|
|
127
|
+
"key_up",
|
|
128
|
+
"char",
|
|
129
|
+
"before_draw",
|
|
130
|
+
"animate",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class EventType(BaseEnum):
|
|
135
|
+
"""The EventType enum specifies the possible events for a RenderCanvas.
|
|
136
|
+
|
|
137
|
+
This includes the events from the jupyter_rfb event spec (see
|
|
138
|
+
https://jupyter-rfb.readthedocs.io/en/stable/events.html) plus some
|
|
139
|
+
rendercanvas-specific events.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
# Jupter_rfb spec
|
|
143
|
+
|
|
144
|
+
resize = None #: The canvas has changed size. Has 'width' and 'height' in logical pixels, 'pixel_ratio'.
|
|
145
|
+
close = None #: The canvas is closed. No additional fields.
|
|
146
|
+
pointer_down = None #: The pointing device is pressed down. Has 'x', 'y', 'button', 'butons', 'modifiers', 'ntouches', 'touches'.
|
|
147
|
+
pointer_up = None #: The pointing device is released. Same fields as pointer_down. Can occur outside of the canvas.
|
|
148
|
+
pointer_move = None #: The pointing device is moved. Same fields as pointer_down. Can occur outside of the canvas if the pointer is currently down.
|
|
149
|
+
pointer_enter = None #: The pointing device is moved into the canvas.
|
|
150
|
+
pointer_leave = None #: The pointing device is moved outside of the canvas (regardless of a button currently being pressed).
|
|
151
|
+
double_click = None #: A double-click / long-tap. This event looks like a pointer event, but without the touches.
|
|
152
|
+
wheel = None #: The mouse-wheel is used (scrolling), or the touchpad/touchscreen is scrolled/pinched. Has 'dx', 'dy', 'x', 'y', 'modifiers'.
|
|
153
|
+
key_down = None #: A key is pressed down. Has 'key', 'modifiers'.
|
|
154
|
+
key_up = None #: A key is released. Has 'key', 'modifiers'.
|
|
155
|
+
|
|
156
|
+
# Pending for the spec, may become part of key_down/key_up
|
|
157
|
+
char = None #: Experimental
|
|
158
|
+
|
|
159
|
+
# Our extra events
|
|
160
|
+
|
|
161
|
+
before_draw = (
|
|
162
|
+
None #: Event emitted right before a draw is performed. Has no extra fields.
|
|
163
|
+
)
|
|
164
|
+
animate = None #: Animation event. Has 'step' representing the step size in seconds. This is stable, except when the 'catch_up' field is nonzero.
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
UpdateModeEnum = Literal["manual", "ondemand", "continuous", "fastest"]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class UpdateMode(BaseEnum):
|
|
171
|
+
"""The UpdateMode enum specifies the different modes to schedule draws for the canvas."""
|
|
172
|
+
|
|
173
|
+
manual = None #: Draw events are never scheduled. Draws only happen when you ``canvas.force_draw()``, and maybe when the GUI system issues them (e.g. when resizing).
|
|
174
|
+
ondemand = None #: Draws are only scheduled when ``canvas.request_draw()`` is called when an update is needed. Safes your laptop battery. Honours ``min_fps`` and ``max_fps``.
|
|
175
|
+
continuous = None #: Continuously schedules draw events, honouring ``max_fps``. Calls to ``canvas.request_draw()`` have no effect.
|
|
176
|
+
fastest = None #: Continuously schedules draw events as fast as possible. Gives high FPS (and drains your battery).
|
|
@@ -5,41 +5,10 @@ The event system.
|
|
|
5
5
|
import time
|
|
6
6
|
from inspect import iscoroutinefunction
|
|
7
7
|
from collections import defaultdict, deque
|
|
8
|
+
from typing import Callable
|
|
8
9
|
|
|
9
|
-
from ._coreutils import log_exception
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class EventType(BaseEnum):
|
|
13
|
-
"""The EventType enum specifies the possible events for a RenderCanvas.
|
|
14
|
-
|
|
15
|
-
This includes the events from the jupyter_rfb event spec (see
|
|
16
|
-
https://jupyter-rfb.readthedocs.io/en/stable/events.html) plus some
|
|
17
|
-
rendercanvas-specific events.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
# Jupter_rfb spec
|
|
21
|
-
|
|
22
|
-
resize = None #: The canvas has changed size. Has 'width' and 'height' in logical pixels, 'pixel_ratio'.
|
|
23
|
-
close = None #: The canvas is closed. No additional fields.
|
|
24
|
-
pointer_down = None #: The pointing device is pressed down. Has 'x', 'y', 'button', 'butons', 'modifiers', 'ntouches', 'touches'.
|
|
25
|
-
pointer_up = None #: The pointing device is released. Same fields as pointer_down. Can occur outside of the canvas.
|
|
26
|
-
pointer_move = None #: The pointing device is moved. Same fields as pointer_down. Can occur outside of the canvas if the pointer is currently down.
|
|
27
|
-
pointer_enter = None #: The pointing device is moved into the canvas.
|
|
28
|
-
pointer_leave = None #: The pointing device is moved outside of the canvas (regardless of a button currently being pressed).
|
|
29
|
-
double_click = None #: A double-click / long-tap. This event looks like a pointer event, but without the touches.
|
|
30
|
-
wheel = None #: The mouse-wheel is used (scrolling), or the touchpad/touchscreen is scrolled/pinched. Has 'dx', 'dy', 'x', 'y', 'modifiers'.
|
|
31
|
-
key_down = None #: A key is pressed down. Has 'key', 'modifiers'.
|
|
32
|
-
key_up = None #: A key is released. Has 'key', 'modifiers'.
|
|
33
|
-
|
|
34
|
-
# Pending for the spec, may become part of key_down/key_up
|
|
35
|
-
char = None #: Experimental
|
|
36
|
-
|
|
37
|
-
# Our extra events
|
|
38
|
-
|
|
39
|
-
before_draw = (
|
|
40
|
-
None #: Event emitted right before a draw is performed. Has no extra fields.
|
|
41
|
-
)
|
|
42
|
-
animate = None #: Animation event. Has 'step' representing the step size in seconds. This is stable, except when the 'catch_up' field is nonzero.
|
|
10
|
+
from ._coreutils import log_exception
|
|
11
|
+
from ._enums import EventType
|
|
43
12
|
|
|
44
13
|
|
|
45
14
|
valid_event_types = set(EventType)
|
|
@@ -76,14 +45,12 @@ class EventEmitter:
|
|
|
76
45
|
self._pending_events.clear()
|
|
77
46
|
self._event_handlers.clear()
|
|
78
47
|
|
|
79
|
-
def add_handler(self, *args, order: float = 0):
|
|
48
|
+
def add_handler(self, *args, order: float = 0) -> Callable:
|
|
80
49
|
"""Register an event handler to receive events.
|
|
81
50
|
|
|
82
51
|
Arguments:
|
|
83
|
-
callback (callable): The event handler. Must accept a single event argument.
|
|
84
|
-
|
|
85
|
-
If you use async callbacks, see :ref:`async` for the limitations.
|
|
86
|
-
*types (list of strings): A list of event types.
|
|
52
|
+
callback (callable, optional): The event handler. Must accept a single event argument.
|
|
53
|
+
*types (list of EventType): A list of event types.
|
|
87
54
|
order (float): Set callback priority order. Callbacks with lower priorities
|
|
88
55
|
are called first. Default is 0. When an event is emitted, callbacks with
|
|
89
56
|
the same priority are called in the order that they were added.
|
|
@@ -121,9 +88,11 @@ class EventEmitter:
|
|
|
121
88
|
|
|
122
89
|
"""
|
|
123
90
|
order = float(order)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
91
|
+
callback = None
|
|
92
|
+
types = args
|
|
93
|
+
if len(args) > 0 and callable(args[0]):
|
|
94
|
+
callback = args[0]
|
|
95
|
+
types = args[1:]
|
|
127
96
|
|
|
128
97
|
if not types:
|
|
129
98
|
raise TypeError("No event types are given to add_event_handler.")
|
|
@@ -133,15 +102,19 @@ class EventEmitter:
|
|
|
133
102
|
if not (type == "*" or type in valid_event_types):
|
|
134
103
|
raise ValueError(f"Adding handler with invalid event_type: '{type}'")
|
|
135
104
|
|
|
136
|
-
def decorator(_callback):
|
|
105
|
+
def decorator(_callback: Callable):
|
|
137
106
|
self._add_handler(_callback, order, *types)
|
|
138
107
|
return _callback
|
|
139
108
|
|
|
140
|
-
if
|
|
109
|
+
if callback is None:
|
|
141
110
|
return decorator
|
|
142
111
|
return decorator(callback)
|
|
143
112
|
|
|
144
113
|
def _add_handler(self, callback, order, *types):
|
|
114
|
+
if iscoroutinefunction(callback):
|
|
115
|
+
raise TypeError(
|
|
116
|
+
"Coroutines cannot be callbacks (anymore). Instead, you can use a regular callback that calls `loop.add_task(coro)`."
|
|
117
|
+
)
|
|
145
118
|
if self._closed:
|
|
146
119
|
return
|
|
147
120
|
self.remove_handler(callback, *types)
|
|
@@ -195,7 +168,7 @@ class EventEmitter:
|
|
|
195
168
|
|
|
196
169
|
self._pending_events.append(event)
|
|
197
170
|
|
|
198
|
-
|
|
171
|
+
def flush(self):
|
|
199
172
|
"""Dispatch all pending events.
|
|
200
173
|
|
|
201
174
|
This should generally be left to the scheduler.
|
|
@@ -205,9 +178,9 @@ class EventEmitter:
|
|
|
205
178
|
event = self._pending_events.popleft()
|
|
206
179
|
except IndexError:
|
|
207
180
|
break
|
|
208
|
-
|
|
181
|
+
self.emit(event)
|
|
209
182
|
|
|
210
|
-
|
|
183
|
+
def emit(self, event):
|
|
211
184
|
"""Directly emit the given event.
|
|
212
185
|
|
|
213
186
|
In most cases events should be submitted, so that they are flushed
|
|
@@ -221,15 +194,12 @@ class EventEmitter:
|
|
|
221
194
|
if event.get("stop_propagation", False):
|
|
222
195
|
break
|
|
223
196
|
with log_exception(f"Error during handling {event_type} event"):
|
|
224
|
-
|
|
225
|
-
await callback(event)
|
|
226
|
-
else:
|
|
227
|
-
callback(event)
|
|
197
|
+
callback(event)
|
|
228
198
|
# Close?
|
|
229
199
|
if event_type == "close":
|
|
230
200
|
self._release()
|
|
231
201
|
|
|
232
|
-
|
|
202
|
+
def close(self):
|
|
233
203
|
"""Close the event handler.
|
|
234
204
|
|
|
235
205
|
Drops all pending events, send the close event, and disables the emitter.
|
|
@@ -238,4 +208,4 @@ class EventEmitter:
|
|
|
238
208
|
if not self._closed:
|
|
239
209
|
self._pending_events.clear()
|
|
240
210
|
self.submit({"event_type": "close"})
|
|
241
|
-
|
|
211
|
+
self.flush()
|
|
@@ -117,7 +117,7 @@ class BaseLoop:
|
|
|
117
117
|
closed_canvas_ids = set(event_emitters) - set(new_event_emitters)
|
|
118
118
|
for canvas_id in closed_canvas_ids:
|
|
119
119
|
events = event_emitters[canvas_id]
|
|
120
|
-
|
|
120
|
+
events.close()
|
|
121
121
|
|
|
122
122
|
# Keep canvases alive
|
|
123
123
|
for canvas in canvases:
|
|
@@ -139,9 +139,14 @@ class BaseLoop:
|
|
|
139
139
|
break
|
|
140
140
|
elif self.__should_stop:
|
|
141
141
|
# Close all remaining canvases. Loop will stop in a next iteration.
|
|
142
|
+
# We store a flag on the canvas, that we only use here.
|
|
142
143
|
for canvas in self.get_canvases():
|
|
143
|
-
|
|
144
|
-
canvas._rc_closed_by_loop
|
|
144
|
+
try:
|
|
145
|
+
closed_by_loop = canvas._rc_closed_by_loop # type: ignore
|
|
146
|
+
except AttributeError:
|
|
147
|
+
closed_by_loop = False
|
|
148
|
+
if not closed_by_loop:
|
|
149
|
+
canvas._rc_closed_by_loop = True # type: ignore
|
|
145
150
|
canvas.close()
|
|
146
151
|
del canvas
|
|
147
152
|
|
|
@@ -372,7 +377,7 @@ class BaseLoop:
|
|
|
372
377
|
This method is optional. A subclass must either implement ``_rc_add_task`` or ``_rc_call_later``.
|
|
373
378
|
|
|
374
379
|
* If you implememt this, make ``_rc_add_task()`` call ``super()._rc_add_task()``.
|
|
375
|
-
* If delay is zero, this should behave like "
|
|
380
|
+
* If delay is zero, this should behave like "call_soon".
|
|
376
381
|
* No need to catch errors from the callback; that's dealt with internally.
|
|
377
382
|
* Return None.
|
|
378
383
|
"""
|