Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CSS-based theming on the canvas #13828

Merged
merged 40 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
366d1c5
Separate rendering from painting
mattpap Mar 29, 2024
4b74d9a
Make RendererView inherit from DOMComponentView
mattpap Mar 29, 2024
717a6c3
Don't use append(), remove() and replaceWith()
mattpap Apr 11, 2024
18f3e08
Move RendererGroup to a separate module
mattpap Apr 13, 2024
8edd1a1
Introduce StyledElement base class
mattpap Apr 14, 2024
346ad21
Attach renderers' elements to the canvas
mattpap Apr 16, 2024
2783f4d
Use CSS variables in canvas visuals
mattpap Apr 16, 2024
ef6b1f0
Add styling/visuals/css_variables.py
mattpap Apr 16, 2024
ecb85a7
Restore functionality of HTML label annotation
mattpap Apr 17, 2024
084a5ec
Update bokehjs' unit tests
mattpap Apr 17, 2024
6f4193d
Use stylesheet based styling in HTML labels
mattpap Apr 18, 2024
0273ed1
Update cross tests baselines
mattpap Apr 18, 2024
85edc1b
Correctly render the Toolbar in ToolbarPanel
mattpap Apr 21, 2024
fc318fc
Add CSS support to Text and Hatch visuals
mattpap Apr 21, 2024
cad5ad0
Style text in css_variables example
mattpap Apr 21, 2024
9827484
Migrate styling/visuals/css_variables.py to bokehjs
mattpap Apr 21, 2024
75eee1b
Allow to specific rendering target for canvas renderers
mattpap Apr 22, 2024
80aac1f
Reposition menu after toolbar resize if open
mattpap Apr 22, 2024
3dabe74
Correctly update canvas renderers' elements
mattpap Apr 22, 2024
f2d9a79
Allow to recover from invalid gesture state
mattpap Apr 22, 2024
be4ad84
Safeguard against disconnected elements
mattpap Apr 22, 2024
67cba1f
Use render_to() to render toolbar's tool buttons
mattpap Apr 22, 2024
d4c9030
Add integration tests for HTMLLabel
mattpap Apr 22, 2024
431040a
Add docstrings to models/ui/ui_element.py
mattpap Apr 23, 2024
15d4f4a
Remove deprecated APIs from core/dom.ts
mattpap Apr 23, 2024
22bcd1d
Compute CSS prefix of visual properties once
mattpap Apr 23, 2024
0c4c892
Robustify render() and after_render() logic
mattpap Apr 26, 2024
8a28c2a
Call r_after_render() after updating children
mattpap May 1, 2024
980a149
Use computed_renderer_views to avoid race conditions
mattpap May 1, 2024
c9cd2dc
Display duration in devtools' progress bar
mattpap May 1, 2024
5b20522
Robustify is_paused and hold_render logic
mattpap May 1, 2024
2d8bfb0
Recompute toolbar buttons after layout
mattpap May 1, 2024
3bbb63e
Update integration baseline images
mattpap May 1, 2024
77a1459
Robustify ready state in TileRenderer
mattpap May 2, 2024
500f55f
Mark _was_build in after_render()
mattpap May 2, 2024
f22de60
Correctly render contents in Dialog
mattpap May 2, 2024
1e4544a
Add a regression test for issue #13787
mattpap May 2, 2024
11804cd
Update *.blf baseline files
mattpap May 2, 2024
cf8dc3e
Update bokeh's examples
mattpap May 2, 2024
55d9deb
Add release notes
mattpap May 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions bokehjs/src/less/renderer.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
position: absolute;
}
24 changes: 2 additions & 22 deletions bokehjs/src/lib/core/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,26 +156,6 @@ export function nbsp(): Text {
return text("\u00a0")
}

export function append(element: Node, ...children: Node[]): void {
for (const child of children) {
element.appendChild(child)
}
}

export function remove(element: Node): void {
const parent = element.parentNode
if (parent != null) {
parent.removeChild(element)
}
}

export function replaceWith(element: Node, replacement: Node): void {
const parent = element.parentNode
if (parent != null) {
parent.replaceChild(replacement, element)
}
}

