Skip to content

Commit

Permalink
can now drag multiple nodes at once. Right clicking doesn't effect no…
Browse files Browse the repository at this point in the history
…de selection
  • Loading branch information
EliCDavis committed Sep 21, 2024
1 parent 57837aa commit b2492c4
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 67 deletions.
21 changes: 9 additions & 12 deletions src/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { FlowNote, FlowNoteConfig } from "./notes/note";
import { NoteSubsystem } from "./notes/subsystem";
import { ConnectionRendererConfiguration, NodeSubsystem } from "./nodes/subsystem";
import { Connection } from './connection';
import { Organize } from './organize';
import { Publisher } from './nodes/publisher';

export type GraphRenderer = (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, position: Vector2, scale: number) => void;
Expand Down Expand Up @@ -43,7 +42,7 @@ function BuildBackgroundRenderer(backgroundColor: string): GraphRenderer {
}
}

export const contextMenuGroup = "graph-context-menu";
const contextMenuGroup = "graph-context-menu";

export interface FlowNodeGraphConfiguration {
backgroundRenderer?: GraphRenderer;
Expand Down Expand Up @@ -81,11 +80,11 @@ class GraphView {
}
}

openContextMenu(position: Vector2): ContextMenuConfig {
openContextMenu(ctx: CanvasRenderingContext2D, position: Vector2): ContextMenuConfig {
let finalConfig: ContextMenuConfig = {};

for (let i = 0; i < this.#subsystems.length; i++) {
const subSystemMenu = this.#subsystems[i].openContextMenu(position);
const subSystemMenu = this.#subsystems[i].openContextMenu(ctx, position);
if (subSystemMenu !== null) {
finalConfig = CombineContextMenus(finalConfig, subSystemMenu);
}
Expand Down Expand Up @@ -177,11 +176,6 @@ export class NodeFlowGraph {
group: contextMenuGroup,
callback: this.#resetView.bind(this)
},
{
name: "Organize Nodes",
group: contextMenuGroup,
callback: this.organize.bind(this)
}
],
}, config?.contextMenu);

Expand Down Expand Up @@ -244,11 +238,14 @@ export class NodeFlowGraph {
#clickStart(mousePosition: Vector2, ctrlKey: boolean): void {
if (this.#contextMenuEntryHovering !== null) {
this.#contextMenuEntryHovering.click();
this.#openedContextMenu = null;
this.#contextMenuEntryHovering = null;
return;
}
this.#openedContextMenu = null;
this.#contextMenuEntryHovering = null;
this.#mousePosition = mousePosition;

this.#mousePosition = mousePosition;
this.currentView().clickStart(mousePosition, ctrlKey);
}

Expand All @@ -257,7 +254,7 @@ export class NodeFlowGraph {
}

