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 { graph } from "./editor"; import Toolbar from "./toolbar"; import * as Graph from "./graph"; 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 { 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) { 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) { this.selectedItem = item; } addSelectedItem(item) { this.selectedItems.add(item); } removeSelectedItem(item) { this.selectedItems.delete(item); } clearSelectedItems() { this.selectedItems.clear(); this.itemsContext = CONTEXT.nothing; } onNodeClick(node) { this.tool.onNodeClick(node); } onLinkClick(link) { this.tool.onLinkClick(link); } linkColor(link) { return 'red'; } nodeColor(node) { return 'black'; } onKeyDown(key) { var id = this.getKeyId(key); var previous = this.keyStates[id]; this.keyStates[id] = true; if (previous !== true) { this.tool.onKeyDown(key); } } onKeyUp(key) { var id = this.getKeyId(key); var previous = this.keyStates[id]; this.keyStates[id] = false; if (previous !== false) { this.tool.onKeyUp(key); } } getKeyId(key) { return key.keyCode; } nodeCanvasObject(node, ctx, globalScale) { var 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 if (node[Graph.NODE_IMAGE] !== undefined) { var path = node[Graph.NODE_IMAGE]; if (!path.includes("/")) { path = Graph.IMAGE_SRC + path; } var img = new Image(); img.src = path; ctx.drawImage( img, node.x - Graph.IMAGE_SIZE / 2, node.y - Graph.IMAGE_SIZE / 2, Graph.IMAGE_SIZE, Graph.IMAGE_SIZE ); } // Draw label if (this.labelVisible) { const label = node[Graph.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 = Graph.IMAGE_SIZE / 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, color, ctx) { var toolValue = this.tool.nodePointerAreaPaint(node, color, ctx); if (toolValue !== undefined) { return toolValue; } ctx.fillStyle = color; ctx.fillRect( node.x - Graph.IMAGE_SIZE / 2, node.y - Graph.IMAGE_SIZE / 2, Graph.IMAGE_SIZE, Graph.IMAGE_SIZE ); // draw square as pointer trap } nodeCanvasObjectMode(node) { var toolValue = this.tool.nodeCanvasObjectMode(node); if (toolValue !== undefined) { return toolValue; } return "after"; } linkCanvasObjectMode(link) { var toolValue = this.tool.linkCanvasObjectMode(link); if (toolValue !== undefined) { return toolValue; } return "replace"; } linkCanvasObject(link, ctx, globalScale) { var toolValue = this.tool.linkCanvasObject(link, ctx); if (toolValue !== undefined) { return toolValue; } // Links already initialized? if (link.source.x === undefined) { return undefined; } // Draw gradient link var 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", graph.getNodeColor(link.target)); gradient.addColorStop("1", graph.getNodeColor(link.source)); 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) { var toolValue = this.tool.linkWidth(link); if (toolValue !== undefined) { return toolValue; } return this.isLinkHighlighted(link) ? 2 : 1; } linkDirectionalParticles() { var toolValue = this.tool.linkDirectionalParticles(); if (toolValue !== undefined) { return toolValue; } return 4; } linkDirectionalParticleWidth(link) { var toolValue = this.tool.linkDirectionalParticleWidth(link); if (toolValue !== undefined) { return toolValue; } return this.isLinkHighlighted(link) ? Graph.LINK_PARTICLE_COUNT : 0; } onBackgroundClick(event, positions) { this.tool.onBackgroundClick(event, positions); } redraw() { this.display.setSelectedTool(this.tool); } isLinkHighlighted(link) { return ( this.selectedItem === link || graph.isLinkOnNode(link, this.selectedItem) ); } setLabelVisibility(visibility) { this.labelVisible = visibility; } }