Skip to content

Improve space drag #277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 86 additions & 99 deletions src/js/space_drag.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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() {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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
}
}