Files
picoGraph/scripts/ui/workspace/WorkspaceDropManager.js
Max Litruv Boonzaayer f917cf6ad5 Add initial Pico-8 cartridges for testing
- Created test_cart.p8 with basic drawing functionality and a print statement.
- Created test_unix.p8 with a simple initialization function.
2026-03-21 18:38:43 +11:00

333 lines
9.4 KiB
JavaScript

/**
* @typedef {import('../BlueprintWorkspace.js').BlueprintWorkspace} BlueprintWorkspace
* @typedef {import('../../nodes/nodeTypes.js').NodeDefinition} NodeDefinition
*/
import { WorkspaceGeometry } from "./WorkspaceGeometry.js";
import {
PALETTE_DRAG_MIME,
VARIABLE_DRAG_MIME,
} from "../BlueprintWorkspaceConstants.js";
/**
* Manages drag-and-drop interactions for palette and variable entries.
*/
export class WorkspaceDropManager {
#workspace;
/**
* @param {BlueprintWorkspace} workspace Owning workspace instance.
*/
constructor(workspace) {
this.#workspace = workspace;
}
/**
* Hooks workspace-level drag events.
*/
initialize() {
const element = this.#workspace.workspaceElement;
element.addEventListener("dragenter", (event) => {
this.#handleDragEnter(event);
});
element.addEventListener("dragover", (event) => {
this.#handleDragOver(event);
});
element.addEventListener("dragleave", (event) => {
this.#handleDragLeave(event);
});
element.addEventListener("drop", (event) => {
this.#handleDrop(event);
});
}
/**
* Begins a variable drag gesture from the overview panel.
*
* @param {DragEvent} event Drag event payload.
* @param {string} variableId Variable identifier being dragged.
*/
handleVariableDragStart(event, variableId) {
if (!event.dataTransfer) {
return;
}
event.dataTransfer.setData(VARIABLE_DRAG_MIME, variableId);
event.dataTransfer.setData("text/plain", variableId);
event.dataTransfer.effectAllowed = "copy";
this.#workspace.workspaceDragDepth = 0;
const element = this.#workspace.workspaceElement;
element.classList.add("is-variable-dragging");
element.classList.remove("is-palette-dragging");
}
/**
* Clears workspace drag styling for variable gestures.
*/
handleVariableDragEnd() {
this.#workspace.workspaceDragDepth = 0;
const element = this.#workspace.workspaceElement;
element.classList.remove("is-variable-dragging");
element.classList.remove("is-drag-target");
this.#workspace.dragManager.removePaletteGhost();
}
/**
* Responds to dragenter events and prepares drop feedback.
*
* @param {DragEvent} event Drag enter event.
*/
#handleDragEnter(event) {
const isPalette = this.#isPaletteDrag(event);
const isVariable = this.#isVariableDrag(event);
if (!isPalette && !isVariable) {
return;
}
event.preventDefault();
this.#workspace.workspaceDragDepth += 1;
const element = this.#workspace.workspaceElement;
element.classList.add("is-drag-target");
if (isPalette) {
const definitionId = this.#resolvePaletteDefinitionId(event);
this.#workspace.dragManager.ensurePaletteGhost(definitionId ?? null);
const point = WorkspaceGeometry.snapPositionToGrid(
this.#workspace,
WorkspaceGeometry.eventToWorkspacePoint(this.#workspace, event)
);
this.#workspace.dragManager.updatePaletteGhost(point);
} else {
this.#workspace.dragManager.removePaletteGhost();
}
}
/**
* Keeps drop feedback active while objects move across the workspace.
*
* @param {DragEvent} event Drag over event.
*/
#handleDragOver(event) {
const isPalette = this.#isPaletteDrag(event);
const isVariable = this.#isVariableDrag(event);
if (!isPalette && !isVariable) {
return;
}
event.preventDefault();
if (event.dataTransfer) {
event.dataTransfer.dropEffect = "copy";
}
if (isPalette) {
const definitionId = this.#resolvePaletteDefinitionId(event);
this.#workspace.dragManager.ensurePaletteGhost(definitionId ?? null);
const point = WorkspaceGeometry.snapPositionToGrid(
this.#workspace,
WorkspaceGeometry.eventToWorkspacePoint(this.#workspace, event)
);
this.#workspace.dragManager.updatePaletteGhost(point);
} else {
this.#workspace.dragManager.removePaletteGhost();
}
}
/**
* Clears drop feedback once the drag leaves workspace bounds.
*
* @param {DragEvent} event Drag leave event.
*/
#handleDragLeave(event) {
const isPalette = this.#isPaletteDrag(event);
const isVariable = this.#isVariableDrag(event);
if (!isPalette && !isVariable) {
return;
}
this.#workspace.workspaceDragDepth = Math.max(
0,
this.#workspace.workspaceDragDepth - 1
);
if (this.#workspace.workspaceDragDepth === 0) {
const element = this.#workspace.workspaceElement;
element.classList.remove("is-drag-target");
this.#workspace.dragManager.removePaletteGhost();
}
}
/**
* Handles finalized drops for palette nodes and variables.
*
* @param {DragEvent} event Drop event containing pointer details.
*/
#handleDrop(event) {
const isPalette = this.#isPaletteDrag(event);
const isVariable = this.#isVariableDrag(event);
if (!isPalette && !isVariable) {
return;
}
event.preventDefault();
const transfer = event.dataTransfer;
if (!transfer) {
return;
}
if (isVariable) {
this.handleVariableDragEnd();
this.#handleVariableDrop(event, transfer);
return;
}
const definitionId =
transfer.getData(PALETTE_DRAG_MIME) || transfer.getData("text/plain");
if (!definitionId) {
return;
}
this.#workspace.paletteManager.handleDragEnd();
// Snap to grid first, then offset for centering (consistent with ghost positioning)
const cursorWorld = WorkspaceGeometry.eventToWorkspacePoint(this.#workspace, event);
const snapped = WorkspaceGeometry.snapPositionToGrid(this.#workspace, cursorWorld);
// Offset spawn to match the centered ghost position (roughly half ghost size)
const spawn = {
x: snapped.x - 110,
y: snapped.y - 70,
};
this.#workspace.addNode(definitionId, spawn);
}
/**
* Prompts for node selection when a variable is dropped onto the workspace.
*
* @param {DragEvent} event Drop event payload.
* @param {DataTransfer} transfer Drag data transfer object.
*/
#handleVariableDrop(event, transfer) {
const variableId =
transfer.getData(VARIABLE_DRAG_MIME) || transfer.getData("text/plain");
const variable = variableId
? this.#workspace.variables.get(variableId)
: undefined;
if (!variableId || !variable) {
return;
}
const definitions = this.#getVariableSpawnDefinitions(variableId);
if (!definitions.length) {
const cursorWorld = WorkspaceGeometry.eventToWorkspacePoint(this.#workspace, event);
const snapped = WorkspaceGeometry.snapPositionToGrid(this.#workspace, cursorWorld);
const spawn = {
x: snapped.x - 110,
y: snapped.y - 70,
};
const fallback = this.#workspace.addNode("get_var", spawn);
if (fallback) {
const nodeName = variable.name || variable.id;
this.#workspace.graph.setNodeProperty(fallback.id, "name", nodeName);
this.#workspace.graph.setNodeProperty(
fallback.id,
"variableId",
variable.id
);
}
return;
}
this.#workspace.pendingVariableSpawn = { variableId };
const placeholderName = variable.name || "variable";
this.#workspace.contextMenuManager.show(event.clientX, event.clientY, {
definitions,
mode: "variable",
placeholder: `Create node for ${placeholderName}`,
});
}
/**
* Resolves potential variable spawn definitions from the registry.
*
* @param {string} variableId Variable identifier.
* @returns {Array<NodeDefinition>}
*/
#getVariableSpawnDefinitions(_variableId) {
const definitions = [];
const getDefinition = this.#workspace.registry.get("get_var");
const setDefinition = this.#workspace.registry.get("set_var");
if (getDefinition) {
definitions.push(getDefinition);
}
if (setDefinition) {
definitions.push(setDefinition);
}
return definitions;
}
/**
* Determines whether the drag originates from the palette interface.
*
* @param {DragEvent} event Drag event to inspect.
* @returns {boolean}
*/
#isPaletteDrag(event) {
const transfer = event.dataTransfer;
if (!transfer) {
return false;
}
const types = Array.from(transfer.types ?? []);
if (types.includes(PALETTE_DRAG_MIME)) {
return true;
}
return this.#workspace.workspaceElement.classList.contains(
"is-palette-dragging"
);
}
/**
* Determines whether the drag originates from the variable overview.
*
* @param {DragEvent} event Drag event to inspect.
* @returns {boolean}
*/
#isVariableDrag(event) {
const transfer = event.dataTransfer;
if (!transfer) {
return false;
}
const types = Array.from(transfer.types ?? []);
if (types.includes(VARIABLE_DRAG_MIME)) {
return true;
}
return this.#workspace.workspaceElement.classList.contains(
"is-variable-dragging"
);
}
/**
* Resolves the palette definition identifier associated with the active drag.
*
* @param {DragEvent} event Drag event payload.
* @returns {string | null}
*/
#resolvePaletteDefinitionId(event) {
if (this.#workspace.activePaletteDefinitionId) {
return this.#workspace.activePaletteDefinitionId;
}
const transfer = event.dataTransfer;
if (!transfer) {
return null;
}
const identifier =
transfer.getData(PALETTE_DRAG_MIME) || transfer.getData("text/plain");
return identifier || null;
}
}