From ac80b943a22cdde0252e1a46aea72293a3c1ac21 Mon Sep 17 00:00:00 2001 From: Matthias Konitzny <konitzny@ibr.cs.tu-bs.de> Date: Wed, 14 Sep 2022 14:46:35 +0200 Subject: [PATCH] Reworking select layer - WIP commit --- src/editor/components/selectlayer.tsx | 361 +++++++++++++++++--------- src/editor/editor.tsx | 12 +- src/editor/renderer.tsx | 22 +- 3 files changed, 245 insertions(+), 150 deletions(-) diff --git a/src/editor/components/selectlayer.tsx b/src/editor/components/selectlayer.tsx index 9b7872d..9b3a86e 100644 --- a/src/editor/components/selectlayer.tsx +++ b/src/editor/components/selectlayer.tsx @@ -1,70 +1,44 @@ -import React from "react"; -import { ReactNode } from "react"; +import React, { useState } from "react"; import { Node } from "../../common/graph/node"; import "./selectlayer.css"; +import { Coordinate2D } from "../../common/graph/graph"; -type propTypes = { - children: any; - allNodes: Node[]; +interface SelectLayerProps { + children: React.ReactNode | React.ReactNode[]; + nodes: Node[]; isEnabled: boolean; - screen2GraphCoords: (x: number, y: number) => any; + screen2GraphCoords: (x: number, y: number) => Coordinate2D; onBoxSelect: (nodes: Node[]) => void; -}; - -type layerCoordinates = { - x: number; - y: number; -}; - -export class SelectLayer extends React.Component<propTypes> { - private layerContainer: any; - private layerBox: any; - private initialSelectPoint: layerCoordinates = undefined; - - constructor(props: propTypes) { - super(props); - - this.isSelecting = this.isSelecting.bind(this); - this.onBoxSelect = this.onBoxSelect.bind(this); - this.boxSelectOnPointerDown = this.boxSelectOnPointerDown.bind(this); - this.boxSelectOnPointerMove = this.boxSelectOnPointerMove.bind(this); - this.boxSelectOnPointerUp = this.boxSelectOnPointerUp.bind(this); - - this.layerContainer = React.createRef(); - this.layerBox = React.createRef(); - } - - componentDidMount(): void { - this.setupBoxSelect(); - } - - setupBoxSelect() { - // Source: https://github.com/vasturiano/force-graph/issues/151#issuecomment-735850938 - this.layerContainer.current.onpointerdown = this.boxSelectOnPointerDown; - this.layerContainer.current.onpointermove = this.boxSelectOnPointerMove; - this.layerContainer.current.onpointerup = this.boxSelectOnPointerUp; - } +} - private isSelecting(): boolean { - if (!this.initialSelectPoint) { - return false; - } +interface Box { + bottom: number; + top: number; + left: number; + right: number; +} - if (!this.props.isEnabled) { - this.initialSelectPoint = undefined; - this.layerBox.current.className = ""; - return false; - } +function SelectLayer({ + children, + nodes, + onBoxSelect, + isEnabled, + screen2GraphCoords, +}: SelectLayerProps) { + const [selectionStart, setSelectionStart] = + useState<Coordinate2D>(undefined); + const [selectionEnd, setSelectionEnd] = useState<Coordinate2D>(undefined); - return true; + if (!isEnabled) { + return <>{children}</>; } - onBoxSelect(left: number, bottom: number, top: number, right: number) { + const makeSelection = (bb: Box) => { // Filter out selected nodes const hitNodes: Node[] = []; - const tl = this.props.screen2GraphCoords(left, top); - const br = this.props.screen2GraphCoords(right, bottom); - this.props.allNodes.forEach((node: any) => { + const tl = screen2GraphCoords(bb.left, bb.top); + const br = screen2GraphCoords(bb.right, bb.bottom); + nodes.forEach((node: Node) => { if ( tl.x < node.x && node.x < br.x && @@ -75,87 +49,216 @@ export class SelectLayer extends React.Component<propTypes> { hitNodes.push(node); } }); + onBoxSelect(hitNodes); + }; - this.props.onBoxSelect(hitNodes); - } - - boxSelectOnPointerDown(e: any) { - if (!this.props.isEnabled) { - return; - } - - e.preventDefault(); - this.layerBox.current.style.left = e.offsetX.toString() + "px"; - this.layerBox.current.style.top = e.offsetY.toString() + "px"; - this.layerBox.current.style.width = "0px"; - this.layerBox.current.style.height = "0px"; - this.initialSelectPoint = { - x: e.offsetX, - y: e.offsetY, + const getBoundingBox = (p1: Coordinate2D, p2: Coordinate2D): Box => { + return { + left: Math.min(p1.x, p2.x), + top: Math.min(p1.y, p2.y), + right: Math.max(p1.x, p2.x), + bottom: Math.max(p1.x, p2.x), }; - this.layerBox.current.className = "visible"; - } + }; - boxSelectOnPointerMove(e: any) { - if (!this.isSelecting()) { - return; - } + const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => { + e.preventDefault(); + makeSelection(getBoundingBox(selectionStart, selectionEnd)); + setSelectionStart(undefined); + setSelectionEnd(undefined); + }; + const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => { e.preventDefault(); - if (e.offsetX < this.initialSelectPoint.x) { - this.layerBox.current.style.left = e.offsetX.toString() + "px"; - this.layerBox.current.style.width = - (this.initialSelectPoint.x - e.offsetX).toString() + "px"; - } else { - this.layerBox.current.style.left = - this.initialSelectPoint.x.toString() + "px"; - this.layerBox.current.style.width = - (e.offsetX - this.initialSelectPoint.x).toString() + "px"; - } - if (e.offsetY < this.initialSelectPoint.y) { - this.layerBox.current.style.top = e.offsetY.toString() + "px"; - this.layerBox.current.style.height = - (this.initialSelectPoint.y - e.offsetY).toString() + "px"; - } else { - this.layerBox.current.style.top = - this.initialSelectPoint.y.toString() + "px"; - this.layerBox.current.style.height = - (e.offsetY - this.initialSelectPoint.y).toString() + "px"; - } - } + setSelectionStart({ x: e.screenX, y: e.screenY }); + setSelectionEnd({ x: e.screenX, y: e.screenY }); + }; - boxSelectOnPointerUp(e: any) { - if (!this.isSelecting()) { - return; - } + const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => { + setSelectionEnd({ x: e.screenX, y: e.screenY }); + }; - e.preventDefault(); - let left, bottom, top, right; - if (e.offsetX < this.initialSelectPoint.x) { - left = e.offsetX; - right = this.initialSelectPoint.x; - } else { - left = this.initialSelectPoint.x; - right = e.offsetX; - } - if (e.offsetY < this.initialSelectPoint.y) { - top = e.offsetY; - bottom = this.initialSelectPoint.y; - } else { - top = this.initialSelectPoint.y; - bottom = e.offsetY; - } - this.initialSelectPoint = undefined; - this.layerBox.current.className = ""; - this.onBoxSelect(left, bottom, top, right); - } + let width = 0; + let height = 0; + let left = 0; + let top = 0; - render(): ReactNode { - return ( - <div ref={this.layerContainer} id="select-layer"> - <div ref={this.layerBox} id="box-select"></div> - {this.props.children} - </div> - ); + if (selectionStart && selectionEnd) { + const box = getBoundingBox(selectionStart, selectionEnd); + width = box.right - box.left; + height = box.bottom - box.top; + left = box.left; + top = box.top; } + + return ( + <div + onPointerDown={handlePointerDown} + onPointerMove={handlePointerMove} + onPointerUp={handlePointerUp} + id="select-layer" + > + <div + id="box-select" + className={selectionStart ? "visible" : ""} + style={{ width: width, height: height, left: left, top: top }} + ></div> + {children} + </div> + ); } + +export default SelectLayer; + +// export class SelectLayer extends React.Component<propTypes> { +// private layerContainer: React.RefObject<HTMLDivElement>; +// private layerBox: React.RefObject<HTMLDivElement>; +// private initialSelectPoint: layerCoordinates = undefined; +// +// constructor(props: propTypes) { +// super(props); +// +// this.isSelecting = this.isSelecting.bind(this); +// this.onBoxSelect = this.onBoxSelect.bind(this); +// this.boxSelectOnPointerDown = this.boxSelectOnPointerDown.bind(this); +// this.boxSelectOnPointerMove = this.boxSelectOnPointerMove.bind(this); +// this.boxSelectOnPointerUp = this.boxSelectOnPointerUp.bind(this); +// +// this.layerContainer = React.createRef(); +// this.layerBox = React.createRef(); +// } +// +// componentDidMount(): void { +// this.setupBoxSelect(); +// } +// +// setupBoxSelect() { +// // Source: https://github.com/vasturiano/force-graph/issues/151#issuecomment-735850938 +// this.layerContainer.current.addEventListener( +// "pointerdown", +// this.boxSelectOnPointerDown +// ); +// this.layerContainer.current.addEventListener( +// "pointermove", +// this.boxSelectOnPointerMove +// ); +// this.layerContainer.current.addEventListener( +// "pointerup", +// this.boxSelectOnPointerUp +// ); +// } +// +// private isSelecting(): boolean { +// if (!this.initialSelectPoint) { +// return false; +// } +// +// if (!this.props.isEnabled) { +// this.initialSelectPoint = undefined; +// this.layerBox.current.className = ""; +// return false; +// } +// +// return true; +// } +// +// onBoxSelect(left: number, bottom: number, top: number, right: number) { +// // Filter out selected nodes +// const hitNodes: Node[] = []; +// const tl = this.props.screen2GraphCoords(left, top); +// const br = this.props.screen2GraphCoords(right, bottom); +// this.props.allNodes.forEach((node: Node) => { +// if ( +// tl.x < node.x && +// node.x < br.x && +// br.y > node.y && +// node.y > tl.y +// ) { +// // Add node if in box area +// hitNodes.push(node); +// } +// }); +// +// this.props.onBoxSelect(hitNodes); +// } +// +// boxSelectOnPointerDown(e: PointerEvent) { +// if (!this.props.isEnabled) { +// return; +// } +// +// e.preventDefault(); +// this.layerBox.current.style.left = e.offsetX.toString() + "px"; +// this.layerBox.current.style.top = e.offsetY.toString() + "px"; +// this.layerBox.current.style.width = "0px"; +// this.layerBox.current.style.height = "0px"; +// this.initialSelectPoint = { +// x: e.offsetX, +// y: e.offsetY, +// }; +// this.layerBox.current.className = "visible"; +// } +// +// boxSelectOnPointerMove(e: PointerEvent) { +// if (!this.isSelecting()) { +// return; +// } +// +// e.preventDefault(); +// if (e.offsetX < this.initialSelectPoint.x) { +// this.layerBox.current.style.left = e.offsetX.toString() + "px"; +// this.layerBox.current.style.width = +// (this.initialSelectPoint.x - e.offsetX).toString() + "px"; +// } else { +// this.layerBox.current.style.left = +// this.initialSelectPoint.x.toString() + "px"; +// this.layerBox.current.style.width = +// (e.offsetX - this.initialSelectPoint.x).toString() + "px"; +// } +// if (e.offsetY < this.initialSelectPoint.y) { +// this.layerBox.current.style.top = e.offsetY.toString() + "px"; +// this.layerBox.current.style.height = +// (this.initialSelectPoint.y - e.offsetY).toString() + "px"; +// } else { +// this.layerBox.current.style.top = +// this.initialSelectPoint.y.toString() + "px"; +// this.layerBox.current.style.height = +// (e.offsetY - this.initialSelectPoint.y).toString() + "px"; +// } +// } +// +// boxSelectOnPointerUp(e: PointerEvent) { +// if (!this.isSelecting()) { +// return; +// } +// +// e.preventDefault(); +// let left, bottom, top, right; +// if (e.offsetX < this.initialSelectPoint.x) { +// left = e.offsetX; +// right = this.initialSelectPoint.x; +// } else { +// left = this.initialSelectPoint.x; +// right = e.offsetX; +// } +// if (e.offsetY < this.initialSelectPoint.y) { +// top = e.offsetY; +// bottom = this.initialSelectPoint.y; +// } else { +// top = this.initialSelectPoint.y; +// bottom = e.offsetY; +// } +// this.initialSelectPoint = undefined; +// this.layerBox.current.className = ""; +// this.onBoxSelect(left, bottom, top, right); +// } +// +// render(): ReactNode { +// return ( +// <div ref={this.layerContainer} id="select-layer"> +// <div ref={this.layerBox} id="box-select"></div> +// {this.props.children} +// </div> +// ); +// } +// } diff --git a/src/editor/editor.tsx b/src/editor/editor.tsx index 73807c4..da04c04 100644 --- a/src/editor/editor.tsx +++ b/src/editor/editor.tsx @@ -7,7 +7,7 @@ import * as Helpers from "../common/helpers"; import { Node, NodeProperties } from "../common/graph/node"; import { SpaceManager } from "./components/spacemanager"; -import { SelectLayer } from "./components/selectlayer"; +import SelectLayer from "./components/selectlayer"; import { GraphData } from "../common/graph/graph"; import { NodeType } from "../common/graph/nodetype"; import { GraphRenderer2D } from "./renderer"; @@ -107,13 +107,13 @@ export class Editor extends React.PureComponent<any, stateTypes> { keyPressed(key: string) { const keys = this.state.keys; keys[key] = true; - this.setState({ keys: keys }); + this.setState({ keys: { ...keys } }); } keyReleased(key: string) { const keys = this.state.keys; keys[key] = false; - this.setState({ keys: keys }); + this.setState({ keys: { ...keys } }); } /** @@ -230,11 +230,7 @@ export class Editor extends React.PureComponent<any, stateTypes> { ref={this.graphContainer} > <SelectLayer - allNodes={ - this.state.graph - ? this.state.graph.nodes - : [] - } + nodes={this.state.graph.nodes} screen2GraphCoords={ this.rendererRef ? this.rendererRef.current diff --git a/src/editor/renderer.tsx b/src/editor/renderer.tsx index 67be19d..831d396 100644 --- a/src/editor/renderer.tsx +++ b/src/editor/renderer.tsx @@ -108,10 +108,10 @@ export class GraphRenderer2D extends React.PureComponent< if (this.keys["Control"]) { // Connect to clicked node as parent while control is pressed - if (this.state.selectedNodes.length == 0) { + if (this.props.selectedNodes.length == 0) { // Have no node connected, so select this.props.onNodeSelectionChanged([node]); - } else if (!this.state.selectedNodes.includes(node)) { + } else if (!this.props.selectedNodes.includes(node)) { // Already have *other* node/s selected, so connect this.connectSelectionToNode(node); } @@ -121,7 +121,6 @@ export class GraphRenderer2D extends React.PureComponent< // By default, simply select node this.props.onNodeSelectionChanged([node]); } - //this.forceUpdate(); // TODO: Remove? } /** @@ -216,7 +215,7 @@ export class GraphRenderer2D extends React.PureComponent< // Add node to selection selection.push(node); } - this.props.onNodeSelectionChanged(selection); + this.props.onNodeSelectionChanged([...selection]); } private handleEngineStop() { @@ -231,19 +230,16 @@ export class GraphRenderer2D extends React.PureComponent< private handleNodeDrag(node: Node) { this.graphInFocus = true; - if ( - !this.state.selectedNodes || - !this.state.selectedNodes.includes(node) - ) { - this.props.onNodeSelectionChanged([node]); - } + // if (!this.props.selectedNodes.includes(node)) { + // this.props.onNodeSelectionChanged([...this.props.selectedNodes, node]); + // } // Should run connect logic? - if (!this.state.connectOnDrag) { + if (!this.props.connectOnDrag) { return; } - const closest = this.state.graph.getClosestNode(node.x, node.y, node); + const closest = this.props.graph.getClosestNode(node.x, node.y, node); // Is close enough for new link? if (closest.distance > this.maxDistanceToConnect) { @@ -256,7 +252,7 @@ export class GraphRenderer2D extends React.PureComponent< } // Add link - node.connect(closest.node); + node.connect(closest.node); // TODO: Change must propagate // this.forceUpdate(); TODO: Remove? } -- GitLab