Skip to content
Merged
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
36 changes: 32 additions & 4 deletions docs/backends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,11 @@ additional dependencies. Currently only presenting a bitmap is supported, as
shown in the examples :doc:`noise.py <gallery/noise>` and :doc:`snake.py<gallery/snake>`.
Support for wgpu is underway.

An HTMLCanvasElement is assumed to be present in the
DOM. By default it connects to the canvas with id "canvas", but a
different id or element can also be provided using ``RenderCanvas(canvas_element)``.
The ``PyodideRenderCanvas`` has a few additional methods that are specific to the browser:
``set_css_width``, ``set_css_height``, ``set_resizable``, and ``show_titlebar``.

The backend will render to an HTML ``<canvas>``. This can be provided with ``RenderCanvas(canvas_element=...)``,
either by providing the element as an object, or via it's id. By default, it connects with the element with id "canvas".

An example using PyScript (which uses Pyodide):

Expand All @@ -312,8 +314,34 @@ An example using PyScript (which uses Pyodide):
</body>
</html>

The 'canvas_element' can also be a ``<div>`` element with the class 'rendercanvas-wrapper'. In this
case the canvas will be created inside that wrapper, plus additional things to support features like
a title bar and manual resizing. These features can be enabled with the 'has-titlebar' and 'is-resizable' css classes.
The wrapper can also contain placeholder elements that will be deleted once the canvas is loaded:

.. code-block:: html

<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script type="module" src="https://pyscript.net/releases/2025.11.1/core.js"></script>
</head>
<body>
<div id="canvas" class='renderview-wrapper is-resizable has-titlebar' style="width: 80%; height: 480px;">
<p style='width:100%; height:100%; background:#aaa; display: flex; justify-content: center; align-items: center; font-size:150%'>
Loading ...
</p>
</div>
<br>
<script type="py" src="yourcode.py" config='{"packages": ["numpy", "rendercanvas"]}'>
</script>
</body>
</html>


An example using Pyodide directly:
It is also possible to use Pyodide directly. It requires a bit more plumbing.
Similar as with PyScript, you can chose between a ``<canvas>`` and a ``<div class='renderview-wrapper'>``:

.. code-block:: html

Expand Down
18 changes: 10 additions & 8 deletions docs/static/_pyodide_iframe.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
</head>

<body>
<dialog id="loading" style='outline: none; border: none; background: transparent;'>
<h1>Loading...</h1>
</dialog>
<canvas id='canvas' style='width:calc(100% - 20px); height: 450px; background-color: #ddd;'></canvas>

<div id="canvas" class='renderview-wrapper is-resizable has-titlebar'
style="width:calc(100% - 20px); height: 450px;">
<p id="loader"
style='width:100%; height:100%; background:#ddd; display: flex; justify-content: center; align-items: center; font-size:150%'>
Loading ...
</p>
</div>

<script type="text/javascript">
async function main() {
let loading = document.getElementById('loading');
loading.showModal();
try {
let example_name = document.location.hash.slice(1);
pythonCode = await (await fetch(example_name)).text();
Expand All @@ -28,9 +31,8 @@ <h1>Loading...</h1>
await micropip.install("rendercanvas");
// Run the Python code async because some calls are async it seems.
pyodide.runPythonAsync(pythonCode);
loading.close();
} catch (err) {
loading.innerHTML = "Failed to load: " + err;
document.getElementById('loader').innerHTML = "Failed to load: " + err;
}
}
main();
Expand Down
17 changes: 6 additions & 11 deletions examples/pyscript.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@
</head>

<body>

<dialog id="loading" style='outline: none; border: none; background: transparent;'>
<h1>Loading...</h1>
</dialog>
<script type="module">
const loading = document.getElementById('loading');
addEventListener('py:ready', () => loading.close());
loading.showModal();
</script>

<p>
This example demonstrates using PyScript. It needs to be loaded through
a web-server for it to access the Python file.
Expand All @@ -29,7 +19,12 @@ <h1>Loading...</h1>
there.
</p>

<canvas id="canvas" style="background:#aaa; width: 90%; height: 480px;"></canvas>
<div id="canvas" class='renderview-wrapper is-resizable' style="width: 640px; height: 480px;">
<p
style='width:100%; height:100%; background:#aaa; display: flex; justify-content: center; align-items: center; font-size:150%'>
Loading ...
</p>
</div>
<br>
<script type="py" src="drag.py" config='{"packages": ["numpy", "rendercanvas"]}'>
</script>
Expand Down
18 changes: 4 additions & 14 deletions examples/serve_browser_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,10 @@ def get_html_index():

<body>
<a href="/">Back to list</a><br><br>

<p>
docstring
</p>
<dialog id="loading" style='outline: none; border: none; background: transparent;'>
<h1>Loading...</h1>
</dialog>
<script type="module">
const loading = document.getElementById('loading');
addEventListener('py:ready', () => loading.close());
loading.showModal();
</script>