export function prepend(element: Node, ...nodes: Node[]): void {
const first = element.firstChild
for (const node of nodes) {
Expand Down Expand Up @@ -495,7 +475,7 @@ export class InlineStyleSheet extends StyleSheet {
}

remove(): void {
remove(this.el)
this.el.remove()
}
}

Expand All @@ -520,7 +500,7 @@ export class ImportedStyleSheet extends StyleSheet {
}

remove(): void {
remove(this.el)
this.el.remove()
}
}

Expand Down
37 changes: 27 additions & 10 deletions bokehjs/src/lib/core/dom_view.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {View} from "./view"
import type {StyleSheet, StyleSheetLike} from "./dom"
import {createElement, remove, empty, InlineStyleSheet, ClassList} from "./dom"
import {createElement, empty, InlineStyleSheet, ClassList} from "./dom"
import {isString} from "./util/types"
import {assert} from "./util/assert"
import base_css from "styles/base.css"

export interface DOMView extends View {
Expand All @@ -22,11 +23,11 @@ export abstract class DOMView extends View {

override initialize(): void {
super.initialize()
this.el = this._createElement()
this.el = this._create_element()
}

override remove(): void {
remove(this.el)
this.el.remove()
super.remove()
}

Expand All @@ -40,26 +41,42 @@ export abstract class DOMView extends View {

abstract render(): void

render_to(target: Node | null): void {
render_to(target: Node): void {
this.render()
target?.appendChild(this.el)
this.after_render()
target.appendChild(this.el)
}

after_render(): void {
this.reposition()
}

finish(): void {
this._has_finished = true
this.notify_finished()
r_after_render(): void {
for (const child_view of this.children()) {
if (child_view instanceof DOMView) {
child_view.r_after_render()
}
}
this.after_render()
this._was_built = true
}

protected _createElement(): this["el"] {
protected _create_element(): this["el"] {
return createElement(this.constructor.tag_name, {class: this.css_classes()})
}

reposition(_displayed?: boolean): void {}

protected _was_built: boolean = false

/**
* Build a top-level DOM view (e.g. during embedding).
*/
build(target: Node): void {
assert(this.is_root)
this.render_to(target)
this.r_after_render()
this.notify_finished()
}
}

export abstract class DOMElementView extends DOMView {
Expand Down
5 changes: 1 addition & 4 deletions bokehjs/src/lib/core/ui_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,11 @@ export class UIEventBus {
}

hit_test_renderers(plot_view: PlotView, sx: number, sy: number): RendererView | null {
const views = plot_view.get_renderer_views()

for (const view of reversed(views)) {
for (const view of reversed(plot_view.computed_renderer_views)) {
if (view.interactive_hit?.(sx, sy) ?? false) {
return view
}
}

return null
}

Expand Down
20 changes: 19 additions & 1 deletion bokehjs/src/lib/core/ui_gestures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,25 @@ export class UIGestures {
}

protected phase: GesturePhase = "idle"
protected pointers: Map<PointerId, Pointer> = new Map()
protected readonly pointers: Map<PointerId, Pointer> = new Map()
protected press_timer: number | null = null
protected tap_timestamp: number = -Infinity

protected last_scale: number | null = null
protected last_rotation: number | null = null

reset(): void {
this._cancel_timeout()

this.phase = "idle"
this.pointers.clear()
this.press_timer = null
this.tap_timestamp = -Infinity

this.last_scale = null
this.last_rotation = null
}

static readonly move_threshold: number = 5/*px*/
static readonly press_threshold: number = 300/*ms*/
static readonly doubletap_threshold: number = 300/*ms*/
Expand Down Expand Up @@ -219,6 +231,9 @@ export class UIGestures {
if (event.isPrimary && event.pointerType == "mouse" && event.buttons != MouseButton.Left) {
return
}
if (!this.hit_area.isConnected) {
return
}
this.pointers.set(event.pointerId, {init: event, last: event})
this.hit_area.setPointerCapture(event.pointerId)
switch (this.phase) {
Expand Down Expand Up @@ -254,6 +269,7 @@ export class UIGestures {
pointer.last = event
switch (this.phase) {
case "idle": {
this.reset()
unreachable()
}
case "started":
Expand Down Expand Up @@ -328,6 +344,7 @@ export class UIGestures {
this._cancel_timeout()
switch (this.phase) {
case "idle": {
this.reset()
unreachable()
}
case "started": {
Expand Down Expand Up @@ -391,6 +408,7 @@ export class UIGestures {
this._cancel_timeout()
switch (this.phase) {
case "idle": {
this.reset()
mattpap marked this conversation as resolved.
Show resolved Hide resolved
unreachable()
}
case "started":
Expand Down
23 changes: 10 additions & 13 deletions bokehjs/src/lib/core/util/menus.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {StyleSheetLike} from "../dom"
import {div, empty, remove, InlineStyleSheet, ClassList} from "../dom"
import {div, empty, InlineStyleSheet, ClassList} from "../dom"
import type {Orientation} from "../enums"
import {reversed} from "./array"
import {isString} from "./types"
Expand Down Expand Up @@ -106,7 +106,7 @@ export class ContextMenu { //extends DOMComponentView {

remove(): void {
this._unlisten()
remove(this.el)
this.el.remove()
}

protected _listen(): void {
Expand Down Expand Up @@ -211,24 +211,21 @@ export class ContextMenu { //extends DOMComponentView {
if (this.items.length == 0) {
return
}

if (!this._open) {
this.render()
if (this.shadow_el.children.length == 0) {
return
}
(this.target.shadowRoot ?? this.target).appendChild(this.el)
this._position(at ?? {left: 0, top: 0})
this._listen()
this._open = true
this.render()
if (this.shadow_el.children.length == 0) {
return
}
(this.target.shadowRoot ?? this.target).appendChild(this.el)
this._position(at ?? {left: 0, top: 0})
this._listen()
this._open = true
mattpap marked this conversation as resolved.
Show resolved Hide resolved
}

hide(): void {
if (this._open) {
this._open = false
this._unlisten()
remove(this.el)
this.el.remove()
}
}

Expand Down
6 changes: 3 additions & 3 deletions bokehjs/src/lib/core/util/panes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {StyleSheetLike, Keys} from "../dom"
import {div, empty, remove, InlineStyleSheet, ClassList} from "../dom"
import {div, empty, InlineStyleSheet, ClassList} from "../dom"
import type {Orientation} from "../enums"
import {isString} from "./types"

Expand Down Expand Up @@ -75,7 +75,7 @@ export class DropPane { //extends DOMComponentView {

remove(): void {
this._unlisten()
remove(this.el)
this.el.remove()
}

protected _listen(): void {
Expand Down Expand Up @@ -124,7 +124,7 @@ export class DropPane { //extends DOMComponentView {
if (this._open) {
this._open = false
this._unlisten()
remove(this.el)
this.el.remove()
}
}

Expand Down
61 changes: 36 additions & 25 deletions bokehjs/src/lib/core/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ export class View implements ISignalable {
return this._ready
}

public *children(): IterViews {}

protected _has_finished: boolean

mark_finished(): void {
this._has_finished = true
}

/** @internal */
protected _slots = new WeakMap<Slot<any, any>, Slot<any, any>>()

Expand Down Expand Up @@ -90,9 +82,7 @@ export class View implements ISignalable {
}
}

initialize(): void {
this._has_finished = false
}
initialize(): void {}

async lazy_initialize(): Promise<void> {}

Expand All @@ -112,6 +102,41 @@ export class View implements ISignalable {
return `${this.model.type}View(${this.model.id})`
}

public *children(): IterViews {}

protected _has_finished: boolean = false

mark_finished(): void {
this._has_finished = true
}

/**
* Mark as finished even if e.g. external resources were not loaded yet.
*/
force_finished(): void {
this.mark_finished()
}

finish(): void {
this.mark_finished()
this.notify_finished()
}

private _idle_notified: boolean = false
notify_finished(): void {
if (!this.is_root) {
this.root.notify_finished()
} else {
if (!this._idle_notified && this.has_finished()) {
const {document} = this.model
if (document != null) {
this._idle_notified = true
document.notify_idle(this.model)
}
}
}
}

serializable_state(): SerializableState {
return {type: this.model.type}
}
Expand Down Expand Up @@ -175,20 +200,6 @@ export class View implements ISignalable {

on_hit?(sx: number, sy: number): boolean

private _idle_notified: boolean = false
notify_finished(): void {
if (!this.is_root) {
this.root.notify_finished()
} else {
if (!this._idle_notified && this.has_finished()) {
if (this.model.document != null) {
this._idle_notified = true
this.model.document.notify_idle(this.model)
}
}
}
}

resolve_frame(): View | null {
return null
}
Expand Down