Skip to content

Commit

Permalink
Merge interface into BaseRenderCanvas (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
almarklein authored Nov 6, 2024
1 parent 6257b49 commit 7739f4a
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 86 deletions.
11 changes: 4 additions & 7 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
rendercanvas base classes
=========================

.. autoclass:: rendercanvas.base.RenderCanvasInterface
:members:

.. autoclass:: rendercanvas.base.BaseRenderCanvas
:members:

.. .. autoclass:: rendercanvas.base.BaseLoop
.. :members:
.. autoclass:: rendercanvas.base.BaseLoop
:members:

.. .. autoclass:: rendercanvas.base.BaseTimer
.. :members:
.. autoclass:: rendercanvas.base.BaseTimer
:members:
3 changes: 1 addition & 2 deletions rendercanvas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
from ._version import __version__, version_info
from . import _gui_utils
from ._events import EventType
from .base import RenderCanvasInterface, BaseRenderCanvas, BaseLoop, BaseTimer
from .base import BaseRenderCanvas, BaseLoop, BaseTimer

__all__ = [
"RenderCanvasInterface",
"BaseRenderCanvas",
"EventType",
"BaseLoop",
Expand Down
132 changes: 60 additions & 72 deletions rendercanvas/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,70 @@
from ._gui_utils import log_exception


class RenderCanvasInterface:
"""The minimal interface to be a valid canvas.
class BaseRenderCanvas:
"""The base canvas class.
This class provides a uniform canvas API so render systems can use
code that is portable accross multiple GUI libraries and canvas targets.
Any object that implements these methods is a canvas that wgpu can work with.
The object does not even have to derive from this class.
Arguments:
update_mode (EventType): The mode for scheduling draws and events. Default 'ondemand'.
min_fps (float): A minimal frames-per-second to use when the ``update_mode`` is 'ondemand'.
The default is 1: even without draws requested, it still draws every second.
max_fps (float): A maximal frames-per-second to use when the ``update_mode`` is 'ondemand' or 'continuous'.
The default is 30, which is usually enough.
vsync (bool): Whether to sync the draw with the monitor update. Helps
against screen tearing, but can reduce fps. Default True.
present_method (str | None): The method to present the rendered image.
Can be set to 'screen' or 'image'. Default None (auto-select).
In most cases it's more convenient to subclass :class:`BaseRenderCanvas <wgpu.gui.BaseRenderCanvas>`.
"""

def __init__(
self,
*args,
update_mode="ondemand",
min_fps=1.0,
max_fps=30.0,
vsync=True,
present_method=None,
**kwargs,
):
super().__init__(*args, **kwargs)
self._vsync = bool(vsync)
present_method # noqa - We just catch the arg here in case a backend does implement it

# Canvas
self.__raw_title = ""
self.__title_kwargs = {
"fps": "?",
"backend": self.__class__.__name__,
}

self.__is_drawing = False
self._events = EventEmitter()
self._scheduler = None
loop = self._get_loop()
if loop:
self._scheduler = Scheduler(
self, loop, min_fps=min_fps, max_fps=max_fps, mode=update_mode
)

def __del__(self):
# On delete, we call the custom close method.
try:
self.close()
except Exception:
pass
# Since this is sometimes used in a multiple inheritance, the
# superclass may (or may not) have a __del__ method.
try:
super().__del__()
except Exception:
pass

# === Implement WgpuCanvasInterface

_canvas_context = None # set in get_context()

def get_present_info(self):
Expand Down Expand Up @@ -82,69 +137,6 @@ def present_image(self, image, **kwargs):
"""
raise NotImplementedError()


class BaseRenderCanvas(RenderCanvasInterface):
"""The base canvas class.
This class provides a uniform canvas API so render systems can use
code that is portable accross multiple GUI libraries and canvas targets.
Arguments:
update_mode (EventType): The mode for scheduling draws and events. Default 'ondemand'.
min_fps (float): A minimal frames-per-second to use when the ``update_mode`` is 'ondemand'.
The default is 1: even without draws requested, it still draws every second.
max_fps (float): A maximal frames-per-second to use when the ``update_mode`` is 'ondemand' or 'continuous'.
The default is 30, which is usually enough.
vsync (bool): Whether to sync the draw with the monitor update. Helps
against screen tearing, but can reduce fps. Default True.
present_method (str | None): The method to present the rendered image.
Can be set to 'screen' or 'image'. Default None (auto-select).
"""

def __init__(
self,
*args,
update_mode="ondemand",
min_fps=1.0,
max_fps=30.0,
vsync=True,
present_method=None,
**kwargs,
):
super().__init__(*args, **kwargs)
self._vsync = bool(vsync)
present_method # noqa - We just catch the arg here in case a backend does implement it

# Canvas
self.__raw_title = ""
self.__title_kwargs = {
"fps": "?",
"backend": self.__class__.__name__,
}

self.__is_drawing = False
self._events = EventEmitter()
self._scheduler = None
loop = self._get_loop()
if loop:
self._scheduler = Scheduler(
self, loop, min_fps=min_fps, max_fps=max_fps, mode=update_mode
)

def __del__(self):
# On delete, we call the custom close method.
try:
self.close()
except Exception:
pass
# Since this is sometimes used in a multiple inheritance, the
# superclass may (or may not) have a __del__ method.
try:
super().__del__()
except Exception:
pass

# === Events

def add_event_handler(self, *args, **kwargs):
Expand Down Expand Up @@ -329,10 +321,6 @@ def _force_draw(self):

# todo: we require subclasses to implement public methods, while everywhere else the implementable-methods are private.

def get_physical_size(self):
"""Get the physical size in integer pixels."""
raise NotImplementedError()

def get_logical_size(self):
"""Get the logical size in float pixels."""
raise NotImplementedError()
Expand Down
2 changes: 0 additions & 2 deletions rendercanvas/jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ def _force_draw(self):
if array is not None:
self._rfb_send_frame(array)

# Implementation needed for RenderCanvasInterface

def get_present_info(self):
# Use a format that maps well to PNG: rgba8norm. Use srgb for
# perceptive color mapping. This is the common colorspace for
Expand Down
4 changes: 2 additions & 2 deletions rendercanvas/utils/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
def setup_drawing_sync(canvas, power_preference="high-performance", limits=None):
"""Setup to draw a rotating cube on the given canvas.
The given canvas must implement RenderCanvasInterface, but nothing more.
The given canvas must implement WgpuCanvasInterface, but nothing more.
Returns the draw function.
"""

Expand All @@ -35,7 +35,7 @@ def setup_drawing_sync(canvas, power_preference="high-performance", limits=None)
async def setup_drawing_async(canvas, limits=None):
"""Setup to async-draw a rotating cube on the given canvas.
The given canvas must implement RenderCanvasInterface, but nothing more.
The given canvas must implement WgpuCanvasInterface, but nothing more.
Returns the draw function.
"""

Expand Down
2 changes: 1 addition & 1 deletion tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


def test_base_canvas_context():
assert hasattr(rendercanvas.RenderCanvasInterface, "get_context")
assert hasattr(rendercanvas.BaseRenderCanvas, "get_context")


def test_canvas_get_context_needs_backend_to_be_selected():
Expand Down

0 comments on commit 7739f4a

Please sign in to comment.