From 039d8418f30ffdbff15dc14817b57ce20dce8842 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 25 Mar 2026 13:18:40 +0100 Subject: [PATCH 1/2] Update renderview.js --- CONTRIBUTING.md | 2 +- rendercanvas/core/renderview.css | 34 ++++++--- rendercanvas/core/renderview.js | 123 +++++++++++++++++-------------- 3 files changed, 90 insertions(+), 69 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ee21db..84aa7dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,5 +133,5 @@ that uses `standardjs`. To format and lint your code locally: * Install npm if you don't have it already. * `npm install --global standard` -* `standard rendercanvas` (run this before committing changes to the JavaScript code) +* `standard rendercanvas --fix` (run this before committing changes to the JavaScript code) diff --git a/rendercanvas/core/renderview.css b/rendercanvas/core/renderview.css index 8335452..de27d62 100644 --- a/rendercanvas/core/renderview.css +++ b/rendercanvas/core/renderview.css @@ -1,3 +1,11 @@ +/************************************************************************************************* + renderview.css + + This file is dedicated to the public domain under CC0 1.0. + This file is developed at https://github.com/pygfx/renderview, please contribute changes there. + + *************************************************************************************************/ + div.renderview-wrapper { display: inline-block; position: relative; @@ -7,17 +15,21 @@ div.renderview-wrapper { min-height: 32px; } -div.renderview-wrapper img, -div.renderview-wrapper canvas { +div.renderview-wrapper .renderview-view { display: block; box-sizing: border-box; width: 100%; height: 100%; + background: none; border-radius: 6px; - background: #777; + border: 1px solid rgba(127, 127, 127, 0.2); +} + +div.renderview-wrapper .renderview-hidden { + display: none; } -div.renderview-wrapper div.renderview-top { +div.renderview-wrapper .renderview-top { display: none; position: absolute; box-sizing: border-box; @@ -26,10 +38,11 @@ div.renderview-wrapper div.renderview-top { height: 1.5em; width: 100%; border-radius: 6px 6px 0 0; - box-shadow: 0px -2px 0px 0px rgba(128, 128, 128, 0.5); + border: 1px solid rgba(127, 127, 127, 0.2); + border-bottom: 0; } -div.renderview-wrapper div.renderview-top span { +div.renderview-wrapper .renderview-top span { display: inline-block; box-sizing: border-box; overflow: hidden; @@ -39,7 +52,7 @@ div.renderview-wrapper div.renderview-top span { width: 100%; } -div.renderview-wrapper div.renderview-resizer { +div.renderview-wrapper .renderview-resizer { display: none; position: absolute; box-sizing: border-box; @@ -55,16 +68,15 @@ div.renderview-wrapper.has-titlebar { margin-top: 2em !important; } -div.renderview-wrapper.has-titlebar div.renderview-top { +div.renderview-wrapper.has-titlebar .renderview-top { display: block; } -div.renderview-wrapper.has-titlebar img, -div.renderview-wrapper.has-titlebar canvas { +div.renderview-wrapper.has-titlebar .renderview-view { border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } -div.renderview-wrapper.is-resizable div.renderview-resizer { +div.renderview-wrapper.is-resizable .renderview-resizer { display: block; } \ No newline at end of file diff --git a/rendercanvas/core/renderview.js b/rendercanvas/core/renderview.js index f955257..598c594 100644 --- a/rendercanvas/core/renderview.js +++ b/rendercanvas/core/renderview.js @@ -1,6 +1,9 @@ /************************************************************************************************* renderview.js + This file is dedicated to the public domain under CC0 1.0. + This file is developed at https://github.com/pygfx/renderview, please contribute changes there. + This module implements a common event spec for render targets in a browser. The code is written with little assumptions about the application, so that it can be shared between different use-cases, such as rendercanvas backends @@ -66,7 +69,7 @@ function getModifiers (ev) { } function getTimestamp () { - return Date.now() / 1000 + return performance.now() / 1000 } function arraysEqual (a, b) { @@ -76,12 +79,7 @@ function arraysEqual (a, b) { /** * The BaseRenderView handles the client-side logic for a render target (typically a or ). * - * It observes events: - * - * - it observes visibility and calls `this.OnVisibleChanged()`. - * - it observes resizes and calls `this.OnResize()`. - * - it observes user events ans calls this.OnEvent()`. - * + * It observes events and calls `this.OnEvent()`. * It provides convenience methods for setting the size, cursor, and more. * * When used with a wrapper element, more features are enabled: @@ -179,6 +177,11 @@ class BaseRenderView { this.wrapperElement.innerHTML = '' this.wrapperElement = null } + const event = { + type: 'close', + timestamp: getTimestamp() + } + this.onEvent(event) } /** @@ -215,11 +218,11 @@ class BaseRenderView { } /** - * Set whether the view is manually resizable. - * Note that the view can only be made resizable if it was instantiated with a wrapper. - * - * @param {boolean} resizable - Whether to make it resizable or not. - */ + * Set whether the view is manually resizable. + * Note that the view can only be made resizable if it was instantiated with a wrapper. + * + * @param {boolean} resizable - Whether to make it resizable or not. + */ setResizable (resizable) { if (this.wrapperElement) { if (resizable) { @@ -231,11 +234,11 @@ class BaseRenderView { } /** - * Set whether the view has a titlebar. - * Note that the view can only have a titlebar if it was instantiated with a wrapper. - * - * @param {boolean} titlebar - Whether to show the titlebar or not. - */ + * Set whether the view has a titlebar. + * Note that the view can only have a titlebar if it was instantiated with a wrapper. + * + * @param {boolean} titlebar - Whether to show the titlebar or not. + */ showTitlebar (titlebar) { if (this.wrapperElement) { if (titlebar) { @@ -279,22 +282,6 @@ class BaseRenderView { this._moveThrottle = throttle } - /** - * The subclass should implement this to handle changes in visibility. - * - * @param {boolean} visible - Whether the view just became visible (true) or invisible (false). - */ - onVisibleChanged (visible) { } - - /** - * The subclass should implement this to handle resizes. The base class does *not* emit resize events by itself. - * - * @param {number} physicalWidth - The width in (physical) pixels. - * @param {number} physicalHeight - The height in (physical) pixels. - * @param {number} pixelRatio - The pixel ratio. Divide the physical size with this to get the logical size. - */ - onResize (physicalWidth, physicalHeight, pixelRatio) { } - /** * The subclass should implement this to handle events. * @@ -341,10 +328,18 @@ class BaseRenderView { const wrapperElement = this.wrapperElement if (wrapperElement !== null) { + this.viewElement.classList.add('renderview-view') + // Wrap it wrapperElement.classList.add('renderview-wrapper') wrapperElement.appendChild(this.viewElement) + // Debug element + const debugElement = document.createElement('div') + debugElement.innerHTML = 'If you can read this, the rendercanvas.css is likely not applied.' + debugElement.classList.add('renderview-hidden') + wrapperElement.appendChild(debugElement) + // Create title bar const topElement = document.createElement('div') topElement.classList.add('renderview-top') @@ -405,10 +400,13 @@ class BaseRenderView { } if (isVisible !== this._isVisible) { this._isVisible = isVisible - this.onVisibleChanged(isVisible) + const event = { + type: isVisible ? 'show' : 'hide', + timestamp: getTimestamp() + } + this.onEvent(event) } - } - ) + }) this._intersectionObserver.observe(viewElement) // ----- resize --------------- @@ -461,8 +459,19 @@ class BaseRenderView { this.sizeElement.style.maxHeight = '90vmin' } + // Store logical size this._lsize = [logicalWidth, logicalHeight] - this.onResize(physicalWidth, physicalHeight, ratio) + + const event = { + type: 'resize', + width: logicalWidth, + height: logicalHeight, + pwidth: physicalWidth, + pheight: physicalHeight, + ratio, + timestamp: getTimestamp() + } + this.onEvent(event) }) this._resizeObserver.observe(this.viewElement) @@ -499,7 +508,7 @@ class BaseRenderView { lastButtons = buttons const event = { - event_type: 'pointer_down', + type: 'pointer_down', x: ev.offsetX, y: ev.offsetY, button, @@ -507,7 +516,7 @@ class BaseRenderView { modifiers, ntouches: 0, // TODO later: maybe via https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent touches: {}, - time_stamp: getTimestamp() + timestamp: getTimestamp() } this.onEvent(event) }, @@ -550,7 +559,7 @@ class BaseRenderView { pendingMoveEvent.y = ev.offsetY } else { const event = { - event_type: 'pointer_move', + type: 'pointer_move', x: ev.offsetX, y: ev.offsetY, button, @@ -558,7 +567,7 @@ class BaseRenderView { modifiers, ntouches: 0, touches: {}, - time_stamp: getTimestamp() + timestamp: getTimestamp() } if (this._moveThrottle > 0) { sendMoveEvent() // Send previous (if any) @@ -585,7 +594,7 @@ class BaseRenderView { lastButtons = buttons const event = { - event_type: 'pointer_up', + type: 'pointer_up', x: ev.offsetX, y: ev.offsetY, button, @@ -593,7 +602,7 @@ class BaseRenderView { modifiers, ntouches: 0, touches: {}, - time_stamp: getTimestamp() + timestamp: getTimestamp() } this.onEvent(event) }, @@ -614,7 +623,7 @@ class BaseRenderView { button = 0 const event = { - event_type: 'pointer_enter', + type: 'pointer_enter', x: ev.offsetX, y: ev.offsetY, button, @@ -622,7 +631,7 @@ class BaseRenderView { modifiers, ntouches: 0, touches: {}, - time_stamp: getTimestamp() + timestamp: getTimestamp() } this.onEvent(event) }, @@ -643,7 +652,7 @@ class BaseRenderView { button = 0 const event = { - event_type: 'pointer_leave', + type: 'pointer_leave', x: ev.offsetX, y: ev.offsetY, button, @@ -651,7 +660,7 @@ class BaseRenderView { modifiers, ntouches: 0, touches: {}, - time_stamp: getTimestamp() + timestamp: getTimestamp() } this.onEvent(event) }, @@ -673,14 +682,14 @@ class BaseRenderView { const modifiers = getModifiers(ev) const event = { - event_type: 'double_click', + type: 'double_click', x: ev.offsetX, y: ev.offsetY, button, buttons, modifiers, // no touches here - time_stamp: getTimestamp() + timestamp: getTimestamp() } this.onEvent(event) }, @@ -727,14 +736,14 @@ class BaseRenderView { pendingWheelEvent.dy += ev.deltaY * scale } else { const event = { - event_type: 'wheel', + type: 'wheel', x: ev.offsetX, y: ev.offsetY, dx: ev.deltaX * scale, dy: ev.deltaY * scale, buttons, modifiers, - time_stamp: getTimestamp() + timestamp: getTimestamp() } if (this._wheelThrottle > 0) { sendWheelEvent() // Send previous (if any) @@ -764,10 +773,10 @@ class BaseRenderView { const modifiers = getModifiers(ev) const event = { - event_type: 'key_down', + type: 'key_down', key: KEY_MAP[ev.key] || ev.key, modifiers, - time_stamp: getTimestamp() + timestamp: getTimestamp() } this.onEvent(event) }, @@ -782,10 +791,10 @@ class BaseRenderView { const modifiers = getModifiers(ev) const event = { - event_type: 'key_up', + type: 'key_up', key: KEY_MAP[ev.key] || ev.key, modifiers, - time_stamp: getTimestamp() + timestamp: getTimestamp() } this.onEvent(event) }, @@ -803,12 +812,12 @@ class BaseRenderView { } const event = { - event_type: 'char', + type: 'char', data: ev.data, is_composing: ev.isComposing, input_type: ev.inputType, // repeat: ev.repeat, // n.a. - time_stamp: getTimestamp() + timestamp: getTimestamp() } this.onEvent(event) }, From 34b79f11cd620766709eb176622e41fe5ea0fc66 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 25 Mar 2026 13:36:25 +0100 Subject: [PATCH 2/2] Update pyodide backend --- rendercanvas/core/renderview-pyodide.js | 28 +++++++++++++------------ rendercanvas/pyodide.py | 13 +++++++++--- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/rendercanvas/core/renderview-pyodide.js b/rendercanvas/core/renderview-pyodide.js index e809645..0ab73f8 100644 --- a/rendercanvas/core/renderview-pyodide.js +++ b/rendercanvas/core/renderview-pyodide.js @@ -40,20 +40,22 @@ class PyodideRenderView extends BaseRenderView { 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) + if (event.type === 'resize') { + // Set canvas physical size + this.viewElement.width = event.pwidth + this.viewElement.height = event.pheight + // Notify canvas, so the render code knows the size + this.pycanvas._on_resize(event.pwidth, event.pheight, event.ratio) + } else if (event.type === 'close') { + this.pycanvas.close() + } else if (event.type === 'show') { + this.pycanvas._on_visible_changed(true) + } else if (event.type === 'hide') { + this.pycanvas._on_visible_changed(false) + } else { + this.pycanvas._on_event(event) + } } } diff --git a/rendercanvas/pyodide.py b/rendercanvas/pyodide.py index eec0c75..b0905b1 100644 --- a/rendercanvas/pyodide.py +++ b/rendercanvas/pyodide.py @@ -75,21 +75,28 @@ def __init__( super().__init__(*args, **kwargs) self._final_canvas_init() - def _onVisibleChanged(self, visible): # noqa: N802 + def _on_visible_changed(self, visible): # Called from JS self._set_visible(visible) - def _onResize(self, physical_width, physical_height, pixel_ratio): # noqa: N802 + def _on_resize(self, physical_width, physical_height, pixel_ratio): # Called from JS self._size_info.set_physical_size(physical_width, physical_height, pixel_ratio) - def _onEvent(self, event): # noqa: N802 + def _on_event(self, event): # Called from JS event = event.to_py() + + # Compatibility between new renderview event spec and current rendercanvas/pygfx events + event["event_type"] = event.pop("type") + event["time_stamp"] = event.pop("timestamp") + + # Python prefers tuples over lists if "buttons" in event: event["buttons"] = tuple(event["buttons"]) if "modifiers" in event: event["modifiers"] = tuple(event["modifiers"]) + self.submit_event(event) def _rc_gui_poll(self):