<canvas id="canvas" style="background:#aaa; width: 90%; height: 480px;"></canvas>
<p>docstring</p>
<div id="canvas" class='renderview-wrapper is-resizable has-titlebar' style="width: 80%; height: 480px;">
<p style='width:100%; height:100%; background:#aaa; display: flex; justify-content: center; align-items: center; font-size:150%'>Loading ...</p>
</div>
<script type="py" src="example.py" ,
config='{"packages": ["numpy", "rendercanvas"]}'>
</script>
Expand Down
7 changes: 6 additions & 1 deletion examples/snake.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
from rendercanvas.auto import RenderCanvas, loop


canvas = RenderCanvas(present_method=None, size=(640, 480), update_mode="continuous")
canvas = RenderCanvas(
title="Snake on $backend",
present_method=None,
size=(640, 480),
update_mode="continuous",
)

context = canvas.get_bitmap_context()

Expand Down
60 changes: 60 additions & 0 deletions rendercanvas/core/renderview-pyodide.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* global BaseRenderView Element HTMLDivElement HTMLCanvasElement */

/**
* Adapter between the JS canvas and the Python canvas.
*/
class PyodideRenderView extends BaseRenderView {
constructor (canvasElement, pycanvas) {
let canvasId = null
let viewElement = null
let wrapperElement = null

// Turn element id into the element
if (typeof canvasElement === 'string' || canvasElement instanceof String) {
canvasId = canvasElement
canvasElement = document.getElementById(canvasId)
if (!canvasElement) {
throw new Error(`Given canvas id '${canvasId}' does not match an element in the DOM.`)
}
}

// Get whether we have a wrapper of an actual canvas
if (canvasElement instanceof HTMLCanvasElement) {
viewElement = canvasElement
} else if (canvasElement instanceof Element && canvasElement.classList.contains('renderview-wrapper')) {
wrapperElement = canvasElement
viewElement = document.createElement('canvas')
} else {
let repr = `${canvasElement}`
if (canvasId) {
repr = `id '${canvasId}' -> ` + repr
}
if (canvasElement instanceof HTMLDivElement) {
repr += ' (Maybe you forgot to add class=\'renderview-wrapper\'?)'
}
throw new Error('Given canvas element does not look like a <canvas>: ' + repr)
}

super(viewElement, wrapperElement)
this.pycanvas = pycanvas
this.setThrottle(0)
}

onVisibleChanged (visible) {
this.pycanvas._onVisibleChanged(visible)
}

onResize (physicalWidth, physicalHeight, pixelRatio) {
// Set canvas physical size
this.viewElement.width = physicalWidth
this.viewElement.height = physicalHeight
// Notify canvas, so the render code knows the size
this.pycanvas._onResize(physicalWidth, physicalHeight, pixelRatio)
}

onEvent (event) {
this.pycanvas._onEvent(event)
}
}

window.PyodideRenderView = PyodideRenderView
37 changes: 19 additions & 18 deletions rendercanvas/core/renderview.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
div.renderview-canvas-wrapper {
div.renderview-wrapper {
display: inline-block;
position: relative;
box-sizing: border-box;
Expand All @@ -7,63 +7,64 @@ div.renderview-canvas-wrapper {
min-height: 32px;
}

div.renderview-canvas-wrapper img,
div.renderview-canvas-wrapper canvas {
div.renderview-wrapper img,
div.renderview-wrapper canvas {
display: block;
box-sizing: border-box;
width: 100%;
height: 100%;
border-radius: 6px;
background: #777;
}

div.renderview-canvas-wrapper div.renderview-top {
div.renderview-wrapper div.renderview-top {
display: none;
position: absolute;
box-sizing: border-box;
z-index: 2;
top: -1.5em;
height: 1.5em;
width: 100%;
border-top: 1px solid rgba(128, 128, 128, 0.5);
border-radius: 6px 6px 0 0;
box-shadow: 0px -2px 0px 0px rgba(128, 128, 128, 0.5);
}

div.renderview-canvas-wrapper div.renderview-top span {
div.renderview-wrapper div.renderview-top span {
display: inline-block;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 0.5em 0 0.5em;
padding: 0.1em 0.5em;
width: 100%;
}

div.renderview-canvas-wrapper div.renderview-resizer {
div.renderview-wrapper div.renderview-resizer {
display: none;
position: absolute;
box-sizing: border-box;
z-index: 3;
bottom: 0;
right: 0;
width: 16px;
height: 16px;
width: 14px;
height: 14px;
cursor: nwse-resize;
}

div.renderview-canvas-wrapper.has-titlebar {
margin-top: 2em;
div.renderview-wrapper.has-titlebar {
margin-top: 2em !important;
}

div.renderview-canvas-wrapper.has-titlebar div.renderview-top {
div.renderview-wrapper.has-titlebar div.renderview-top {
display: block;
}

div.renderview-canvas-wrapper.has-titlebar img,
div.renderview-canvas-wrapper.has-titlebar canvas {
border-top-left-radius: 0;
border-top-right-radius: 0;
div.renderview-wrapper.has-titlebar img,
div.renderview-wrapper.has-titlebar canvas {
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
}

div.renderview-canvas-wrapper.is-resizable div.renderview-resizer {
div.renderview-wrapper.is-resizable div.renderview-resizer {
display: block;
}
Loading
Loading