From 31866451e799f825cb82c9a884ab39e2c72e15ad Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Thu, 2 Apr 2026 23:40:15 +0200 Subject: [PATCH 1/2] closable --- src/renderview.css | 25 +++++++++++++++++++++++-- src/renderview.js | 26 +++++++++++++++++++++----- src/tester.html | 3 ++- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/renderview.css b/src/renderview.css index 403f84a..6eacb65 100644 --- a/src/renderview.css +++ b/src/renderview.css @@ -45,12 +45,29 @@ div.renderview-wrapper .renderview-top { align-items: center; } -div.renderview-wrapper .renderview-top span { +div.renderview-wrapper .renderview-title { display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - padding-left: 0.5em; + margin-left: 0.5em; + flex: 1; +} + +div.renderview-wrapper .renderview-button { + display: none; + flex: 0; + padding: 0 0.3em; + border-radius: 5px; + cursor: pointer; +} + +div.renderview-wrapper .renderview-button:hover { + background: var(--line-color); +} + +div.renderview-wrapper .renderview-button:last-child { + margin-right: 0.2em; } div.renderview-wrapper .renderview-resizer { @@ -79,4 +96,8 @@ div.renderview-wrapper.has-titlebar .renderview-top { div.renderview-wrapper.is-resizable .renderview-resizer { display: block; +} + +div.renderview-wrapper.is-closable .renderview-close-button { + display: inline-block; } \ No newline at end of file diff --git a/src/renderview.js b/src/renderview.js index 1dfbd66..71ed842 100644 --- a/src/renderview.js +++ b/src/renderview.js @@ -173,8 +173,12 @@ class BaseRenderView { this.viewElement = null this.sizeElement = null this.titleElement = null + this.butCloseElement = null if (this.wrapperElement) { this.wrapperElement.innerHTML = '' + this.wrapperElement.classList.remove('renderview-wrapper') + this.wrapperElement.style.width = '' + this.wrapperElement.style.height = '' this.wrapperElement = null } const event = { @@ -343,10 +347,14 @@ class BaseRenderView { // Create title bar const topElement = document.createElement('div') topElement.classList.add('renderview-top') - const titleElement = document.createElement('span') - this.titleElement = titleElement - titleElement.innerText = 'RenderView' - topElement.appendChild(titleElement) + this.titleElement = document.createElement('span') + this.titleElement.innerText = 'RenderView' + this.titleElement.classList.add('renderview-title') + this.butCloseElement = document.createElement('span') + this.butCloseElement.innerText = '×' + this.butCloseElement.classList.add('renderview-button', 'renderview-close-button') + topElement.appendChild(this.titleElement) + topElement.appendChild(this.butCloseElement) wrapperElement.appendChild(topElement) // Enable resizing @@ -389,7 +397,7 @@ class BaseRenderView { const viewElement = this.viewElement const signal = this._abortController.signal // to unregister/abort stuff - // ----- visibility and focus --------------- + // ----- visibility and focus and closing --------------- this._intersectionObserver = new IntersectionObserver((entries, observer) => { // This gets called when one of the observed elements becomes visible/invisible. @@ -426,6 +434,14 @@ class BaseRenderView { { signal } ) + if (this.butCloseElement) { + this.butCloseElement.addEventListener('click', (ev) => { + this.close() + }, + { signal } + ) + } + // ----- resize --------------- this._resizeObserver = new ResizeObserver((entries) => { diff --git a/src/tester.html b/src/tester.html index 9c2fa69..eb6ab65 100644 --- a/src/tester.html +++ b/src/tester.html @@ -51,6 +51,7 @@ draw() { this._drawRequested = false + if (!this.viewElement) { return } // closed const ctx = this.viewElement.getContext('2d') // CanvasRenderingContext2D ctx.fillStyle = '#abc' @@ -121,7 +122,7 @@

Plain canvas

Canvas with wrapper

-
Loading ...
From d9f9fbe9e97d5023bc8762effa82e0cd0159862d Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Fri, 3 Apr 2026 00:18:45 +0200 Subject: [PATCH 2/2] minimizable --- src/renderview.css | 9 +++++++++ src/renderview.js | 50 +++++++++++++++++++++++++++++++++++++--------- src/tester.html | 2 +- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/renderview.css b/src/renderview.css index 6eacb65..3106e3e 100644 --- a/src/renderview.css +++ b/src/renderview.css @@ -98,6 +98,15 @@ div.renderview-wrapper.is-resizable .renderview-resizer { display: block; } +div.renderview-wrapper.is-minimizable .renderview-minimize-button { + display: inline-block; +} + +div.renderview-wrapper.is-minimized { + height: 0 !important; + min-height: 0 !important; +} + div.renderview-wrapper.is-closable .renderview-close-button { display: inline-block; } \ No newline at end of file diff --git a/src/renderview.js b/src/renderview.js index 71ed842..4999976 100644 --- a/src/renderview.js +++ b/src/renderview.js @@ -138,7 +138,7 @@ class BaseRenderView { this._lsize = null // cached logical size this._wheelThrottle = 20 // to avoid flooding wheel events this._moveThrottle = 20 // to avoid flooding move events - this._isVisible = false // set by intersection observer + this._isVisible = 0 // bitmask: 1->intersected, 2->nonzerosize this._focusElement = null this._abortController = new AbortController() @@ -293,6 +293,25 @@ class BaseRenderView { */ onEvent (event) { } + /** + * Internal method to handle visibility. + */ + _updateVisibleBitmask (i, bitValue) { + const wasVisible = this._isVisible === 3 + if (bitValue) { this._isVisible |= i } else { this._isVisible &= (~i) } + const nowVisible = this._isVisible === 3 + if (nowVisible !== wasVisible) { + if (!nowVisible) { + this._focusElement.blur() + } + const event = { + type: nowVisible ? 'show' : 'hide', + timestamp: getTimestamp() + } + this.onEvent(event) + } + } + /** * Internal method to initialize the view's helper elements. */ @@ -350,10 +369,14 @@ class BaseRenderView { this.titleElement = document.createElement('span') this.titleElement.innerText = 'RenderView' this.titleElement.classList.add('renderview-title') + this.butMinimizeElement = document.createElement('span') + this.butMinimizeElement.innerText = '_' + this.butMinimizeElement.classList.add('renderview-button', 'renderview-minimize-button') this.butCloseElement = document.createElement('span') this.butCloseElement.innerText = '×' this.butCloseElement.classList.add('renderview-button', 'renderview-close-button') topElement.appendChild(this.titleElement) + topElement.appendChild(this.butMinimizeElement) topElement.appendChild(this.butCloseElement) wrapperElement.appendChild(topElement) @@ -406,14 +429,7 @@ class BaseRenderView { for (const entry of entries) { isVisible = isVisible || entry.isIntersecting } - if (isVisible !== this._isVisible) { - this._isVisible = isVisible - const event = { - type: isVisible ? 'show' : 'hide', - timestamp: getTimestamp() - } - this.onEvent(event) - } + this._updateVisibleBitmask(1, isVisible) // 1 for intersection bit }) this._intersectionObserver.observe(viewElement) @@ -434,6 +450,14 @@ class BaseRenderView { { signal } ) + if (this.butMinimizeElement) { + this.butMinimizeElement.addEventListener('click', (ev) => { + this.wrapperElement.classList.toggle('is-minimized') + }, + { signal } + ) + } + if (this.butCloseElement) { this.butCloseElement.addEventListener('click', (ev) => { this.close() @@ -478,6 +502,14 @@ class BaseRenderView { physicalHeight = Math.floor(lsize[1] * ratio) } + // Handle visibility. If zero-size we assume we're minimized or otherwise hidden; zero size is not valid. + if (!physicalHeight || !physicalWidth) { + this._updateVisibleBitmask(2, false) // 2 for non-zero-size bit + return + } else { + this._updateVisibleBitmask(2, true) + } + // If the container element does not have its size set via its style, we set it to the logical size. const logicalWidth = physicalWidth / ratio const logicalHeight = physicalHeight / ratio diff --git a/src/tester.html b/src/tester.html index eb6ab65..c1bc99e 100644 --- a/src/tester.html +++ b/src/tester.html @@ -122,7 +122,7 @@

Plain canvas

Canvas with wrapper

-
Loading ...