From a892e1fcb2e92af8da76dd8860c1b8fcec5f8e24 Mon Sep 17 00:00:00 2001 From: pk Date: Wed, 18 Dec 2024 22:27:27 +0100 Subject: [PATCH] wip --- connections.js | 16 ++++----- panning.js | 28 +++++++++++---- tp-flow-nodes.js | 9 +++-- zoom.js | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 zoom.js diff --git a/connections.js b/connections.js index 199ee02..f063400 100644 --- a/connections.js +++ b/connections.js @@ -101,13 +101,13 @@ export const connections = function(superClass) { const canvasRect = this.canvas.getBoundingClientRect(); const start = { - x: sourceRect.left + sourceRect.width/2 - canvasRect.left, - y: sourceRect.top + sourceRect.height/2 - canvasRect.top + x: (sourceRect.left + sourceRect.width/2 - canvasRect.left) / this.scale, + y: (sourceRect.top + sourceRect.height/2 - canvasRect.top) / this.scale }; const end = { - x: this.mousePosition.x - canvasRect.left, - y: this.mousePosition.y - canvasRect.top + x: (this.mousePosition.x - canvasRect.left) / this.scale, + y: (this.mousePosition.y - canvasRect.top) / this.scale }; const controlPoint1 = { @@ -150,13 +150,13 @@ export const connections = function(superClass) { const canvasRect = this.canvas.getBoundingClientRect(); const start = { - x: sourceRect.left + sourceRect.width/2 - canvasRect.left, - y: sourceRect.top + sourceRect.height/2 - canvasRect.top + x: (sourceRect.left + sourceRect.width/2 - canvasRect.left) / this.scale, + y: (sourceRect.top + sourceRect.height/2 - canvasRect.top) / this.scale }; const end = { - x: targetRect.left + targetRect.width/2 - canvasRect.left, - y: targetRect.top + targetRect.height/2 - canvasRect.top + x: (targetRect.left + targetRect.width/2 - canvasRect.left) / this.scale, + y: (targetRect.top + targetRect.height/2 - canvasRect.top) / this.scale }; const controlPoint1 = { diff --git a/panning.js b/panning.js index 19ab50f..7dffa99 100644 --- a/panning.js +++ b/panning.js @@ -73,8 +73,14 @@ export const panning = function(superClass) { this.xOffset = matrix.m41; this.yOffset = matrix.m42; - this.initialX = e.clientX - this.xOffset; - this.initialY = e.clientY - this.yOffset; + if (this.targetElement === this.canvas) { + this.initialX = e.clientX - this.xOffset; + this.initialY = e.clientY - this.yOffset; + } else { + // For nodes, compensate for scale in initial position too + this.initialX = e.clientX - (this.xOffset * this.scale); + this.initialY = e.clientY - (this.yOffset * this.scale); + } } } @@ -82,11 +88,19 @@ export const panning = function(superClass) { if (this.isDragging && this.targetElement) { e.preventDefault(); - this.currentX = e.clientX - this.initialX; - this.currentY = e.clientY - this.initialY; - - this.targetElement.style.transform = - `translate(${this.currentX}px, ${this.currentY}px)`; + if (this.targetElement === this.canvas) { + // For canvas panning, no scale compensation needed + this.currentX = e.clientX - this.initialX; + this.currentY = e.clientY - this.initialY; + this.targetElement.style.transform = + `translate(${this.currentX}px, ${this.currentY}px) scale(${this.scale})`; + } else { + // For nodes, compensate for canvas scale + this.currentX = (e.clientX - this.initialX) / this.scale; + this.currentY = (e.clientY - this.initialY) / this.scale; + this.targetElement.style.transform = + `translate(${this.currentX}px, ${this.currentY}px)`; + } } } diff --git a/tp-flow-nodes.js b/tp-flow-nodes.js index 6a524a4..426261b 100644 --- a/tp-flow-nodes.js +++ b/tp-flow-nodes.js @@ -7,8 +7,9 @@ This program is available under Apache License Version 2.0 import { LitElement, html, css } from 'lit'; import { connections, connectionStyles } from './connections.js'; import { panning } from './panning.js'; +import { zoom } from './zoom.js'; -class TpFlowNodes extends panning(connections(LitElement)) { +class TpFlowNodes extends zoom(panning(connections(LitElement))) { static get styles() { return [ connectionStyles, @@ -23,8 +24,10 @@ class TpFlowNodes extends panning(connections(LitElement)) { width: 100%; height: 100%; user-select: none; - position: relative; - overflow: hidden; + position: absolute; + min-width: 100%; + min-height: 100%; + overflow: visible; transform: translate(0px, 0px); } ` diff --git a/zoom.js b/zoom.js new file mode 100644 index 0000000..7bce75b --- /dev/null +++ b/zoom.js @@ -0,0 +1,91 @@ +export const zoom = function(superClass) { + return class extends superClass { + static get properties() { + return { + scale: { type: Number } + }; + } + + constructor() { + super(); + this.scale = 1; + this._handleWheel = this._handleWheel.bind(this); + this.MIN_SCALE = 0.1; + this.MAX_SCALE = 1.0; + this.SCALE_STEP = 0.1; + } + + firstUpdated() { + super.firstUpdated(); + this.canvas = this.shadowRoot.querySelector('.canvas'); + this.addEventListener('wheel', this._handleWheel, { passive: false }); + } + + _handleWheel(e) { + e.preventDefault(); + + if (e.deltaY > 0) { + this.zoomOut(); + } else if (e.deltaY < 0) { + this.zoomIn(); + } + } + + /** + * Zooms out the canvas by one step + * @returns {boolean} True if zoom was applied, false if already at minimum + */ + zoomOut() { + const newScale = Math.max(this.MIN_SCALE, this.scale - this.SCALE_STEP); + if (newScale !== this.scale) { + this._applyZoom(newScale); + return true; + } + return false; + } + + /** + * Zooms in the canvas by one step + * @returns {boolean} True if zoom was applied, false if already at maximum + */ + zoomIn() { + const newScale = Math.min(this.MAX_SCALE, this.scale + this.SCALE_STEP); + if (newScale !== this.scale) { + this._applyZoom(newScale); + return true; + } + return false; + } + + /** + * Immediately resets zoom to 100% + */ + resetZoom() { + this._applyZoom(this.MAX_SCALE); + } + + /** + * Applies the zoom scale to the canvas + * @private + */ + _applyZoom(newScale) { + this.scale = newScale; + + // Get current translation + const transform = window.getComputedStyle(this.canvas).transform; + const matrix = new DOMMatrix(transform); + const currentX = matrix.m41; + const currentY = matrix.m42; + + // Apply both transforms + this.canvas.style.transform = `translate(${currentX}px, ${currentY}px) scale(${this.scale})`; + this.canvas.style.transformOrigin = 'center center'; + this.requestUpdate(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + this.removeEventListener('wheel', this._handleWheel); + } + }; +} \ No newline at end of file