diff --git a/src/js/space_drag.js b/src/js/space_drag.js index 8cc4af0..5b15178 100644 --- a/src/js/space_drag.js +++ b/src/js/space_drag.js @@ -6,15 +6,12 @@ Copyright (C) 2022 Petter Ericson, Yannis Rammos, Mehdi Merah, and the EPFL Dig MuseReduce is free software: you can redistribute it and/or modify it under the terms of the Affero General Public License as published by the Free Software Foundation. MuseReduce is distributed without explicit or implicit warranty. See the Affero General Public License at https://www.gnu.org/licenses/agpl-3.0.en.html for more details. */ -import $ from 'jquery' +import * as d3 from 'd3' import { getDrawContexts } from './app' -// Space drag implementation -let isDragging = false -let dragStartX = 0 -let dragStartY = 0 +// Space drag implementation using D3.js let currentContext = null -let initialTransform = null +let isDragging = false export function setupSpaceDrag() { const drawContexts = getDrawContexts() @@ -26,99 +23,63 @@ export function setupSpaceDrag() { layersContainer.style.cursor = 'grab' } + // Define the drag behavior + const drag = d3.drag() + .filter(() => window._spacePressed) // Only allow dragging when space is pressed + .on('start', startSpaceDrag) + .on('drag', moveSpaceDrag) + .on('end', endSpaceDrag) + drawContexts.forEach(context => { if (context && context.svg_elem) { - // Find the SVG container which is the parent of svg_elem - const svgContainer = context.svg_elem.closest('.svg_container') || context.svg_elem.parentNode + // Find the view element that contains this svg_elem + const viewElement = context.view_elem + if (!viewElement) return - // Change cursor on both elements - context.svg_elem.style.cursor = 'grab' - if (svgContainer) { - svgContainer.style.cursor = 'grab' - - // Store context on both elements for easy access during drag - svgContainer._dragContext = context - context.svg_elem._dragContext = context + // Set cursor and store context + viewElement.style.cursor = 'grab' + viewElement._dragContext = context - // Attach mousedown to both container and SVG element - svgContainer.addEventListener('mousedown', startSpaceDrag) - context.svg_elem.addEventListener('mousedown', startSpaceDrag) + // Apply D3 drag behavior to the view element instead of just the svg_elem + d3.select(viewElement).call(drag) - // Make sure svg element can receive events - context.svg_elem.style.pointerEvents = 'auto' - } + // Make sure both elements can receive events + viewElement.style.pointerEvents = 'auto' } }) - - // Global event listeners - document.addEventListener('mousemove', moveSpaceDrag) - document.addEventListener('mouseup', endSpaceDrag) } -export function startSpaceDrag(event) { +function startSpaceDrag(event) { if (!window._spacePressed) return - event.preventDefault() + event.sourceEvent.preventDefault() + event.sourceEvent.stopPropagation() isDragging = true - dragStartX = event.clientX - dragStartY = event.clientY - - // First try to get the context from the element's _dragContext property - const container = event.currentTarget - - if (container && container._dragContext) { - currentContext = container._dragContext - } else { - // Fall back to finding context by traversing up the DOM - const svgContainer = event.target.closest('.svg_container') - - if (svgContainer) { - // Try to find the context from any stored context on the container - if (svgContainer._dragContext) { - currentContext = svgContainer._dragContext - } else { - // Fall back to search through all contexts - currentContext = getDrawContexts().find(ctx => { - return ctx.svg_elem.closest('.svg_container') === svgContainer - }) - } - } - } - if (currentContext) { - initialTransform = getComputedTransformMatrix(currentContext.svg_elem) + // Get the context from the element's _dragContext property + const container = event.sourceEvent.currentTarget + currentContext = container._dragContext + if (currentContext) { // Change cursor to indicate dragging - document.body.style.cursor = 'grabbing' - if (currentContext.svg_elem) { - currentContext.svg_elem.style.cursor = 'grabbing' - } - - // Also change cursor on container - const container = currentContext.svg_elem.closest('.svg_container') - if (container) { - container.style.cursor = 'grabbing' - } + container.style.cursor = 'grabbing' } } -export function moveSpaceDrag(event) { - if (!isDragging || !currentContext || !initialTransform) return +function moveSpaceDrag(event) { + if (!isDragging || !currentContext) return - const dx = event.clientX - dragStartX - const dy = event.clientY - dragStartY + // Get the current transform + const transformStr = currentContext.svg_elem.style.transform || 'matrix(1, 0, 0, 1, 0, 0)' + const matrix = parseCSSTransform(transformStr) - // Make sure we have the correct cursor during drag - document.body.style.cursor = 'grabbing' - - // Apply the new transform - const transform = initialTransform - const newTransform = `matrix(${transform.a}, ${transform.b}, ${transform.c}, ${transform.d}, ${transform.e + dx}, ${transform.f + dy})` + // Update the transform with the drag movement + const newTransform = `matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d}, ${matrix.e + event.dx}, ${matrix.f + event.dy})` currentContext.svg_elem.style.transform = newTransform } -export function endSpaceDrag() { +function endSpaceDrag() { if (!isDragging) return isDragging = false @@ -127,18 +88,15 @@ export function endSpaceDrag() { document.body.style.cursor = '' if (currentContext) { - currentContext.svg_elem.style.cursor = 'grab' - // Reset cursor on container too - const container = currentContext.svg_elem.closest('.svg_container') - if (container) { - container.style.cursor = 'grab' + // Find and reset the view element cursor as well + const viewElement = currentContext.view_elem + if (viewElement) { + viewElement.style.cursor = 'grab' } currentContext = null } - - initialTransform = null } export function removeSpaceDrag() { @@ -157,8 +115,24 @@ export function removeSpaceDrag() { context.svg_elem.style.cursor = '' context.svg_elem.style.pointerEvents = '' - // Remove event listener from SVG element - context.svg_elem.removeEventListener('mousedown', startSpaceDrag) + // Find the view element + const viewElement = context.view_elem + if (viewElement) { + // Reset cursor and pointer-events + viewElement.style.cursor = '' + viewElement.style.pointerEvents = '' + + // Remove D3 drag behavior + d3.select(viewElement).on('.drag', null) + + // Clean up stored context + if (viewElement._dragContext) { + delete viewElement._dragContext + } + } + + // Remove D3 drag behavior from SVG as well + d3.select(context.svg_elem).on('.drag', null) // Clean up stored context on SVG element if (context.svg_elem._dragContext) { @@ -172,8 +146,8 @@ export function removeSpaceDrag() { // Reset cursor svgContainer.style.cursor = '' - // Remove event listener from container - svgContainer.removeEventListener('mousedown', startSpaceDrag) + // Remove D3 drag behavior + d3.select(svgContainer).on('.drag', null) // Clean up stored context on container if (svgContainer._dragContext) { @@ -183,26 +157,39 @@ export function removeSpaceDrag() { } }) - // Remove global event listeners - document.removeEventListener('mousemove', moveSpaceDrag) - document.removeEventListener('mouseup', endSpaceDrag) - + // Reset all state variables isDragging = false currentContext = null - initialTransform = null } -function getComputedTransformMatrix(element) { - try { - const style = window.getComputedStyle(element) +// Helper function to parse CSS transform matrix +function parseCSSTransform(transformStr) { + if (!transformStr || transformStr === 'none') { + return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 } // Identity matrix + } - // Handle case where transform is not set or "none" - if (!style.transform || style.transform === 'none') { - return new DOMMatrix() // Identity matrix + try { + // Handle matrix(...) format + if (transformStr.startsWith('matrix(')) { + const values = transformStr + .replace('matrix(', '') + .replace(')', '') + .split(',') + .map(v => parseFloat(v.trim())) + + return { + a: values[0], + b: values[1], + c: values[2], + d: values[3], + e: values[4], + f: values[5] + } } - return new DOMMatrix(style.transform) + // Return identity matrix if we can't parse it + return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 } } catch (e) { - return new DOMMatrix() // Return identity matrix on error + return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 } // Return identity matrix on error } }