Node graph box selection when dragging (#1616)

* Node box selection

* Cancel box selection on rmb
This commit is contained in:
0HyperCube 2024-02-17 22:24:53 +00:00 committed by GitHub
parent a50b6b0a09
commit 80bffd39bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 77 additions and 6 deletions

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { getContext, tick } from "svelte"; import { getContext, onMount, tick } from "svelte";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import { FADE_TRANSITION } from "@graphite/consts"; import { FADE_TRANSITION } from "@graphite/consts";
@ -35,6 +35,9 @@
let transform = { scale: 1, x: 1200, y: 0 }; let transform = { scale: 1, x: 1200, y: 0 };
let panning = false; let panning = false;
let draggingNodes: { startX: number; startY: number; roundX: number; roundY: number } | undefined = undefined; let draggingNodes: { startX: number; startY: number; roundX: number; roundY: number } | undefined = undefined;
type Box = { startX: number; startY: number; endX: number; endY: number };
let boxSelection: Box | undefined = undefined;
let previousSelection: bigint[] = [];
let selectIfNotDragged: undefined | bigint = undefined; let selectIfNotDragged: undefined | bigint = undefined;
let linkInProgressFromConnector: SVGSVGElement | undefined = undefined; let linkInProgressFromConnector: SVGSVGElement | undefined = undefined;
let linkInProgressToConnector: SVGSVGElement | DOMRect | undefined = undefined; let linkInProgressToConnector: SVGSVGElement | DOMRect | undefined = undefined;
@ -48,6 +51,7 @@
let inputs: SVGSVGElement[][] = []; let inputs: SVGSVGElement[][] = [];
let outputs: SVGSVGElement[][] = []; let outputs: SVGSVGElement[][] = [];
let nodeElements: HTMLDivElement[] = [];
$: watchNodes($nodeGraph.nodes); $: watchNodes($nodeGraph.nodes);
@ -171,6 +175,8 @@
}); });
} }
onMount(refreshLinks);
function nodeIcon(nodeName: string): IconName { function nodeIcon(nodeName: string): IconName {
const iconMap: Record<string, IconName> = { const iconMap: Record<string, IconName> = {
Output: "NodeOutput", Output: "NodeOutput",
@ -439,9 +445,16 @@
return; return;
} }
// Clicked on the graph background with something selected, so we deselect everything // Clicked on the graph background so we box select
if (lmb && $nodeGraph.selected.length !== 0) { if (lmb) {
editor.instance.selectNodes(new BigUint64Array([])); previousSelection = $nodeGraph.selected;
// Clear current selection
if (!e.shiftKey) editor.instance.selectNodes(new BigUint64Array(0));
const graphBounds = graph?.getBoundingClientRect();
boxSelection = { startX: e.x - (graphBounds?.x || 0), startY: e.y - (graphBounds?.y || 0), endX: e.x - (graphBounds?.x || 0), endY: e.y - (graphBounds?.y || 0) };
return;
} }
// LMB clicked on the graph background or MMB clicked anywhere // LMB clicked on the graph background or MMB clicked anywhere
@ -491,9 +504,43 @@
DRAG_SMOOTHING_TIME * 1000 + 10, DRAG_SMOOTHING_TIME * 1000 + 10,
); );
} }
} else if (boxSelection) {
// The mouse button was released but we missed the pointer up event
if ((e.buttons & 1) === 0) {
completeBoxSelection();
boxSelection = undefined;
} else if ((e.buttons & 2) !== 0) {
editor.instance.selectNodes(new BigUint64Array(previousSelection));
boxSelection = undefined;
} else {
const graphBounds = graph?.getBoundingClientRect();
boxSelection.endX = e.x - (graphBounds?.x || 0);
boxSelection.endY = e.y - (graphBounds?.y || 0);
}
} }
} }
function intersetNodeAABB(boxSelection: Box | undefined, nodeIndex: number): boolean {
const bounds = nodeElements[nodeIndex]?.getBoundingClientRect();
const graphBounds = graph?.getBoundingClientRect();
return (
boxSelection !== undefined &&
bounds &&
Math.min(boxSelection.startX, boxSelection.endX) < bounds.right - (graphBounds?.x || 0) &&
Math.max(boxSelection.startX, boxSelection.endX) > bounds.left - (graphBounds?.x || 0) &&
Math.min(boxSelection.startY, boxSelection.endY) < bounds.bottom - (graphBounds?.y || 0) &&
Math.max(boxSelection.startY, boxSelection.endY) > bounds.top - (graphBounds?.y || 0)
);
}
function completeBoxSelection() {
editor.instance.selectNodes(new BigUint64Array($nodeGraph.selected.concat($nodeGraph.nodes.filter((_, nodeIndex) => intersetNodeAABB(boxSelection, nodeIndex)).map((node) => node.id))));
}
function showSelected(selected: bigint[], boxSelect: Box | undefined, node: bigint, nodeIndex: number): boolean {
return selected.includes(node) || intersetNodeAABB(boxSelect, nodeIndex);
}
function toggleLayerVisibility(id: bigint) { function toggleLayerVisibility(id: bigint) {
editor.instance.toggleLayerVisibility(id); editor.instance.toggleLayerVisibility(id);
} }
@ -611,6 +658,9 @@
draggingNodes = undefined; draggingNodes = undefined;
selectIfNotDragged = undefined; selectIfNotDragged = undefined;
} else if (boxSelection) {
completeBoxSelection();
boxSelection = undefined;
} }
linkInProgressFromConnector = undefined; linkInProgressFromConnector = undefined;
@ -743,7 +793,7 @@
{@const labelWidthGridCells = Math.ceil(((layerNameLabelWidths?.[String(node.id)] || 0) - extraWidthToReachGridMultiple) / 24)} {@const labelWidthGridCells = Math.ceil(((layerNameLabelWidths?.[String(node.id)] || 0) - extraWidthToReachGridMultiple) / 24)}
<div <div
class="layer" class="layer"
class:selected={$nodeGraph.selected.includes(node.id)} class:selected={showSelected($nodeGraph.selected, boxSelection, node.id, nodeIndex)}
class:previewed={node.previewed} class:previewed={node.previewed}
class:disabled={node.disabled} class:disabled={node.disabled}
style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)} style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
@ -753,6 +803,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`} style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`}
style:--label-width={labelWidthGridCells} style:--label-width={labelWidthGridCells}
data-node={node.id} data-node={node.id}
bind:this={nodeElements[nodeIndex]}
> >
{#if node.errors} {#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span> <span class="node-error faded" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
@ -860,7 +911,7 @@
{@const clipPathId = String(Math.random()).substring(2)} {@const clipPathId = String(Math.random()).substring(2)}
<div <div
class="node" class="node"
class:selected={$nodeGraph.selected.includes(node.id)} class:selected={showSelected($nodeGraph.selected, boxSelection, node.id, nodeIndex)}
class:previewed={node.previewed} class:previewed={node.previewed}
class:disabled={node.disabled} class:disabled={node.disabled}
style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)} style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
@ -869,6 +920,7 @@
style:--data-color={`var(--color-data-${node.primaryOutput?.dataType || "general"})`} style:--data-color={`var(--color-data-${node.primaryOutput?.dataType || "general"})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`} style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`}
data-node={node.id} data-node={node.id}
bind:this={nodeElements[nodeIndex]}
> >
{#if node.errors} {#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span> <span class="node-error faded" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
@ -989,6 +1041,17 @@
</div> </div>
</div> </div>
<!-- Box select widget -->
{#if boxSelection}
<div
class="box-selection"
style:left={`${Math.min(boxSelection.startX, boxSelection.endX)}px`}
style:top={`${Math.min(boxSelection.startY, boxSelection.endY)}px`}
style:width={`${Math.abs(boxSelection.startX - boxSelection.endX)}px`}
style:height={`${Math.abs(boxSelection.startY - boxSelection.endY)}px`}
></div>
{/if}
<style lang="scss" global> <style lang="scss" global>
.graph { .graph {
position: relative; position: relative;
@ -1413,4 +1476,12 @@
} }
} }
} }
.box-selection {
position: absolute;
z-index: 2;
background-color: rgba(77, 168, 221, 0.2);
border: 1px solid rgba(77, 168, 221);
pointer-events: none;
}
</style> </style>