diff --git a/src/renderers/webgl/LifecycleManager.ts b/src/renderers/webgl/LifecycleManager.ts index 7a1a402..fbdc473 100644 --- a/src/renderers/webgl/LifecycleManager.ts +++ b/src/renderers/webgl/LifecycleManager.ts @@ -3,7 +3,6 @@ import { LineSegment } from './objects/lineSegment' import { NodeFill } from './objects/nodeFill' import { Arrow } from './objects/arrow' import ObjectManager from './objects/ObjectManager' -import TextHighlight from './objects/text/TextHighlight' import Icon from './objects/Icon' import Text from './objects/text/Text' @@ -12,7 +11,7 @@ export default class LifecycleManager { icons = new ObjectManager(1000) edges = new ObjectManager(2000) arrows = new ObjectManager(1000) - labels = new ObjectManager(2000) + labels = new ObjectManager(2000) interactions = new ObjectManager(2000) // interactions = new ObjectManager(2000) // TODO diff --git a/src/renderers/webgl/edge.ts b/src/renderers/webgl/edge.ts index eca2b45..0c9b9d6 100644 --- a/src/renderers/webgl/edge.ts +++ b/src/renderers/webgl/edge.ts @@ -35,7 +35,8 @@ export class EdgeRenderer { targetRadius?: number private hitArea: EdgeHitArea - private arrow?: { forward: Arrow; reverse?: undefined } | { forward?: undefined; reverse: Arrow } | { forward: Arrow; reverse: Arrow } + private forwardArrow?: Arrow + private reverseArrow?: Arrow private doubleClickTimeout: NodeJS.Timeout | undefined private doubleClick = false @@ -52,35 +53,34 @@ export class EdgeRenderer { this.target = target const arrow = edge.style?.arrow ?? DEFAULT_ARROW - if (arrow !== this.arrowStyle) { - this.arrow?.forward?.delete() - this.arrow?.reverse?.delete() - this.arrow = undefined + if (arrow !== this.arrow) { switch (arrow) { case 'forward': - this.arrow = { forward: new Arrow(this.renderer.edgesContainer, this.renderer.arrow) } + this.createArrow('forward').deleteArrow('reverse') break + case 'reverse': - this.arrow = { reverse: new Arrow(this.renderer.edgesContainer, this.renderer.arrow) } + this.deleteArrow('forward').createArrow('reverse') break + case 'both': - this.arrow = { - forward: new Arrow(this.renderer.edgesContainer, this.renderer.arrow), - reverse: new Arrow(this.renderer.edgesContainer, this.renderer.arrow) - } + this.createArrow('forward').createArrow('reverse') + break + + case 'none': + this.deleteArrow('forward').deleteArrow('reverse') + break } } - if (edge.label === undefined || edge.label.trim() === '') { - if (this.label) { + if (this.label) { + if (edge.label === undefined || edge.label.trim() === '') { this.managers.labels.delete(this.label) this.label = undefined + } else { + this.label.update(edge.label, edge.style?.label) } - } else if (this.label === undefined) { - this.label = new Text(this.renderer.assets, this.renderer.labelsContainer, edge.label, edge.style?.label, DEFAULT_LABEL_STYLE) - } else { - this.label.update(edge.label, edge.style?.label) } return this @@ -95,51 +95,6 @@ export class EdgeRenderer { const targetRadius = this.target.strokes.radius const isVisible = this.visible(Math.min(x0, x1), Math.min(y0, y1), Math.max(x0, x1), Math.max(y0, y1)) - // TODO - disable events if edge has no event handlers - // TODO - disable events when dragging/scrolling - const shouldHitAreaMount = isVisible && this.renderer.zoom > MIN_INTERACTION_ZOOM - const hitAreaMounted = this.managers.interactions.isMounted(this.hitArea) - if (shouldHitAreaMount && !hitAreaMounted) { - this.managers.interactions.mount(this.hitArea) - } else if (!shouldHitAreaMount && hitAreaMounted) { - this.managers.interactions.unmount(this.hitArea) - } - - const lineMounted = this.managers.edges.isMounted(this.lineSegment) - if (isVisible && !lineMounted) { - this.managers.edges.mount(this.lineSegment) - } else if (!isVisible && lineMounted) { - this.managers.edges.unmount(this.lineSegment) - } - - if (this.arrow?.forward) { - const forwardArrowMounted = this.managers.arrows.isMounted(this.arrow.forward) - if (isVisible && !forwardArrowMounted) { - this.managers.arrows.mount(this.arrow.forward) - } else if (!isVisible && forwardArrowMounted) { - this.managers.arrows.unmount(this.arrow.forward) - } - } - - if (this.arrow?.reverse) { - const reverseArrowMounted = this.managers.arrows.isMounted(this.arrow.reverse) - if (isVisible && !reverseArrowMounted) { - this.managers.arrows.mount(this.arrow.reverse) - } else if (!isVisible && reverseArrowMounted) { - this.managers.arrows.unmount(this.arrow.reverse) - } - } - - if (this.label) { - const shouldLabelMount = isVisible && this.renderer.zoom > MIN_LABEL_ZOOM - const labelMounted = this.managers.labels.isMounted(this.label) - if (shouldLabelMount && !labelMounted) { - this.managers.labels.mount(this.label) - } else if (!shouldLabelMount && labelMounted) { - this.managers.labels.unmount(this.label) - } - } - if (isVisible) { const width = this.edge?.style?.width ?? DEFAULT_EDGE_WIDTH const stroke = this.edge?.style?.stroke ?? DEFAULT_EDGE_COLOR @@ -171,24 +126,24 @@ export class EdgeRenderer { let edgeX1 = this.x1 let edgeY1 = this.y1 - if (this.arrow?.forward) { - const edgePoint = movePoint(x1, y1, this.theta, this.targetRadius + this.arrow.forward.height) + if (this.forwardArrow) { + const edgePoint = movePoint(x1, y1, this.theta, this.targetRadius + this.forwardArrow.height) edgeX1 = edgePoint[0] edgeY1 = edgePoint[1] const [arrowX1, arrowY1] = movePoint(x1, y1, this.theta, this.targetRadius) - this.arrow.forward.update(arrowX1, arrowY1, this.theta, this.stroke, this.strokeOpacity) + this.forwardArrow.update(arrowX1, arrowY1, this.theta, this.stroke, this.strokeOpacity) } else { const edgePoint = movePoint(x1, y1, this.theta, this.targetRadius) edgeX1 = edgePoint[0] edgeY1 = edgePoint[1] } - if (this.arrow?.reverse) { - const edgePoint = movePoint(x0, y0, this.theta, -this.sourceRadius - this.arrow.reverse.height) + if (this.reverseArrow) { + const edgePoint = movePoint(x0, y0, this.theta, -this.sourceRadius - this.reverseArrow.height) edgeX0 = edgePoint[0] edgeY0 = edgePoint[1] const [arrowX0, arrowY0] = movePoint(x0, y0, this.theta, -this.sourceRadius) - this.arrow.reverse.update(arrowX0, arrowY0, this.theta + Math.PI, this.stroke, this.strokeOpacity) + this.reverseArrow.update(arrowX0, arrowY0, this.theta + Math.PI, this.stroke, this.strokeOpacity) } else { const edgePoint = movePoint(x0, y0, this.theta, -this.sourceRadius) edgeX0 = edgePoint[0] @@ -196,6 +151,7 @@ export class EdgeRenderer { } this.center = midPoint(edgeX0, edgeY0, edgeX1, edgeY1) + if (this.label) { this.label.rotation = this.theta this.label.moveTo(...this.center) @@ -206,6 +162,56 @@ export class EdgeRenderer { this.hitArea.update(edgeX0, edgeY0, edgeX1, edgeY1, this.width, this.theta) } } + + // TODO - disable events if edge has no event handlers + // TODO - disable events when dragging/scrolling + const shouldHitAreaMount = isVisible && this.renderer.zoom > MIN_INTERACTION_ZOOM + const hitAreaMounted = this.managers.interactions.isMounted(this.hitArea) + if (shouldHitAreaMount && !hitAreaMounted) { + this.managers.interactions.mount(this.hitArea) + } else if (!shouldHitAreaMount && hitAreaMounted) { + this.managers.interactions.unmount(this.hitArea) + } + + const lineMounted = this.managers.edges.isMounted(this.lineSegment) + if (isVisible && !lineMounted) { + this.managers.edges.mount(this.lineSegment) + } else if (!isVisible && lineMounted) { + this.managers.edges.unmount(this.lineSegment) + } + + if (this.forwardArrow) { + const forwardArrowMounted = this.managers.arrows.isMounted(this.forwardArrow) + if (isVisible && !forwardArrowMounted) { + this.managers.arrows.mount(this.forwardArrow) + } else if (!isVisible && forwardArrowMounted) { + this.managers.arrows.unmount(this.forwardArrow) + } + } + + if (this.reverseArrow) { + const reverseArrowMounted = this.managers.arrows.isMounted(this.reverseArrow) + if (isVisible && !reverseArrowMounted) { + this.managers.arrows.mount(this.reverseArrow) + } else if (!isVisible && reverseArrowMounted) { + this.managers.arrows.unmount(this.reverseArrow) + } + } + + const shouldLabelMount = isVisible && this.renderer.zoom > MIN_LABEL_ZOOM + + if (shouldLabelMount) { + this.applyLabel() + } + + if (this.label) { + const labelMounted = this.managers.labels.isMounted(this.label) + if (shouldLabelMount && !labelMounted) { + this.managers.labels.mount(this.label) + } else if (!shouldLabelMount && labelMounted) { + this.managers.labels.unmount(this.label) + } + } } delete() { @@ -213,11 +219,11 @@ export class EdgeRenderer { this.managers.edges.delete(this.lineSegment) this.managers.interactions.delete(this.hitArea) - if (this.arrow?.forward) { - this.managers.arrows.delete(this.arrow.forward) + if (this.forwardArrow) { + this.managers.arrows.delete(this.forwardArrow) } - if (this.arrow?.reverse) { - this.managers.arrows.delete(this.arrow.reverse) + if (this.reverseArrow) { + this.managers.arrows.delete(this.reverseArrow) } } @@ -385,15 +391,49 @@ export class EdgeRenderer { return this.renderer.managers } - private get arrowStyle(): ArrowStyle { - if (this.arrow === undefined) { + private get arrow(): ArrowStyle { + if (this.forwardArrow === undefined && this.reverseArrow === undefined) { return 'none' - } else if (this.arrow.forward !== undefined && this.arrow.reverse !== undefined) { - return 'both' - } else if (this.arrow.forward !== undefined) { + } else if (this.reverseArrow === undefined) { return 'forward' - } else { + } else if (this.forwardArrow === undefined) { return 'reverse' + } else { + return 'both' + } + } + + private deleteArrow(arrow: 'forward' | 'reverse') { + if (arrow === 'forward' && this.forwardArrow) { + this.managers.arrows.delete(this.forwardArrow) + this.forwardArrow = undefined + } else if (arrow === 'reverse' && this.reverseArrow) { + this.managers.arrows.delete(this.reverseArrow) + this.reverseArrow = undefined } + + return this + } + + private createArrow(arrow: 'forward' | 'reverse') { + if (arrow === 'forward' && this.forwardArrow === undefined) { + this.forwardArrow = new Arrow(this.renderer.edgesContainer, this.renderer.arrow) + } else if (arrow === 'reverse' && this.reverseArrow === undefined) { + this.reverseArrow = new Arrow(this.renderer.edgesContainer, this.renderer.arrow) + } + + return this + } + + private applyLabel() { + const label = this.edge.label + const style = this.edge.style?.label + if (label !== undefined && label.trim() !== '' && this.label === undefined) { + this.label = new Text(this.renderer.assets, this.renderer.labelsContainer, label, style, DEFAULT_LABEL_STYLE) + this.label.rotation = this.theta + this.label.moveTo(...this.center) + } + + return this } } diff --git a/src/renderers/webgl/node.ts b/src/renderers/webgl/node.ts index 7e1f4bf..48ca512 100644 --- a/src/renderers/webgl/node.ts +++ b/src/renderers/webgl/node.ts @@ -39,26 +39,22 @@ export class NodeRenderer { } update(node: Node) { - if (node.label === undefined || node.label.trim() === '') { - if (this.label) { + if (this.label) { + if (node.label === undefined || node.label.trim() === '') { this.managers.labels.delete(this.label) this.label = undefined + } else { + this.label.update(node.label, node.style?.label) } - } else if (this.label === undefined) { - this.label = new Text(this.renderer.assets, this.renderer.labelsContainer, node.label, node.style?.label, DEFAULT_LABEL_STYLE) - } else { - this.label.update(node.label, node.style?.label) } - if (node.style?.icon === undefined) { - if (this.icon) { + if (this.icon) { + if (node.style?.icon === undefined) { this.icon.delete() this.icon = undefined + } else { + this.icon.update(node.style.icon) } - } else if (this.icon === undefined) { - this.icon = new Icon(this.renderer.assets, this.renderer.textIcon, this.renderer.nodesContainer, this.fill, node.style.icon) - } else { - this.icon.update(node.style.icon) } /** @@ -140,6 +136,10 @@ export class NodeRenderer { const isVisible = this.visible() + if (isVisible) { + this.applyLabel().applyIcon() + } + // TODO - disable events if node has no event handlers // TODO - disable events if node pixel width < ~5px // TODO - disable events when dragging/scrolling @@ -155,9 +155,9 @@ export class NodeRenderer { const fillMounted = this.managers.nodes.isMounted(this.fill) if (isVisible && !fillMounted) { - this.fill.mount() - } else if (!isVisible && this.fill.mounted) { - this.fill.unmount() + this.managers.nodes.mount(this.fill) + } else if (!isVisible && fillMounted) { + this.managers.nodes.unmount(this.fill) } const shouldStrokesMount = isVisible && this.renderer.zoom > MIN_NODE_STROKE_ZOOM @@ -520,4 +520,26 @@ export class NodeRenderer { private get managers() { return this.renderer.managers } + + private applyLabel() { + const label = this.node.label + const style = this.node.style?.label + if (label !== undefined && label.trim() !== '' && this.label === undefined) { + this.label = new Text(this.renderer.assets, this.renderer.labelsContainer, label, style, DEFAULT_LABEL_STYLE) + this.label.offset = this.strokes.radius + this.label.moveTo(this.x, this.y) + } + + return this + } + + private applyIcon() { + const icon = this.node.style?.icon + if (icon !== undefined && this.icon === undefined) { + this.icon = new Icon(this.renderer.assets, this.renderer.textIcon, this.renderer.nodesContainer, this.fill, icon) + this.icon.moveTo(this.x, this.y) + } + + return this + } } diff --git a/src/renderers/webgl/objects/text/Text.ts b/src/renderers/webgl/objects/text/Text.ts index 384b1ee..06b61dd 100644 --- a/src/renderers/webgl/objects/text/Text.ts +++ b/src/renderers/webgl/objects/text/Text.ts @@ -46,23 +46,6 @@ export default class Text { } } - private loadFont(fontFamily: string, fontWeight: FontWeight) { - return this.assets.loadFont({ - fontFamily, - fontWeight, - resolve: () => { - this.font = undefined - this.style.fontLoading = false - if (this.isBitmapText()) { - this.transformText() - this.transformed = false - } else { - this.applyStyle() - } - } - }) - } - update(content: string, style: TextStyle | undefined) { const contentHasChanged = this.content !== content const styleHasChanged = !this.style.compare(style) @@ -91,11 +74,10 @@ export default class Text { } if (styleHasChanged) { - this.applyStyle().applyHighlight() - } - - if (this.transformed && this.highlight) { - this.highlight.text = this.object + this.applyHighlight() + if (!this.transformed) { + this.applyStyle() + } } const nextSpacing = [this.style.margin, this.style.highlight?.padding ?? 0] @@ -227,6 +209,8 @@ export default class Text { } private transformText() { + const rotation = this.object.rotation + this.transformed = true const isMounted = this.mounted @@ -234,6 +218,11 @@ export default class Text { this.object = this.create() this.object.x = this.x this.object.y = this.y + this.object.rotation = rotation + + if (this.highlight) { + this.highlight.text = this.object + } if (isMounted) { this.mount() @@ -241,17 +230,10 @@ export default class Text { } private applyStyle() { - if (this.isBitmapText(this.object)) { - this.object.align = this.style.align - this.object.fontSize = this.style.fontSize - this.object.letterSpacing = this.style.letterSpacing - if (this.object.fontName !== this.style.fontName) { - this.style.createFont() - this.object.fontName = this.style.fontName - } + this.object.anchor.set(...this.style.anchor) + this.highlight?.anchor.set(...this.style.anchor) - // TODO -> regenerate bitmap texture if any of the below styles change - } else { + if (!this.isBitmapText(this.object)) { this.object.style.stroke = this.style.stroke.color this.object.style.strokeThickness = this.style.stroke.width this.object.style.wordWrap = this.style.wordWrap @@ -262,11 +244,10 @@ export default class Text { this.object.style.align = this.style.align this.object.style.fontSize = this.style.fontSize this.object.style.fontFamily = this.style.fontFamily + } else { + this.transformText() } - this.object.anchor.set(...this.style.anchor) - this.highlight?.anchor.set(...this.style.anchor) - return this } @@ -321,4 +302,16 @@ export default class Text { return { top: hy, right: hx, bottom: hy, left: hx } } } + + private loadFont(fontFamily: string, fontWeight: FontWeight) { + return this.assets.loadFont({ + fontFamily, + fontWeight, + resolve: () => { + this.font = undefined + this.style.fontLoading = false + this.applyStyle() + } + }) + } } diff --git a/src/renderers/webgl/textures/TextTexture.ts b/src/renderers/webgl/textures/TextTexture.ts index 28bfdac..9d09e9b 100644 --- a/src/renderers/webgl/textures/TextTexture.ts +++ b/src/renderers/webgl/textures/TextTexture.ts @@ -139,8 +139,10 @@ export default class TextTexture { this._fontLoading = loading if (loading) { + this.fontName = `LoadingFont:${this.fontName}` this.fontFamily = this.defaultTextStyle.fontFamily } else { + this.fontName = this._style?.fontName ?? this.defaultTextStyle.fontName this.fontFamily = this._style?.fontFamily ?? this.defaultTextStyle.fontFamily } }