import Tool from "./tools/tool"; import UndoTool from "./tools/undotool"; import RedoTool from "./tools/redotool"; import SelectTool from "./tools/selecttool"; import DeleteTool from "./tools/deletetool"; import AddNodeTool from "./tools/addnodetool"; import ConnectTool from "./tools/connecttool"; import SettingsTool from "./tools/settingstool"; import SaveTool from "./tools/savetool"; import Toolbar from "./toolbar"; import { DRAG_THRESHOLD_2D } from "../../config"; import { GraphElement } from "./structures/graph/graphelement"; import { Node } from "./structures/graph/node"; export const TOOLS = { undo: new UndoTool("undo"), redo: new RedoTool("redo"), select: new SelectTool("select"), delete: new DeleteTool("delete"), addnode: new AddNodeTool("addnode"), connect: new ConnectTool("connect"), settings: new SettingsTool("settings"), save: new SaveTool("save"), }; export const CONTEXT = { node: "node", link: "link", mixed: "mixed", nothing: "nothing", }; export class State extends Tool { display: Toolbar; tool: Tool; previousTool: Tool; selectedItem: GraphElement; selectedItems: Set<GraphElement>; itemsContext: any; labelVisible: boolean; keyStates: any; constructor() { super("State"); this.display = new Toolbar(TOOLS); this.tool = undefined; this.setTool(TOOLS.select); // Shared variables this.selectedItem = undefined; this.selectedItems = new Set(); this.itemsContext = CONTEXT.nothing; this.labelVisible = true; this.keyStates = {}; } setTool(tool: Tool) { if (this.tool === tool) { return; } if (this.tool !== undefined) { this.tool.deactivateTool(tool); } this.previousTool = this.tool; this.tool = tool; this.display.setSelectedTool(tool); if (this.tool !== undefined) { this.tool.activateTool(); } } setSelectedItem(item: GraphElement) { this.selectedItem = item; } addSelectedItem(item: GraphElement) { this.selectedItems.add(item); } addSelectedItems(items: GraphElement) { Object.values(items).forEach((item) => { this.selectedItems.add(item); }); } removeSelectedItem(item: GraphElement) { this.selectedItems.delete(item); } clearSelectedItems() { this.selectedItems.clear(); this.itemsContext = CONTEXT.nothing; } onNodeClick(node: GraphElement) { this.tool.onNodeClick(node); } onNodeDragEnd(node: any, translate: any) { // Handle as click event, if drag distance under a certain threshold const distanceDragged = Math.sqrt( Math.pow(translate.x, 2) + Math.pow(translate.y, 2) ); if (distanceDragged < DRAG_THRESHOLD_2D) { this.onNodeClick(node); return; } else { this.tool.onNodeDragEnd(node, translate); } } onLinkClick(link: any) { this.tool.onLinkClick(link); } linkColor(link: any) { return "red"; } nodeColor(node: any) { return "black"; } onKeyDown(key: any) { const id = this.getKeyId(key); const previous = this.keyStates[id]; this.keyStates[id] = true; if (previous !== true) { this.tool.onKeyDown(key); } } onKeyUp(key: any) { const id = this.getKeyId(key); const previous = this.keyStates[id]; this.keyStates[id] = false; if (previous !== false) { this.tool.onKeyUp(key); } } getKeyId(key: any) { return key.keyCode; } nodeCanvasObject(node: any, ctx: any, globalScale: any) { const toolValue = this.tool.nodeCanvasObject(node, ctx); if (toolValue !== undefined) { return toolValue; } // TODO: Clean up function // add ring just for highlighted nodes if (this.selectedItem === node || this.selectedItems.has(node)) { ctx.beginPath(); ctx.arc(node.x, node.y, 4 * 0.6, 0, 2 * Math.PI, false); ctx.fillStyle = this.selectedItem === node ? "red" : "white"; ctx.fill(); } // Draw image const imageSize = 12; if (node.icon !== undefined) { const img = new Image(); img.src = node.icon.link; ctx.drawImage( img, node.x - imageSize / 2, node.y - imageSize / 2, imageSize, imageSize ); } // Draw label if (this.labelVisible) { const label = node.label; const fontSize = 11 / globalScale; ctx.font = `${fontSize}px Sans-Serif`; const textWidth = ctx.measureText(label).width; const bckgDimensions = [textWidth, fontSize].map( (n) => n + fontSize * 0.2 ); // some padding const nodeHeightOffset = imageSize / 3 + bckgDimensions[1]; ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; ctx.fillRect( node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2 + nodeHeightOffset, ...bckgDimensions ); ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = "white"; ctx.fillText(label, node.x, node.y + nodeHeightOffset); } // TODO: Render label as always visible } nodePointerAreaPaint(node: any, color: any, ctx: any) { const toolValue = this.tool.nodePointerAreaPaint(node, color, ctx); if (toolValue !== undefined) { return toolValue; } const imageSize = 12; ctx.fillStyle = color; ctx.fillRect( node.x - imageSize / 2, node.y - imageSize / 2, imageSize, imageSize ); // draw square as pointer trap } nodeCanvasObjectMode(node: any) { const toolValue = this.tool.nodeCanvasObjectMode(node); if (toolValue !== undefined) { return toolValue; } return "after"; } linkCanvasObjectMode(link: any) { const toolValue = this.tool.linkCanvasObjectMode(link); if (toolValue !== undefined) { return toolValue; } return "replace"; } linkCanvasObject(link: any, ctx: any, globalScale: any) { const toolValue = this.tool.linkCanvasObject(link, ctx); if (toolValue !== undefined) { return toolValue; } // Links already initialized? if (link.source.x === undefined) { return undefined; } // Draw gradient link const gradient = ctx.createLinearGradient( link.source.x, link.source.y, link.target.x, link.target.y ); // Have reversed colors // Color at source node referencing the target node and vice versa gradient.addColorStop("0", link.target.type.color); gradient.addColorStop("1", link.source.type.color); ctx.beginPath(); ctx.moveTo(link.source.x, link.source.y); ctx.lineTo(link.target.x, link.target.y); ctx.strokeStyle = gradient; ctx.stroke(); // Only render strokes on last link // var lastLink = graph.data[Graph.GRAPH_LINKS][graph.data[Graph.GRAPH_LINKS].length - 1]; // if (link === lastLink) { // ctx.stroke(); // } return undefined; } linkWidth(link: any) { const toolValue = this.tool.linkWidth(link); if (toolValue !== undefined) { return toolValue; } return this.isLinkHighlighted(link) ? 2 : 1; } linkDirectionalParticles() { const toolValue = this.tool.linkDirectionalParticles(); if (toolValue !== undefined) { return toolValue; } return 4; } linkDirectionalParticleWidth(link: any) { const toolValue = this.tool.linkDirectionalParticleWidth(link); if (toolValue !== undefined) { return toolValue; } return this.isLinkHighlighted(link) ? 4 : 0; } onBackgroundClick(event: any, positions: any) { this.tool.onBackgroundClick(event, positions); } redraw() { this.display.setSelectedTool(this.tool); } isLinkHighlighted(link: any) { return false; // Code after that is causing issues if (this.selectedItem === link) { return true; } if (this.selectedItem.node) { return (this.selectedItem as Node).links.includes(link); } return false; } setLabelVisibility(visibility: boolean) { this.labelVisible = visibility; } }