Skip to content

Commit 6b96641

Browse files
committed
More natural instantiation
1 parent b11190d commit 6b96641

File tree

6 files changed

+79
-47
lines changed

6 files changed

+79
-47
lines changed

rendercanvas/base.py

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,37 +37,27 @@ class BaseRenderCanvas:
3737
3838
"""
3939

40-
#
41-
__canvas_kwargs = dict(
40+
def __init__(
41+
self,
42+
*args,
4243
size=(640, 480),
4344
title="$backend",
4445
update_mode="ondemand",
4546
min_fps=1.0,
4647
max_fps=30.0,
4748
vsync=True,
4849
present_method=None,
49-
)
50-
51-
def __init__(self, *args, **kwargs):
52-
# Extract canvas kwargs
53-
canvas_kwargs = {}
54-
for key, default in BaseRenderCanvas.__canvas_kwargs.items():
55-
val = kwargs.pop(key, default)
56-
if val is None:
57-
val = default
58-
canvas_kwargs[key] = val
59-
50+
**kwargs,
51+
):
6052
# Initialize superclass. Note that super() can be e.g. a QWidget, RemoteFrameBuffer, or object.
6153
super().__init__(*args, **kwargs)
6254

63-
# If this is a wrapper, it should pass the canvas kwargs to the subwidget.
55+
# If this is a wrapper, no need to initialize furher
6456
if isinstance(self, WrapperRenderCanvas):
65-
self._rc_init(**canvas_kwargs)
66-
self._events = self._subwidget._events
6757
return
6858

6959
# The vsync is not-so-elegantly strored on the canvas, and picked up by wgou's canvas contex.
70-
self._vsync = bool(canvas_kwargs["vsync"])
60+
self._vsync = bool(vsync)
7161

7262
# Variables and flags used internally
7363
self.__is_drawing = False
@@ -86,17 +76,32 @@ def __init__(self, *args, **kwargs):
8676
self,
8777
self._events,
8878
self._rc_get_loop(),
89-
min_fps=canvas_kwargs["min_fps"],
90-
max_fps=canvas_kwargs["max_fps"],
91-
mode=canvas_kwargs["update_mode"],
79+
min_fps=min_fps,
80+
max_fps=max_fps,
81+
mode=update_mode,
9282
)
9383

94-
# Initialze the canvas subclass
95-
self._rc_init(**canvas_kwargs)
84+
# We cannot initialize the size and title now, because the subclass may not have done
85+
# the initialization to support this. So we require the subclass to call _final_canvas_init.
86+
self.__kwargs_for_later = dict(size=size, title=title)
87+
88+
def _final_canvas_init(self):
89+
"""Must be called by the subclasses at the end of their ``__init__``.
9690
97-
# Finalize the initialization
98-
self.set_logical_size(*canvas_kwargs["size"])
99-
self.set_title(canvas_kwargs["title"])
91+
This sets the canvas size and title, which must happen *after* the widget itself
92+
is initialized. Doing this automatically can be done with a metaclass, but let's keep it simple.
93+
"""
94+
# Pop kwargs
95+
try:
96+
kwargs = self.__kwargs_for_later
97+
except AttributeError:
98+
return
99+
else:
100+
del self.__kwargs_for_later
101+
# Apply
102+
if not isinstance(self, WrapperRenderCanvas):
103+
self.set_logical_size(*kwargs["size"])
104+
self.set_title(kwargs["title"])
100105

101106
def __del__(self):
102107
# On delete, we call the custom close method.
@@ -366,14 +371,6 @@ def set_title(self, title):
366371

367372
# %% Methods for the subclass to implement
368373

369-
def _rc_init(self, **canvas_kwargs):
370-
"""Method to initialize the canvas.
371-
372-
This method is called near the end of the initialization
373-
process, but before setting things like size and title.
374-
"""
375-
pass
376-
377374
def _rc_get_loop(self):
378375
"""Get the loop instance for this backend.
379376
@@ -438,6 +435,9 @@ def _rc_set_logical_size(self, width, height):
438435
def _rc_close(self):
439436
"""Close the canvas.
440437
438+
For widgets that are wrapped by a WrapperRenderCanvas, this should probably
439+
close the wrapper instead.
440+
441441
Note that ``BaseRenderCanvas`` implements the ``close()`` method, which
442442
is a rather common name; it may be necessary to re-implement that too.
443443
"""
@@ -450,6 +450,9 @@ def _rc_is_closed(self):
450450
def _rc_set_title(self, title):
451451
"""Set the canvas title. May be ignored when it makes no sense.
452452
453+
For widgets that are wrapped by a WrapperRenderCanvas, this should probably
454+
set the title of the wrapper instead.
455+
453456
The default implementation does nothing.
454457
"""
455458
pass
@@ -462,6 +465,17 @@ class WrapperRenderCanvas(BaseRenderCanvas):
462465
Wrapper classes should not implement any of the ``_rc_`` methods.
463466
"""
464467

468+
# Events
469+
470+
def add_event_handler(self, *args, **kwargs):
471+
return self._subwidget._events.add_handler(*args, **kwargs)
472+
473+
def remove_event_handler(self, *args, **kwargs):
474+
return self._subwidget._events.remove_handler(*args, **kwargs)
475+
476+
def submit_event(self, event):
477+
return self._subwidget._events.submit(event)
478+
465479
# Must implement
466480

467481
def get_context(self, *args, **kwargs):

rendercanvas/glfw.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ class GlfwRenderCanvas(BaseRenderCanvas):
146146

147147
# See https://www.glfw.org/docs/latest/group__window.html
148148

149-
def _rc_init(self, *, present_method, **_):
149+
def __init__(self, *args, present_method=None, **kwargs):
150+
super().__init__(*args, **kwargs)
151+
150152
loop.init_glfw()
151153

152154
if present_method == "image":
@@ -195,6 +197,9 @@ def _rc_init(self, *, present_method, **_):
195197
self._pixel_ratio = -1
196198
self._screen_size_is_logical = False
197199

200+
# Set size, title, etc.
201+
self._final_canvas_init()
202+
198203
def _on_window_dirty(self, *args):
199204
self.request_draw()
200205

rendercanvas/jupyter.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
class JupyterRenderCanvas(BaseRenderCanvas, RemoteFrameBuffer):
1818
"""An ipywidgets widget providing a render canvas. Needs the jupyter_rfb library."""
1919

20-
def _rc_init(self, **_):
20+
def __init__(self, *args, **kwargs):
21+
super().__init__(*args, **kwargs)
22+
2123
# Internal variables
2224
self._last_image = None
2325
self._pixel_ratio = 1
@@ -28,6 +30,9 @@ def _rc_init(self, **_):
2830
# Register so this can be display'ed when run() is called
2931
loop._pending_jupyter_canvases.append(weakref.ref(self))
3032

33+
# Set size, title, etc.
34+
self._final_canvas_init()
35+
3136
def get_frame(self):
3237
# The _draw_frame_and_present() does the drawing and then calls
3338
# present_context.present(), which calls our present() method.

rendercanvas/offscreen.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ class ManualOffscreenRenderCanvas(BaseRenderCanvas):
1010
def __init__(self, *args, pixel_ratio=1.0, **kwargs):
1111
super().__init__(*args, **kwargs)
1212
self._pixel_ratio = pixel_ratio
13-
14-
def _rc_init(self, **_):
1513
self._closed = False
1614
self._last_image = None
15+
self._final_canvas_init()
1716

1817
# %% Methods to implement RenderCanvas
1918

rendercanvas/qt.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ def enable_hidpi():
147147
class QRenderWidget(BaseRenderCanvas, QtWidgets.QWidget):
148148
"""A QWidget representing a render canvas that can be embedded in a Qt application."""
149149

150-
def _rc_init(self, *, present_method, **_):
150+
def __init__(self, *args, present_method=None, **kwargs):
151+
super().__init__(*args, **kwargs)
152+
151153
# Determine present method
152154
self._surface_ids = None
153155
if not present_method:
@@ -175,6 +177,9 @@ def _rc_init(self, *, present_method, **_):
175177
self.setMouseTracking(True)
176178
self.setFocusPolicy(FocusPolicy.StrongFocus)
177179

180+
# Set size, title, etc.
181+
self._final_canvas_init()
182+
178183
def _get_surface_ids(self):
179184
if sys.platform.startswith("win") or sys.platform.startswith("darwin"):
180185
return {
@@ -466,14 +471,13 @@ class QRenderCanvas(WrapperRenderCanvas, QtWidgets.QWidget):
466471
# size can be set to subpixel (logical) values, without being able to
467472
# detect this. See https://github.com/pygfx/wgpu-py/pull/68
468473

469-
def __init__(self, *args, **kwargs):
474+
def __init__(self, parent=None, **kwargs):
470475
# When using Qt, there needs to be an
471476
# application before any widget is created
472477
loop.init_qt()
473-
super().__init__(*args, **kwargs)
478+
super().__init__(parent)
474479

475-
def _rc_init(self, **canvas_kwargs):
476-
self._subwidget = QRenderWidget(self, **canvas_kwargs)
480+
self._subwidget = QRenderWidget(self, **kwargs)
477481

478482
self.setAttribute(WA_DeleteOnClose, True)
479483
self.setMouseTracking(True)
@@ -487,7 +491,9 @@ def _rc_init(self, **canvas_kwargs):
487491
layout.setContentsMargins(0, 0, 0, 0)
488492
self.setLayout(layout)
489493
layout.addWidget(self._subwidget)
494+
490495
self.show()
496+
self._final_canvas_init()
491497

492498
# Qt methods
493499

rendercanvas/wx.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ def enable_hidpi():
128128
class WxRenderWidget(BaseRenderCanvas, wx.Window):
129129
"""A wx Window representing a render canvas that can be embedded in a wx application."""
130130

131-
def _rc_init(self, present_method=None, **_):
131+
def __init__(self, *args, present_method=None, **kwargs):
132+
super().__init__(*args, **kwargs)
133+
132134
# Determine present method
133135
self._surface_ids = None
134136
if not present_method:
@@ -161,6 +163,7 @@ def _rc_init(self, present_method=None, **_):
161163
self.Bind(wx.EVT_MOTION, self._on_mouse_move)
162164

163165
self.Show()
166+
self._final_canvas_init()
164167

165168
def _on_resize_done(self, *args):
166169
self._draw_lock = False
@@ -425,14 +428,14 @@ class WxRenderCanvas(WrapperRenderCanvas, wx.Frame):
425428

426429
# Most of this is proxying stuff to the inner widget.
427430

428-
def __init__(self, *, parent=None, **kwargs):
431+
def __init__(self, parent=None, **kwargs):
429432
loop.init_wx()
430-
super().__init__(parent, **kwargs)
433+
super().__init__(parent)
431434

432-
def _rc_init(self, **canvas_kwargs):
433-
self._subwidget = WxRenderWidget(parent=self, **canvas_kwargs)
435+
self._subwidget = WxRenderWidget(parent=self, **kwargs)
434436
self.Bind(wx.EVT_CLOSE, lambda e: self.Destroy())
435437
self.Show()
438+
self._final_canvas_init()
436439

437440
# wx methods
438441

0 commit comments

Comments
 (0)