Skip to content

Commit

Permalink
Refactor snap-to-grid feature.
Browse files Browse the repository at this point in the history
Signed-off-by: Gökay Şatır <[email protected]>
Change-Id: Ie175d7e4a36ac3e715dff220ffaa733ed499bc72
  • Loading branch information
gokaysatir committed Oct 9, 2024
1 parent 9aaa9ac commit 810d230
Showing 1 changed file with 73 additions and 135 deletions.
208 changes: 73 additions & 135 deletions browser/src/canvas/sections/ShapeHandlesSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}

Expand All @@ -896,15 +836,21 @@ 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(
[position[0] + dragDistance[0], position[0] + dragDistance[0] + size[0]],
[position[1] + dragDistance[1], position[1] + dragDistance[1] + size[1]]
);
}

this.containerObject.requestReDraw();
}

onMouseMove(position: number[], dragDistance: number[]) {
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit 810d230

Please sign in to comment.