Skip to content

Commit

Permalink
[breadboard-ui] Allow expand/collapse of nodes (#1505)
Browse files Browse the repository at this point in the history
  • Loading branch information
paullewis authored Apr 25, 2024
1 parent cdc23bb commit dbd9267
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/beige-wolves-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@google-labs/breadboard-web": patch
"@google-labs/breadboard-ui": patch
---

Teach graph how to expand and collapse nodes
5 changes: 5 additions & 0 deletions packages/breadboard-ui/src/elements/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export class Editor extends LitElement {
@property()
boardId: number = -1;

@property()
collapseNodesByDefault = false;

@property()
kits: Kit[] = [];

Expand Down Expand Up @@ -314,6 +317,8 @@ export class Editor extends LitElement {
}
}

// TODO: Make this a flag.
this.#graph.collapseNodesByDefault = this.collapseNodesByDefault;
this.#graph.ports = ports;
this.#graph.edges = breadboardGraph.edges();
this.#graph.nodes = breadboardGraph.nodes();
Expand Down
184 changes: 172 additions & 12 deletions packages/breadboard-ui/src/elements/editor/graph-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { InspectablePort } from "@google-labs/breadboard";
import { InspectablePort, PortStatus } from "@google-labs/breadboard";
import * as PIXI from "pixi.js";
import { GRAPH_OPERATIONS, GraphNodePortType } from "./types.js";
import { GraphNodePort } from "./graph-node-port.js";
Expand All @@ -26,6 +26,8 @@ const outputNodeColor = getGlobalColor("--bb-output-100");
// TODO: Enable board node coloring.
// const boardNodeColor = getGlobalColor('--bb-boards-100');

const DBL_CLICK_DELTA = 450;

export class GraphNode extends PIXI.Graphics {
#width = 0;
#height = 0;
Expand Down Expand Up @@ -64,6 +66,12 @@ export class GraphNode extends PIXI.Graphics {
#outPortLocations: Map<string, PIXI.ObservablePoint<unknown>> = new Map();
#editable = false;
#selected = false;
#collapsed = false;
#emitCollapseToggleEventOnNextDraw = false;

#headerInPort = new GraphNodePort(GraphNodePortType.IN);
#headerOutPort = new GraphNodePort(GraphNodePortType.OUT);
#lastClickTime = 0;

constructor(id: string, type: string, title: string) {
super();
Expand Down Expand Up @@ -95,13 +103,55 @@ export class GraphNode extends PIXI.Graphics {

this.eventMode = "static";
this.cursor = "pointer";

this.addChild(this.#headerInPort);
this.addChild(this.#headerOutPort);

this.#headerInPort.editable = false;
this.#headerOutPort.editable = false;

this.#headerInPort.visible = false;
this.#headerOutPort.visible = false;
}

addPointerEventListeners() {
let dragStart: PIXI.IPointData | null = null;
let originalPosition: PIXI.ObservablePoint<unknown> | null = null;
let hasMoved = false;

this.addEventListener("click", (evt: PIXI.FederatedPointerEvent) => {
const clickDelta = window.performance.now() - this.#lastClickTime;
this.#lastClickTime = window.performance.now();

if (clickDelta > DBL_CLICK_DELTA) {
return;
}

if (!this.#titleText) {
return;
}

const titleHeight =
this.#padding + this.#titleText.height + this.#padding;

const nodeGlobal = this.getBounds();
const titleY = this.toGlobal({ x: 0, y: titleHeight }).y;

const isInHeaderArea =
evt.global.x > nodeGlobal.left &&
evt.global.x < nodeGlobal.right &&
evt.global.y > nodeGlobal.top &&
evt.global.y < titleY;

if (!isInHeaderArea) {
return;
}

this.collapsed = !this.collapsed;
this.#emitCollapseToggleEventOnNextDraw = true;
this.#lastClickTime = 0;
});

this.addEventListener("pointerdown", (evt: PIXI.FederatedPointerEvent) => {
if (!(evt.target instanceof GraphNode)) {
return;
Expand Down Expand Up @@ -175,7 +225,7 @@ export class GraphNode extends PIXI.Graphics {
return;
}

this.#titleText.text = `${this.#type} (${this.#nodeTitle})`;
this.#titleText.text = this.#nodeTitle;
this.#isDirty = true;
}

Expand All @@ -188,13 +238,22 @@ export class GraphNode extends PIXI.Graphics {
this.#isDirty = true;
}

get collapsed() {
return this.#collapsed;
}

set collapsed(collapsed: boolean) {
this.#collapsed = collapsed;
this.#isDirty = true;
}

get type() {
return this.#type;
}

set type(type: string) {
this.#type = type;
this.#title = `${type} (${this.#nodeTitle})`;
this.#title = `${this.#nodeTitle}`;
this.#clearOldTitle();

this.#isDirty = true;
Expand Down Expand Up @@ -294,6 +353,11 @@ export class GraphNode extends PIXI.Graphics {

this.emit(GRAPH_OPERATIONS.GRAPH_NODE_DRAWN);
}

if (this.#emitCollapseToggleEventOnNextDraw) {
this.#emitCollapseToggleEventOnNextDraw = false;
this.emit(GRAPH_OPERATIONS.GRAPH_NODE_EXPAND_COLLAPSE);
}
}

set inPorts(ports: InspectablePort[]) {
Expand Down Expand Up @@ -413,7 +477,11 @@ export class GraphNode extends PIXI.Graphics {

// Height calculations.
let height = this.#padding + (this.#titleText?.height || 0) + this.#padding;
height += this.#padding + portCount * portRowHeight + this.#padding;

// Only add the port heights on when the node is expanded.
if (!this.collapsed) {
height += this.#padding + portCount * portRowHeight + this.#padding;
}

// Width calculations.
let width = this.#padding + (this.#titleText?.width || 0) + this.#padding;
Expand Down Expand Up @@ -444,8 +512,89 @@ export class GraphNode extends PIXI.Graphics {

this.#drawBackground();
const portStartY = this.#drawTitle();
this.#drawInPorts(portStartY);
this.#drawOutPorts(portStartY);

if (this.collapsed) {
this.#hideAllPorts();
this.#showHeaderPorts();
} else {
this.#drawInPorts(portStartY);
this.#drawOutPorts(portStartY);
this.#hideHeaderPorts();
}
}

#showHeaderPorts() {
this.#headerInPort.visible = true;
this.#headerOutPort.visible = true;

const titleHeight =
this.#padding + (this.#titleText?.height || 0) + this.#padding;

this.#headerInPort.y = titleHeight / 2;
this.#headerOutPort.y = titleHeight / 2;

this.#headerInPort.x = 0;
this.#headerOutPort.x = this.#width;

let inPortStatus = this.#headerInPort.status;
for (const inPort of this.#inPorts.values()) {
if (!inPort) {
continue;
}

if (inPort.port.status === PortStatus.Connected) {
inPortStatus = PortStatus.Connected;
break;
}
}

if (inPortStatus !== this.#headerInPort.status) {
this.#headerInPort.status = inPortStatus;
}

let outPortStatus = this.#headerOutPort.status;
for (const outPort of this.#outPorts.values()) {
if (!outPort) {
continue;
}

if (outPort.port.status === PortStatus.Connected) {
outPortStatus = PortStatus.Connected;
break;
}
}

if (outPortStatus !== this.#headerOutPort.status) {
this.#headerOutPort.status = outPortStatus;
}
}

#hideHeaderPorts() {
this.#headerInPort.visible = false;
this.#headerOutPort.visible = false;
}

#hideAllPorts() {
for (const portItem of this.#inPorts.values()) {
if (!portItem) {
continue;
}

portItem.label.visible = false;
portItem.nodePort.visible = false;
}

for (const portItem of this.#outPorts.values()) {
if (!portItem) {
continue;
}

portItem.label.visible = false;
portItem.nodePort.visible = false;
}

this.#inPortLocations.clear();
this.#outPortLocations.clear();
}

#drawBackground() {
Expand Down Expand Up @@ -482,12 +631,15 @@ export class GraphNode extends PIXI.Graphics {
this.#padding + this.#titleText.height + this.#padding;
this.beginFill(this.#color);
this.drawRoundedRect(0, 0, this.#width, titleHeight, this.#borderRadius);
this.drawRect(
0,
titleHeight - 2 * this.#borderRadius,
this.#width,
2 * this.#borderRadius
);

if (!this.collapsed) {
this.drawRect(
0,
titleHeight - 2 * this.#borderRadius,
this.#width,
2 * this.#borderRadius
);
}
this.endFill();
}
}
Expand Down Expand Up @@ -573,10 +725,18 @@ export class GraphNode extends PIXI.Graphics {
}

inPortLocation(name: string): PIXI.ObservablePoint<unknown> | null {
if (this.collapsed) {
return this.#headerInPort.position;
}

return this.#inPortLocations.get(name) || null;
}

outPortLocation(name: string): PIXI.ObservablePoint<unknown> | null {
if (this.collapsed) {
return this.#headerOutPort.position;
}

return this.#outPortLocations.get(name) || null;
}
}
36 changes: 36 additions & 0 deletions packages/breadboard-ui/src/elements/editor/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class Graph extends PIXI.Container {
#highlightPadding = 8;
#editable = false;

collapseNodesByDefault = false;
layoutRect: DOMRectReadOnly | null = null;

constructor() {
Expand Down Expand Up @@ -82,6 +83,10 @@ export class Graph extends PIXI.Container {
}

if (evt.target instanceof GraphNodePort) {
if (!evt.target.editable) {
return;
}

nodePortBeingEdited = evt.target;
nodeBeingEdited = evt.target.parent as GraphNode;
nodePortBeingEdited.overrideStatus = PortStatus.Connected;
Expand Down Expand Up @@ -109,6 +114,17 @@ export class Graph extends PIXI.Container {
nodePortBeingEdited
);

// Both nodes need to be open before a change can be made. Otherwise
// we don't know exactly which edge is being edited.
if (
edgeBeingEdited &&
(edgeBeingEdited.toNode.collapsed ||
edgeBeingEdited.fromNode.collapsed)
) {
edgeBeingEdited = null;
return;
}

nodePortType = GraphNodePortType.IN;
if (!edgeBeingEdited) {
originalEdgeDescriptor = {
Expand Down Expand Up @@ -615,6 +631,7 @@ export class Graph extends PIXI.Container {
if (!graphNode) {
graphNode = new GraphNode(id, node.descriptor.type, node.title());
graphNode.editable = this.editable;
graphNode.collapsed = this.collapseNodesByDefault;

this.#nodeById.set(id, graphNode);
}
Expand Down Expand Up @@ -655,6 +672,10 @@ export class Graph extends PIXI.Container {
graphNode,
layout: this.#layout.get(id) || null,
});

graphNode.on(GRAPH_OPERATIONS.GRAPH_NODE_EXPAND_COLLAPSE, () => {
this.#redrawAllEdges();
});
this.addChild(graphNode);
}

Expand Down Expand Up @@ -690,6 +711,21 @@ export class Graph extends PIXI.Container {
return edgeGraphic;
}

#redrawAllEdges() {
if (!this.#edges) {
return;
}

for (const edge of this.#edges) {
const edgeGraphic = this.#edgeGraphics.get(edgeToString(edge));
if (!edgeGraphic) {
continue;
}

edgeGraphic.forceRedraw();
}
}

#drawEdges() {
if (!this.#edges) {
return;
Expand Down
1 change: 1 addition & 0 deletions packages/breadboard-ui/src/elements/editor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export enum GRAPH_OPERATIONS {
GRAPH_EDGE_ATTACH = "graphedgeattach",
GRAPH_EDGE_DETACH = "graphedgedetach",
GRAPH_EDGE_CHANGE = "graphedgechange",
GRAPH_NODE_EXPAND_COLLAPSE = "graphnodeexpandcollapse",
}

export enum GraphNodePortType {
Expand Down
Loading

0 comments on commit dbd9267

Please sign in to comment.