From 810d23023cb6c2c833f845856e237ecb864ce568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kay=20=C5=9Eat=C4=B1r?= Date: Wed, 9 Oct 2024 11:49:30 +0300 Subject: [PATCH] Refactor snap-to-grid feature. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gökay Şatır Change-Id: Ie175d7e4a36ac3e715dff220ffaa733ed499bc72 --- .../canvas/sections/ShapeHandlesSection.ts | 208 ++++++------------ 1 file changed, 73 insertions(+), 135 deletions(-) diff --git a/browser/src/canvas/sections/ShapeHandlesSection.ts b/browser/src/canvas/sections/ShapeHandlesSection.ts index a4c0414d03d1..a364119cfa1c 100644 --- a/browser/src/canvas/sections/ShapeHandlesSection.ts +++ b/browser/src/canvas/sections/ShapeHandlesSection.ts @@ -45,8 +45,6 @@ class ShapeHandlesSection extends CanvasSectionObject { this.sectionProperties.lastDragDistance = [0, 0]; this.sectionProperties.pickedIndexX = 0; // Which corner of shape is closest to snap point when moving the shape. this.sectionProperties.pickedIndexY = 0; // Which corner of shape is closest to snap point when moving the shape. - this.sectionProperties.closestGridPoint = null; // This will hold the position to highlight and snap. - this.sectionProperties.radiusForGridSnap = 20 * app.dpiScale; // How close should the point be to a grid point to snap. // These are for snapping the objects to the same level with others' boundaries. this.sectionProperties.closestX = null; @@ -753,140 +751,82 @@ class ShapeHandlesSection extends CanvasSectionObject { else this.sectionProperties.closestY = null; } - private findClosestGridPoint(size: number[], position: number[], dragDistance: number[]): boolean { - this.sectionProperties.closestGridPoint = null; + private cloneSelectedPartInfoForGridSnap() { + const selectedPart = Object.assign({}, app.impress.partList[app.map._docLayer._selectedPart]); + selectedPart.leftBorder *= app.impress.twipsCorrection; + selectedPart.upperBorder *= app.impress.twipsCorrection; + selectedPart.rightBorder *= app.impress.twipsCorrection; + selectedPart.lowerBorder *= app.impress.twipsCorrection; + selectedPart.gridCoarseWidth *= app.impress.twipsCorrection; + selectedPart.gridCoarseHeight *= app.impress.twipsCorrection; - if (app.map.stateChangeHandler.getItemValue('.uno:GridUse') === 'true') { - /* - We will find and highlight a close-enough grid point, if any. - */ - - const selectedPart = Object.assign({}, app.impress.partList[app.map._docLayer._selectedPart]); - selectedPart.leftBorder *= app.impress.twipsCorrection; - selectedPart.upperBorder *= app.impress.twipsCorrection; - selectedPart.rightBorder *= app.impress.twipsCorrection; - selectedPart.lowerBorder *= app.impress.twipsCorrection; - selectedPart.gridCoarseWidth *= app.impress.twipsCorrection; - selectedPart.gridCoarseHeight *= app.impress.twipsCorrection; - - // The 4 corners and the midpoints of selected object's rectangle. - const checkList = [ - // 4 corners. - new cool.SimplePoint((position[0] + dragDistance[0]) * app.pixelsToTwips, (position[1] + dragDistance[1]) * app.pixelsToTwips), - new cool.SimplePoint((size[0] + position[0] + dragDistance[0]) * app.pixelsToTwips, (position[1] + dragDistance[1]) * app.pixelsToTwips), - new cool.SimplePoint((position[0] + dragDistance[0]) * app.pixelsToTwips, (size[1] + position[1] + dragDistance[1]) * app.pixelsToTwips), - new cool.SimplePoint((size[0] + position[0] + dragDistance[0]) * app.pixelsToTwips, (size[1] + position[1] + dragDistance[1]) * app.pixelsToTwips), - - // Mid points. - new cool.SimplePoint((position[0] + size[0] * 0.5 + dragDistance[0]) * app.pixelsToTwips, (position[1] + dragDistance[1]) * app.pixelsToTwips), - new cool.SimplePoint((position[0] + dragDistance[0]) * app.pixelsToTwips, (position[1] + size[1] * 0.5 + dragDistance[1]) * app.pixelsToTwips), - new cool.SimplePoint((position[0] + size[0] + dragDistance[0]) * app.pixelsToTwips, (position[1] + size[1] * 0.5 + dragDistance[1]) * app.pixelsToTwips), - new cool.SimplePoint((position[0] + size[0] * 0.5 + dragDistance[0]) * app.pixelsToTwips, (position[1] + size[1] + dragDistance[1]) * app.pixelsToTwips), - ]; - - // We need to subtract the left and top borders. - for (let i = 0; i < checkList.length; i++) { - checkList[i].x -= selectedPart.leftBorder; - checkList[i].y -= selectedPart.upperBorder; - } + return selectedPart; + } - // The rectangle that is shaped by the page margins. - const innerRectangle = new cool.SimpleRectangle( - selectedPart.leftBorder, - selectedPart.upperBorder, - (selectedPart.width - selectedPart.leftBorder - selectedPart.rightBorder), - (selectedPart.height - selectedPart.upperBorder - selectedPart.lowerBorder) - ); + private getInnerRecrangleForGridSnap(selectedPart: any) { + return new cool.SimpleRectangle( + selectedPart.leftBorder, + selectedPart.upperBorder, + (selectedPart.width - selectedPart.leftBorder - selectedPart.rightBorder), + (selectedPart.height - selectedPart.upperBorder - selectedPart.lowerBorder) + ); + } - let minDistance = 1000000; - let pickedPoint = null; - const gapX = selectedPart.gridCoarseWidth / (selectedPart.innerSpacesX > 0 ? selectedPart.innerSpacesX : 1); - const gapY = selectedPart.gridCoarseHeight / (selectedPart.innerSpacesY > 0 ? selectedPart.innerSpacesY : 1); - for (let i = 0; i < checkList.length; i++) { - if (innerRectangle.containsPoint(checkList[i].toArray())) { - // Now, think the areas enclosed with thick grid points as rectangles. - // We need to find which enclosed rectangle contains our point. - - // Position the grid rectangle. - const leftCount = Math.round(checkList[i].x / selectedPart.gridCoarseWidth); - const topCount = Math.round(checkList[i].y / selectedPart.gridCoarseHeight); - - // Distances to the edges of grid rectangle. - const distances = [ - checkList[i].x - leftCount * selectedPart.gridCoarseWidth, // To left. - checkList[i].y - topCount * selectedPart.gridCoarseHeight, // To top. - (leftCount + 1) * selectedPart.gridCoarseWidth - checkList[i].x, // To right. - (topCount + 1) * selectedPart.gridCoarseHeight - checkList[i].y // To bottom. - ]; - - const closestDistance = Math.min(...distances); - const index = distances.indexOf(closestDistance); - - if (index === 0 || index === 2) { // closestDistance is horizontal. - let closestSmallDotVerticalDistance; - const leftOver = checkList[i].y % gapY; - if (leftOver > gapY * 0.5) - closestSmallDotVerticalDistance = gapY - leftOver; - else - closestSmallDotVerticalDistance = leftOver; - - const lineDistance = Math.pow(Math.pow(closestDistance, 2) + Math.pow(closestSmallDotVerticalDistance, 2), 0.5); - - if (lineDistance < minDistance) { - minDistance = lineDistance; - pickedPoint = []; - pickedPoint[0] = (index === 0 ? leftCount : leftCount + 1); - pickedPoint[1] = topCount; - - // pickedPoint currently holds indexes, turn them to coordinates, don't forget to add small dots detail to vertical. - pickedPoint[0] *= selectedPart.gridCoarseWidth; - pickedPoint[1] *= selectedPart.gridCoarseHeight; - pickedPoint[1] += Math.round((checkList[i].y % selectedPart.gridCoarseHeight) / gapY) + (leftOver > gapY * 0.5 ? gapY : 0); - } - } - else { // closestDistance is vertical. - let closestSmallDotHorizontalDistance; - const leftOver = checkList[i].x % gapX; - if (leftOver > gapX * 0.5) - closestSmallDotHorizontalDistance = gapX - leftOver; - else - closestSmallDotHorizontalDistance = leftOver; - - const lineDistance = Math.pow(Math.pow(closestDistance, 2) + Math.pow(closestSmallDotHorizontalDistance, 2), 0.5); - - if (lineDistance < minDistance) { - minDistance = lineDistance; - pickedPoint = []; - pickedPoint[0] = leftCount; - pickedPoint[1] = (index === 1 ? topCount : topCount + 1); - - // pickedPoint currently holds indexes, turn them to coordinates, don't forget to add small dots detail to horizontal. - pickedPoint[0] *= selectedPart.gridCoarseWidth; - pickedPoint[0] += Math.round((checkList[i].x % selectedPart.gridCoarseWidth) / gapX) + (leftOver > gapX * 0.5 ? gapX : 0); - pickedPoint[1] *= selectedPart.gridCoarseHeight; - } - } - } - } + private getCornerPointsForGridSnap(size: number[], position: number[], dragDistance: number[]) { + return [ + new cool.SimplePoint((position[0] + dragDistance[0]) * app.pixelsToTwips, (position[1] + dragDistance[1]) * app.pixelsToTwips), + new cool.SimplePoint((size[0] + position[0] + dragDistance[0]) * app.pixelsToTwips, (position[1] + dragDistance[1]) * app.pixelsToTwips), + new cool.SimplePoint((position[0] + dragDistance[0]) * app.pixelsToTwips, (size[1] + position[1] + dragDistance[1]) * app.pixelsToTwips), + new cool.SimplePoint((size[0] + position[0] + dragDistance[0]) * app.pixelsToTwips, (size[1] + position[1] + dragDistance[1]) * app.pixelsToTwips), + ]; + } + + private findClosestGridPoint(size: number[], position: number[], dragDistance: number[]) { + // First rule of snap-to-grid: If you enable snap-to-grid, you have to snap. + + const selectedPart = this.cloneSelectedPartInfoForGridSnap(); + + // The 4 corners of selected object's rectangle. + const checkList = this.getCornerPointsForGridSnap(size, position, dragDistance); + + // The rectangle that is shaped by the page margins. + const innerRectangle = this.getInnerRecrangleForGridSnap(selectedPart); + + const gapX = selectedPart.gridCoarseWidth / (selectedPart.innerSpacesX > 0 ? selectedPart.innerSpacesX : 1); + const gapY = selectedPart.gridCoarseHeight / (selectedPart.innerSpacesY > 0 ? selectedPart.innerSpacesY : 1); - if (minDistance * app.twipsToPixels < this.sectionProperties.radiusForGridSnap) { - this.sectionProperties.closestGridPoint = pickedPoint; - this.sectionProperties.closestGridPoint[0] += selectedPart.leftBorder; - this.sectionProperties.closestGridPoint[1] += selectedPart.upperBorder; - this.sectionProperties.closestGridPoint[0] *= app.twipsToPixels; - this.sectionProperties.closestGridPoint[1] *= app.twipsToPixels; - this.sectionProperties.closestGridPoint[0] += this.containerObject.getDocumentAnchor()[0] - this.documentTopLeft[0]; - this.sectionProperties.closestGridPoint[1] += this.containerObject.getDocumentAnchor()[1] - this.documentTopLeft[1]; + let minX = 100000; + let minY = 100000; + for (let i = 0; i < 1; i++) { + if (innerRectangle.containsPoint(checkList[i].toArray())) { + + const countX = Math.round((checkList[i].x - innerRectangle.x1) / gapX); + const countY = Math.round((checkList[i].y - innerRectangle.y1) / gapY); + + const diffX = Math.abs(checkList[i].x - innerRectangle.x1 - gapX * countX); + const diffY = Math.abs(checkList[i].y - innerRectangle.y1 - gapY * countY); + + if (diffX < minX) { + minX = diffX; + this.sectionProperties.closestX = innerRectangle.x1 + (countX * gapX); + this.sectionProperties.pickedIndexX = [1, 3].includes(i) ? 0 : 1; // Do we substract width or not. + } + if (diffY < minY) { + minY = diffY; + this.sectionProperties.closestY = innerRectangle.y1 + (countY * gapY); + this.sectionProperties.pickedIndexY = [2, 3].includes(i) ? 0 : 1; // Do we substract height or not. + } } } - else return false; + + this.sectionProperties.closestX *= app.twipsToPixels; + this.sectionProperties.closestY *= app.twipsToPixels; } public checkObjectsBoundaries(xListToCheck: number[], yListToCheck: number[]) { if (app.map._docLayer._docType === 'presentation') { this.findClosestX(xListToCheck); this.findClosestY(yListToCheck); - this.containerObject.requestReDraw(); } } @@ -896,8 +836,12 @@ class ShapeHandlesSection extends CanvasSectionObject { If there is a grid point to snap to, then we'll ignore helper lines. Because core side doesn't know about our helper lines, and it'll ignore them if it can snap to a grid point. */ - if (this.findClosestGridPoint(size, position, dragDistance)) { - this.sectionProperties.closestX = this.sectionProperties.closestY = null; + + this.sectionProperties.closestX = null; + this.sectionProperties.closestY = null; + + if (app.map.stateChangeHandler.getItemValue('.uno:GridUse') === 'true') { + this.findClosestGridPoint(size, position, dragDistance) } else { this.checkObjectsBoundaries( @@ -905,6 +849,8 @@ class ShapeHandlesSection extends CanvasSectionObject { [position[1] + dragDistance[1], position[1] + dragDistance[1] + size[1]] ); } + + this.containerObject.requestReDraw(); } onMouseMove(position: number[], dragDistance: number[]) { @@ -1016,20 +962,12 @@ class ShapeHandlesSection extends CanvasSectionObject { this.context.closePath(); } - if (this.sectionProperties.closestGridPoint) { - const p = this.sectionProperties.closestGridPoint; - this.context.fillStyle = 'orange'; - this.context.arc(p[0], p[1], 10 * app.dpiScale, 0, 2 * Math.PI, false); - this.context.fill(); - } - this.context.restore(); } private anythingToDraw(): boolean { return this.sectionProperties.closestX !== null || - this.sectionProperties.closestY !== null || - this.sectionProperties.closestGridPoint !== null; + this.sectionProperties.closestY !== null; } public onDraw() {