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
12 changes: 12 additions & 0 deletions jupyter_rfb/renderview-rfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ class RendercanvasAnywidgetModel {
view.setResizable(resizable)
}
})
anymodel.on(`change:${prefix}is_minimizable`, () => {
const minimizable = anymodel.get(`${prefix}is_minimizable`)
for (const view of this.views) {
view.setMinimizable(minimizable)
}
})
anymodel.on(`change:${prefix}is_closable`, () => {
const closable = anymodel.get(`${prefix}is_closable`)
for (const view of this.views) {
view.setClosable(closable)
}
})
anymodel.on(`change:${prefix}has_titlebar`, () => {
const titlebar = anymodel.get(`${prefix}has_titlebar`)
for (const view of this.views) {
Expand Down
68 changes: 49 additions & 19 deletions jupyter_rfb/renderview.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@
*************************************************************************************************/

div.renderview-wrapper {
--line-radius: 6px;
--line-thickness: 2px;
--line-color: rgba(127, 127, 127, 0.3);
--line-color-focus: rgba(127, 127, 127, 0.5);
--titlebar-height: 1.7em;
display: inline-block;
position: relative;
box-sizing: border-box;
overflow: visible;
min-width: 32px;
min-height: 32px;
border: var(--line-thickness) solid var(--line-color);
}

div.renderview-wrapper .renderview-view {
display: block;
box-sizing: border-box;
width: 100%;
height: 100%;
background: none;
border-radius: 6px;
border: 1px solid rgba(127, 127, 127, 0.2);
}

div.renderview-wrapper .renderview-hidden {
Expand All @@ -32,30 +34,41 @@ div.renderview-wrapper .renderview-hidden {
div.renderview-wrapper .renderview-top {
display: none;
position: absolute;
box-sizing: border-box;
z-index: 2;
top: -1.5em;
height: 1.5em;
top: calc(0px - var(--titlebar-height) - 2 * var(--line-thickness));
height: var(--titlebar-height);
left: calc(0px - var(--line-thickness));
width: 100%;
border-radius: 6px 6px 0 0;
border: 1px solid rgba(127, 127, 127, 0.2);
border-radius: var(--line-radius) var(--line-radius) 0 0;
border: var(--line-thickness) solid var(--line-color);
border-bottom: 0;
align-items: center;
}

div.renderview-wrapper .renderview-top span {
div.renderview-wrapper .renderview-title {
display: inline-block;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0.1em 0.5em;
width: 100%;
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-resizer {
display: none;
position: absolute;
box-sizing: border-box;
z-index: 3;
bottom: 0;
right: 0;
Expand All @@ -64,19 +77,36 @@ div.renderview-wrapper .renderview-resizer {
cursor: nwse-resize;
}

div.renderview-wrapper.has-focus,
div.renderview-wrapper.has-focus .renderview-top {
border-color: var(--line-color-focus);
}

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

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

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

div.renderview-wrapper.has-titlebar .renderview-view {
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
div.renderview-wrapper.is-resizable.is-minimized .renderview-resizer {
display: none;
}

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;
}
139 changes: 124 additions & 15 deletions jupyter_rfb/renderview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -233,6 +237,38 @@ class BaseRenderView {
}
}

/**
* Set whether the view has a button to minimize the widget.
* Note that the view can only be made minimizable if it was instantiated with a wrapper.
*
* @param {boolean} minimizable - Whether to make it minimizable or not.
*/
setMinimizable (minimizable) {
if (this.wrapperElement) {
if (minimizable) {
this.wrapperElement.classList.add('is-minimizable')
} else {
this.wrapperElement.classList.remove('is-minimizable')
}
}
}

/**
* Set whether the view has a button to close the widget.
* Note that the view can only be made closable if it was instantiated with a wrapper.
*
* @param {boolean} closable - Whether to make it closable or not.
*/
setClosable (closable) {
if (this.wrapperElement) {
if (closable) {
this.wrapperElement.classList.add('is-closable')
} else {
this.wrapperElement.classList.remove('is-closable')
}
}
}

/**
* Set whether the view has a titlebar.
* Note that the view can only have a titlebar if it was instantiated with a wrapper.
Expand Down Expand Up @@ -289,6 +325,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.
*/
Expand Down Expand Up @@ -343,10 +398,21 @@ 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.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')
const butPadElement = document.createElement('span')
butPadElement.style.width = '0.3em'
topElement.appendChild(this.titleElement)
topElement.appendChild(this.butMinimizeElement)
topElement.appendChild(this.butCloseElement)
topElement.appendChild(butPadElement)
wrapperElement.appendChild(topElement)

// Enable resizing
Expand Down Expand Up @@ -389,7 +455,7 @@ class BaseRenderView {
const viewElement = this.viewElement
const signal = this._abortController.signal // to unregister/abort stuff

// ----- visibility ---------------
// ----- visibility and focus and closing ---------------

this._intersectionObserver = new IntersectionObserver((entries, observer) => {
// This gets called when one of the observed elements becomes visible/invisible.
Expand All @@ -398,17 +464,52 @@ 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)

this._focusElement.addEventListener('focus', (ev) => {
if (this.wrapperElement) {
this.wrapperElement.classList.add('has-focus')
}
const event = {
type: 'focus_in',
timestamp: getTimestamp()
}
this.onEvent(event)
},
{ signal }
)

this._focusElement.addEventListener('blur', (ev) => {
if (this.wrapperElement) {
this.wrapperElement.classList.remove('has-focus')
}
const event = {
type: 'focus_out',
timestamp: getTimestamp()
}
this.onEvent(event)
},
{ 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()
},
{ signal }
)
}

// ----- resize ---------------

this._resizeObserver = new ResizeObserver((entries) => {
Expand Down Expand Up @@ -445,6 +546,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
Expand Down Expand Up @@ -474,7 +583,7 @@ class BaseRenderView {
this.onEvent(event)
})

this._resizeObserver.observe(this.sizeElement)
this._resizeObserver.observe(this.viewElement)

// ----- pointer ---------------

Expand Down
2 changes: 2 additions & 0 deletions jupyter_rfb/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class RemoteFrameBuffer(anywidget.AnyWidget):
css_width = Unicode("500px").tag(sync=True)
css_height = Unicode("300px").tag(sync=True)
resizable = Bool(True).tag(sync=True)
is_minimizable = Bool(False).tag(sync=True)
is_closable = Bool(False).tag(sync=True)
has_titlebar = Bool(False).tag(sync=True)
cursor = Unicode("default").tag(sync=True)
title = Unicode("").tag(sync=True)
Expand Down
Loading