From 34cb733ed73929d7f1ffb734344c32f11a9df6f8 Mon Sep 17 00:00:00 2001 From: Ayobami Date: Sun, 6 Jul 2025 22:06:31 +0100 Subject: [PATCH] Fix: issue 11: objects not snapping. and increase snap threshold --- src/components/DockBuilder/DockBuilder.jsx | 199 +++++++++++++-------- 1 file changed, 126 insertions(+), 73 deletions(-) diff --git a/src/components/DockBuilder/DockBuilder.jsx b/src/components/DockBuilder/DockBuilder.jsx index 3c27e3c..add3d10 100644 --- a/src/components/DockBuilder/DockBuilder.jsx +++ b/src/components/DockBuilder/DockBuilder.jsx @@ -412,95 +412,148 @@ export const DockBuilder = () => { }; function edgeDetectionAndSnap(options) { - if (editorMemo.getZoom() !== 1) { + if (this.isZoomed) { return; } // TODO: Edge detection and snap to object within snap range - const selectedObj = options.target; - if (!selectedObj.dockData) return; + if (options && options.target) { + const selectedObj = options.target.setCoords(); - // TODO: Detect if the selected object is in the allowed categories - const snapCategories = [ - DockPanelCategories.Accessories, - DockPanelCategories.BoatLift2, - DockPanelCategories.BoatLift4, - ]; + // TODO: Detect if the object is within the snap range of the selected object + // This is handled by the edgeDetection function which checks if objects are within snap threshold + editorMemo.forEachObject((obj) => { + if (obj === selectedObj) return; - if (!snapCategories.includes(selectedObj.dockData.itemName)) return; + const detectedObjBound = obj.getBoundingRect(); + const selectedObjBound = selectedObj.getBoundingRect(); - let closestSnapPoint = null; - let minDistance = edgeSnapThreshold; + // Only process image objects that are not snap clones + if (!obj.snapClone && obj.type === "image") { + // TODO: handle rotation of 0, 90, 180, 270 degrees - editorMemo.forEachObject((obj) => { - if (obj === selectedObj || !obj.dockData) return; + // Handle TOP edge snapping + if (edgeDetection(selectedObj, obj, "top")) { + let newTop = selectedObjBound.top; - // TODO: Only consider objects in the allowed categories - if (!snapCategories.includes(obj.dockData.itemName)) return; + // Handle different rotation combinations for top snapping + if ([0, nintyDeg].includes(obj.angle)) { + if ([oneEightyDeg, twoSeventyDeg].includes(selectedObj.angle)) { + newTop = obj.oCoords.tl.y; + } else { + newTop = obj.oCoords.tl.y - selectedObjBound.height; + } + } else { + if ([oneEightyDeg, twoSeventyDeg].includes(selectedObj.angle)) { + newTop = obj.oCoords.tl.y - detectedObjBound.height; + } else { + newTop = + obj.oCoords.tl.y - + (selectedObjBound.height + detectedObjBound.height); + } + } - // TODO: handle rotation of 0, 90, 180, 270 degrees - const rotation = obj.angle % 360; - const isRotated = rotation !== 0 && rotation !== 180; + // Apply the new position + selectedObj.setPositionByOrigin( + { x: selectedObj.oCoords.tl.x, y: newTop }, + "left", + "top" + ); + selectedObj.setCoords(); + editorMemo.renderAll(); + } + // Handle BOTTOM edge snapping + else if (edgeDetection(selectedObj, obj, "bottom")) { + let newTop = selectedObjBound.top; - const objCenter = obj.getCenterPoint(); - const selectedCenter = selectedObj.getCenterPoint(); + // Handle different rotation combinations for bottom snapping + if ([0, nintyDeg].includes(obj.angle)) { + if ([oneEightyDeg, twoSeventyDeg].includes(selectedObj.angle)) { + newTop = + obj.oCoords.tl.y + + (detectedObjBound.height + selectedObjBound.height); + } else { + newTop = obj.oCoords.tl.y + detectedObjBound.height; + } + } else { + if ([oneEightyDeg, twoSeventyDeg].includes(selectedObj.angle)) { + newTop = obj.oCoords.tl.y + selectedObjBound.height; + } else { + newTop = obj.oCoords.tl.y; + } + } - // Calculate potential snap points - const snapPoints = []; + // Apply the new position + selectedObj.setPositionByOrigin( + { x: selectedObj.oCoords.tl.x, y: newTop }, + "left", + "top" + ); + selectedObj.setCoords(); + editorMemo.renderAll(); + } + // Handle LEFT edge snapping + else if (edgeDetection(selectedObj, obj, "left")) { + let newLeft = selectedObjBound.left; - // Center point - snapPoints.push(objCenter); + // Handle different rotation combinations for left snapping + if ([0, twoSeventyDeg].includes(obj.angle)) { + if ([oneEightyDeg, nintyDeg].includes(selectedObj.angle)) { + newLeft = obj.oCoords.tl.x; + } else { + newLeft = obj.oCoords.tl.x - selectedObjBound.width; + } + } else { + if ([oneEightyDeg, nintyDeg].includes(selectedObj.angle)) { + newLeft = obj.oCoords.tl.x - detectedObjBound.width; + } else { + newLeft = + obj.oCoords.tl.x - + (selectedObjBound.width + detectedObjBound.width); + } + } - // Edge points - if (!isRotated || rotation === 0 || rotation === 180) { - // Horizontal edges - snapPoints.push( - new fabric.Point(objCenter.x, obj.getBoundingRect().top) - ); - snapPoints.push( - new fabric.Point( - objCenter.x, - obj.getBoundingRect().top + obj.getBoundingRect().height - ) - ); - } + // Apply the new position + selectedObj.setPositionByOrigin( + { x: newLeft, y: selectedObj.oCoords.tl.y }, + "left", + "top" + ); + selectedObj.setCoords(); + editorMemo.renderAll(); + } + // Handle RIGHT edge snapping + else if (edgeDetection(selectedObj, obj, "right")) { + let newLeft = selectedObjBound.left; - if (!isRotated || rotation === 90 || rotation === 270) { - // Vertical edges - snapPoints.push( - new fabric.Point(obj.getBoundingRect().left, objCenter.y) - ); - snapPoints.push( - new fabric.Point( - obj.getBoundingRect().left + obj.getBoundingRect().width, - objCenter.y - ) - ); - } + // Handle different rotation combinations for right snapping + if ([0, twoSeventyDeg].includes(obj.angle)) { + if ([oneEightyDeg, nintyDeg].includes(selectedObj.angle)) { + newLeft = + obj.oCoords.tl.x + + (detectedObjBound.width + selectedObjBound.width); + } else { + newLeft = obj.oCoords.tl.x + detectedObjBound.width; + } + } else { + if ([oneEightyDeg, nintyDeg].includes(selectedObj.angle)) { + newLeft = obj.oCoords.tl.x + selectedObjBound.width; + } else { + newLeft = obj.oCoords.tl.x; + } + } - // Find closest snap point - snapPoints.forEach((point) => { - const distance = Math.sqrt( - Math.pow(selectedCenter.x - point.x, 2) + - Math.pow(selectedCenter.y - point.y, 2) - ); - - if (distance < minDistance) { - minDistance = distance; - closestSnapPoint = point; + // Apply the new position + selectedObj.setPositionByOrigin( + { x: newLeft, y: selectedObj.oCoords.tl.y }, + "left", + "top" + ); + selectedObj.setCoords(); + editorMemo.renderAll(); + } } }); - }); - - // Snap to closest point if found - if (closestSnapPoint) { - const selectedCenter = selectedObj.getCenterPoint(); - selectedObj.set({ - left: selectedObj.left + (closestSnapPoint.x - selectedCenter.x), - top: selectedObj.top + (closestSnapPoint.y - selectedCenter.y), - }); - selectedObj.setCoords(); - editorMemo.renderAll(); } editorMemo.defaultCursor = "default"; @@ -832,13 +885,13 @@ export const DockBuilder = () => { fabric.devicePixelRatio = 1; fabric.Group.prototype.hasControls = true; fabric.Group.prototype.snapAngle = 45; - fabric.Group.prototype.snapThreshold = 5; + fabric.Group.prototype.snapThreshold = 10; fabric.Group.prototype.stroke = "#0f75bc"; fabric.Object.prototype.snapAngle = 45; // Optimize object rendering fabric.Object.prototype.objectCaching = true; - fabric.Object.prototype.snapThreshold = 5; + fabric.Object.prototype.snapThreshold = 10; fabric.Object.prototype.setControlsVisibility({ tl: false, //top-left mt: false, // middle-top