organize(): void {
Organize(this.#ctx, this);
this.#mainNodeSubsystem.organize(this.#ctx);
}

addPublisher(identifier: string, publisher: Publisher): void {
Expand Down Expand Up @@ -300,7 +297,7 @@ export class NodeFlowGraph {

const contextMenuPosition = this.#sceenPositionToMousePosition(position);

finalConfig = CombineContextMenus(finalConfig, this.currentView().openContextMenu(contextMenuPosition));
finalConfig = CombineContextMenus(finalConfig, this.currentView().openContextMenu(this.#ctx, contextMenuPosition));

this.#openedContextMenu = {
Menu: new ContextMenu(finalConfig),
Expand Down
2 changes: 1 addition & 1 deletion src/graphSubsystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface GraphSubsystem {

render(ctx: CanvasRenderingContext2D, scale: number, position: Vector2, mousePosition: Vector2 | undefined): RenderResults | undefined;

openContextMenu(position: Vector2): ContextMenuConfig | null;
openContextMenu(ctx: CanvasRenderingContext2D, position: Vector2): ContextMenuConfig | null;

/**
* Event for when the graph is clicked.
Expand Down
10 changes: 9 additions & 1 deletion src/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export class MouseObserver {
}

#down(event: MouseEvent): void {
// Only register left clicks
if (event.button !== 0) {
return;
}
this.#clicked = true;
this.#clickStart(this.#mousePosition(event), event.ctrlKey || event.shiftKey);
}
Expand All @@ -103,7 +107,11 @@ export class MouseObserver {
this.#clickStart(this.#lastTouch, false);
}

#up(): void {
#up(event: MouseEvent): void {
// Only register left clicks
if (event.button !== 0) {
return;
}
this.#clicked = false
this.#clickStop();
}
Expand Down
5 changes: 2 additions & 3 deletions src/nodes/factory.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { FlowNode } from "../node";
import { Publisher, PublisherConfig } from "./publisher";
import { ContextMenuConfig } from "../contextMenu";
import { contextMenuGroup, NodeFlowGraph } from "../graph";
import { Vector2 } from "../types/vector2";
import { NodeSubsystem } from "./subsystem";
import { nodeFlowGroup as nodeFlowContextMenuGroup, NodeSubsystem } from "./subsystem";

type NodeCreatedCallback = (publisher: string, nodeType: string, node: FlowNode) => void;

Expand Down Expand Up @@ -77,7 +76,7 @@ export class NodeFactory {
}
return {
name: "New Node",
group: contextMenuGroup,
group: nodeFlowContextMenuGroup,
subMenus: menus,
};
}
Expand Down
149 changes: 106 additions & 43 deletions src/nodes/subsystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import { Connection, ConnectionRenderer, DefaultConnectionRenderer } from "../co
import { ContextMenuConfig } from "../contextMenu";
import { RenderResults } from "../graphSubsystem";
import { FlowNode, NodeState } from "../node";
import { Organize } from "../organize";
import { TimeExecution } from "../performance";
import { Port } from "../port";
import { CursorStyle } from "../styles/cursor";
import { List } from "../types/list";
import { Vector2 } from "../types/vector2";
import { Widget } from "../widgets/widget";
import { NodeFactory, NodeFactoryConfig } from "./factory";
import { Publisher } from "./publisher";

export const nodeFlowGroup = "node-flow-graph-node-menu";

export interface ConnectionRendererConfiguration {
size?: number;
color?: string;
Expand Down Expand Up @@ -53,7 +57,7 @@ export class NodeSubsystem {

#nodeHovering: number;

#nodeGrabbed: number;
#nodesGrabbed: List<number>;

#connectionSelected: Connection | null;

Expand All @@ -72,7 +76,7 @@ export class NodeSubsystem {
constructor(config?: NodeSubsystemConfig) {
this.#nodes = [];
this.#nodeHovering = -1;
this.#nodeGrabbed = -1;
this.#nodesGrabbed = new List<number>();
this.#connectionSelected = null;
this.#portHovering = null;
this.#widgetHovering = null;
Expand All @@ -86,11 +90,18 @@ export class NodeSubsystem {
this.#nodeFactory.addPublisher(identifier, publisher);
}


clickStart(mousePosition: Vector2, ctrlKey: boolean): boolean {
let hoveringSomething = false;
if (this.#nodeHovering > -1) {
this.#nodeGrabbed = this.#nodeHovering;
this.#selectNode(this.#nodeHovering, !ctrlKey);
this.#selectNodeByIndex(this.#nodeHovering, !ctrlKey);

for (let i = 0; i < this.#nodes.length; i++) {
if (this.#nodes[i].selected()) {
this.#nodesGrabbed.Push(i);
}
}

hoveringSomething = true;
}

Expand Down Expand Up @@ -134,20 +145,25 @@ export class NodeSubsystem {
}

mouseDragEvent(delta: Vector2, scale: number): boolean {
if (this.#interactingWithNode() && !this.#nodes[this.#nodeGrabbed].locked()) {
this.#nodes[this.#nodeGrabbed].translate({
x: delta.x * (1 / scale),
y: delta.y * (1 / scale)
});
return true
} else if (this.#interactingWithConnection()) {
// intentionally left blank
return true
} else if (this.#interactingWithWidget()) {
// intentionally left blank
return true
}
return false
if (this.#interactingWithNode()) {
let nodeMoved = false;
for (let i = 0; i < this.#nodesGrabbed.Count(); i++) {
const node = this.#nodes[this.#nodesGrabbed.At(i)];
if (node.locked()) {
continue;
}
node.translate({
x: delta.x * (1 / scale),
y: delta.y * (1 / scale)
});
nodeMoved = true;
}

if (nodeMoved) {
return true;
}
}
return this.#interactingWithConnection() || this.#interactingWithWidget()
}

clearNodeInputConnection(node: FlowNode, index: number): void {
Expand All @@ -160,7 +176,7 @@ export class NodeSubsystem {
}

clickEnd(): void {
this.#nodeGrabbed = -1;
this.#nodesGrabbed.Clear();

if (this.#widgetCurrentlyClicking !== null) {
this.#widgetCurrentlyClicking.ClickEnd();
Expand Down Expand Up @@ -305,7 +321,7 @@ export class NodeSubsystem {
}

#interactingWithNode(): boolean {
return this.#nodeGrabbed > -1;
return this.#nodesGrabbed.Count() > 0;
}

#interactingWithConnection(): boolean {
Expand Down Expand Up @@ -343,45 +359,66 @@ export class NodeSubsystem {
}
}

openContextMenu(position: Vector2): ContextMenuConfig | null {
organize(ctx: CanvasRenderingContext2D): void {
Organize(ctx, this);
}

openContextMenu(ctx: CanvasRenderingContext2D, position: Vector2): ContextMenuConfig | null {

let config: ContextMenuConfig = {
items: [],
items: [
{
name: "Organize All Nodes",
group: nodeFlowGroup,
callback: () => {
this.organize(ctx);
}
}
],
subMenus: [
this.#nodeFactory.openMenu(this, position)
]
}

if (this.#nodeHovering > -1) {
const nodeToReview = this.#nodeHovering;
const group = "node-flow-graph-node-menu";

config.items?.push({
name: "Delete Node",
group: group,
callback: () => {
this.#removeNodeByIndex(nodeToReview);
}
}, {
name: "Clear Node Connections",
group: group,
callback: () => {
this.#removeNodeConnections(nodeToReview);
config.items?.push(
{
name: "Delete Node",
group: nodeFlowGroup,
callback: () => {
this.#removeNodeByIndex(nodeToReview);
}
},
{
name: "Select Connected Nodes",
group: nodeFlowGroup,
callback: () => {
this.#selectConnectedNodes(nodeToReview);
}
},
{
name: "Clear Node Connections",
group: nodeFlowGroup,
callback: () => {
this.#removeNodeConnections(nodeToReview);
}
}
});
);

if (this.#nodes[nodeToReview].locked()) {
config.items?.push({
name: "Unlock Node Position",
group: group,
group: nodeFlowGroup,
callback: () => {
this.#unlockNode(nodeToReview);
}
});
} else {
config.items?.push({
name: "Lock Node Position",
group: group,
group: nodeFlowGroup,
callback: () => {
this.#lockNode(nodeToReview);
}
Expand All @@ -392,13 +429,39 @@ export class NodeSubsystem {
return config;
}

#selectNode(nodeIndex: number, unselectOthers: boolean): void {
#selectConnectedNodes(nodeIndex: number): void {
const node = this.#nodes[nodeIndex];
if (node === undefined) {
return;
}

const outputs = this.connectedOutputsNodeReferences(nodeIndex);
for (let i = 0; i < outputs.length; i++) {
this.#selectNode(outputs[i], false);
}

const inputs = this.connectedInputsNodeReferences(nodeIndex);
for (let i = 0; i < inputs.length; i++) {
this.#selectNode(inputs[i], false);
}
}

#selectNodeByIndex(nodeIndex: number, unselectOthers: boolean): void {
this.#selectNode(this.#nodes[nodeIndex], unselectOthers);
}

#selectNode(node: FlowNode, unselectOthers: boolean): void {
node.select();

if (!unselectOthers) {
return;
}

for (let i = 0; i < this.#nodes.length; i++) {
if (nodeIndex === i) {
this.#nodes[i].select();
} else if (unselectOthers) {
this.#nodes[i].unselect();
if (node === this.#nodes[i]) {
continue
}
this.#nodes[i].unselect();
}
}

Expand Down Expand Up @@ -456,7 +519,7 @@ export class NodeSubsystem {
// state = NodeState.Selected;
// }

if (i === this.#nodeGrabbed) {
if (this.#nodes[i].selected() && this.#nodesGrabbed.Count() > 0) {
state = NodeState.Grabbed;
this.#cursor = CursorStyle.Grabbing;
}
Expand Down
2 changes: 1 addition & 1 deletion src/notes/subsystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class NoteSubsystem {
this.#notes.push(note);
}

openContextMenu(position: Vector2): ContextMenuConfig | null {
openContextMenu(ctx: CanvasRenderingContext2D, position: Vector2): ContextMenuConfig | null {
const group = "node-flow-graph-note-menu";

const result: ContextMenuConfig = {
Expand Down
Loading

0 comments on commit b2492c4

Please sign in to comment.