fix snap calculations. fix #888

This commit is contained in:
Anton Lavrenov
2020-04-13 10:12:39 -05:00
parent 8937741c31
commit 9a23b0aa7d
4 changed files with 237 additions and 110 deletions

View File

@@ -8,7 +8,7 @@
* Konva JavaScript Framework v4.2.2 * Konva JavaScript Framework v4.2.2
* http://konvajs.org/ * http://konvajs.org/
* Licensed under the MIT * Licensed under the MIT
* Date: Fri Apr 10 2020 * Date: Mon Apr 13 2020
* *
* Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS)
* Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) * Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva)
@@ -14866,6 +14866,18 @@
var updated = transformShape(shape, oldSelection, newSelection); var updated = transformShape(shape, oldSelection, newSelection);
return rotateAroundPoint(updated, newSelection.rotation - oldSelection.rotation, newSelection); return rotateAroundPoint(updated, newSelection.rotation - oldSelection.rotation, newSelection);
} }
function getSnap(snaps, newRotationRad, tol) {
var snapped = newRotationRad;
for (var i = 0; i < snaps.length; i++) {
var angle = Konva.getAngle(snaps[i]);
var absDiff = Math.abs(angle - newRotationRad) % (Math.PI * 2);
var dif = Math.min(absDiff, Math.PI * 2 - absDiff);
if (dif < tol) {
snapped = angle;
}
}
return snapped;
}
/** /**
* Transformer constructor. Transformer is a special type of group that allow you transform Konva * Transformer constructor. Transformer is a special type of group that allow you transform Konva
* primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes * primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes
@@ -15057,7 +15069,8 @@
var absPos = node.getAbsolutePosition(); var absPos = node.getAbsolutePosition();
var dx = rect.x * absScale.x - node.offsetX() * absScale.x; var dx = rect.x * absScale.x - node.offsetX() * absScale.x;
var dy = rect.y * absScale.y - node.offsetY() * absScale.y; var dy = rect.y * absScale.y - node.offsetY() * absScale.y;
var rotation = Konva.getAngle(node.getAbsoluteRotation()); var rotation = (Konva.getAngle(node.getAbsoluteRotation()) + Math.PI * 2) %
(Math.PI * 2);
var box = { var box = {
x: absPos.x + dx * Math.cos(rotation) + dy * Math.sin(-rotation), x: absPos.x + dx * Math.cos(rotation) + dy * Math.sin(-rotation),
y: absPos.y + dy * Math.cos(rotation) + dx * Math.sin(rotation), y: absPos.y + dy * Math.cos(rotation) + dx * Math.sin(rotation),
@@ -15235,7 +15248,27 @@
if (oldAbs.x === newAbs.x && oldAbs.y === newAbs.y) { if (oldAbs.x === newAbs.x && oldAbs.y === newAbs.y) {
return; return;
} }
var centeredScaling = this.centeredScaling() || e.altKey; // rotater is working very differently, so do it first
if (this._movingAnchorName === 'rotater') {
var attrs = this._getNodeRect();
x = anchorNode.x() - attrs.width / 2;
y = -anchorNode.y() + attrs.height / 2;
// hor angle is changed?
var delta = Math.atan2(-y, x) + Math.PI / 2;
if (attrs.height < 0) {
delta -= Math.PI;
}
var oldRotation = Konva.getAngle(this.rotation());
var newRotation = oldRotation + delta;
var tol = Konva.getAngle(this.rotationSnapTolerance());
var snappedRot = getSnap(this.rotationSnaps(), newRotation, tol);
var diff = snappedRot - attrs.rotation;
var shape = rotateAroundCenter(attrs, diff);
this._fitNodesInto(shape, e);
return;
}
var padding = 0;
// var centeredScaling = this.centeredScaling() || e.altKey;
// if (centeredScaling && this._movingAnchorName.indexOf('left') >= 0) { // if (centeredScaling && this._movingAnchorName.indexOf('left') >= 0) {
// var topLeft = this.findOne('.top-left'); // var topLeft = this.findOne('.top-left');
// var bottomRight = this.findOne('.bottom-right'); // var bottomRight = this.findOne('.bottom-right');
@@ -15244,8 +15277,8 @@
// var bottomOffsetX = this.getWidth() - bottomRight.x() + padding; // var bottomOffsetX = this.getWidth() - bottomRight.x() + padding;
// var bottomOffsetY = this.getHeight() - bottomRight.y() + padding; // var bottomOffsetY = this.getHeight() - bottomRight.y() + padding;
// bottomRight.move({ // bottomRight.move({
// x: -anchorNode.x(), // x: -topOffsetX,
// y: -anchorNode.y() // y: -topOffsetY
// }); // });
// topLeft.move({ // topLeft.move({
// x: bottomOffsetX, // x: bottomOffsetX,
@@ -15253,7 +15286,6 @@
// }); // });
// } // }
var keepProportion = this.keepRatio() || e.shiftKey; var keepProportion = this.keepRatio() || e.shiftKey;
var padding = 0;
if (this._movingAnchorName === 'top-left') { if (this._movingAnchorName === 'top-left') {
// if (centeredScaling) { // if (centeredScaling) {
// this.findOne('.bottom-right').move({ // this.findOne('.bottom-right').move({
@@ -15286,6 +15318,20 @@
this.findOne('.top-left').y(anchorNode.y()); this.findOne('.top-left').y(anchorNode.y());
} }
else if (this._movingAnchorName === 'top-right') { else if (this._movingAnchorName === 'top-right') {
// if (centeredScaling) {
// // this.findOne('.bottom-left').move({
// // x: -(anchorNode.x() - this.width()),
// // y: -anchorNode.y()
// // });
// // this.findOne('.top-left').move({
// // x: -(anchorNode.x() - this.width()),
// // y: anchorNode.y()
// // });
// // this.findOne('.bottom-right').move({
// // x: -(anchorNode.x() - this.width()),
// // y: anchorNode.y()
// // });
// }
// var center = getCenter({ // var center = getCenter({
// x // x
// }) // })
@@ -15381,32 +15427,6 @@
this.findOne('.bottom-right').y(y + padding); this.findOne('.bottom-right').y(y + padding);
} }
} }
else if (this._movingAnchorName === 'rotater') {
var attrs = this._getNodeRect();
x = anchorNode.x() - attrs.width / 2;
y = -anchorNode.y() + attrs.height / 2;
var dAlpha = Math.atan2(-y, x) + Math.PI / 2;
if (attrs.height < 0) {
dAlpha -= Math.PI;
}
var rot = Konva.getAngle(this.rotation());
var newRotation = Util._radToDeg(rot) + Util._radToDeg(dAlpha);
var alpha = Konva.getAngle(this.getNode().rotation());
var newAlpha = Util._degToRad(newRotation);
var snaps = this.rotationSnaps();
var offset = Konva.getAngle(this.rotationSnapTolerance());
for (var i = 0; i < snaps.length; i++) {
var angle = Konva.getAngle(snaps[i]);
var dif = Math.abs(angle - Util._degToRad(newRotation)) % (Math.PI * 2);
if (dif < offset) {
newRotation = Util._radToDeg(angle);
newAlpha = angle;
}
}
var delta = newAlpha - attrs.rotation;
var shape = rotateAroundCenter(attrs, delta);
this._fitNodesInto(shape, e);
}
else { else {
console.error(new Error('Wrong position argument of selection resizer: ' + console.error(new Error('Wrong position argument of selection resizer: ' +
this._movingAnchorName)); this._movingAnchorName));
@@ -15422,6 +15442,9 @@
var topOffsetY = topLeft.y() + padding; var topOffsetY = topLeft.y() + padding;
var bottomOffsetX = this.getWidth() - bottomRight.x() + padding; var bottomOffsetX = this.getWidth() - bottomRight.x() + padding;
var bottomOffsetY = this.getHeight() - bottomRight.y() + padding; var bottomOffsetY = this.getHeight() - bottomRight.y() + padding;
if (Math.abs(topOffsetY) > 10) {
debugger;
}
bottomRight.move({ bottomRight.move({
x: -topOffsetX, x: -topOffsetX,
y: -topOffsetY y: -topOffsetY

4
konva.min.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -288,6 +288,21 @@ function transformAndRotateShape(
); );
} }
function getSnap(snaps: Array<number>, newRotationRad: number, tol: number) {
let snapped = newRotationRad;
for (let i = 0; i < snaps.length; i++) {
const angle = Konva.getAngle(snaps[i]);
const absDiff = Math.abs(angle - newRotationRad) % (Math.PI * 2);
const dif = Math.min(absDiff, Math.PI * 2 - absDiff);
if (dif < tol) {
snapped = angle;
}
}
return snapped;
}
/** /**
* Transformer constructor. Transformer is a special type of group that allow you transform Konva * Transformer constructor. Transformer is a special type of group that allow you transform Konva
* primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes * primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes
@@ -493,7 +508,9 @@ export class Transformer extends Group {
var dx = rect.x * absScale.x - node.offsetX() * absScale.x; var dx = rect.x * absScale.x - node.offsetX() * absScale.x;
var dy = rect.y * absScale.y - node.offsetY() * absScale.y; var dy = rect.y * absScale.y - node.offsetY() * absScale.y;
const rotation = Konva.getAngle(node.getAbsoluteRotation()); const rotation =
(Konva.getAngle(node.getAbsoluteRotation()) + Math.PI * 2) %
(Math.PI * 2);
const box = { const box = {
x: absPos.x + dx * Math.cos(rotation) + dy * Math.sin(-rotation), x: absPos.x + dx * Math.cos(rotation) + dy * Math.sin(-rotation),
@@ -699,7 +716,34 @@ export class Transformer extends Group {
return; return;
} }
var centeredScaling = this.centeredScaling() || e.altKey; // rotater is working very differently, so do it first
if (this._movingAnchorName === 'rotater') {
var attrs = this._getNodeRect();
x = anchorNode.x() - attrs.width / 2;
y = -anchorNode.y() + attrs.height / 2;
// hor angle is changed?
let delta = Math.atan2(-y, x) + Math.PI / 2;
if (attrs.height < 0) {
delta -= Math.PI;
}
var oldRotation = Konva.getAngle(this.rotation());
const newRotation = oldRotation + delta;
const tol = Konva.getAngle(this.rotationSnapTolerance());
const snappedRot = getSnap(this.rotationSnaps(), newRotation, tol);
const diff = snappedRot - attrs.rotation;
const shape = rotateAroundCenter(attrs, diff);
this._fitNodesInto(shape, e);
return;
}
var padding = 0;
// var centeredScaling = this.centeredScaling() || e.altKey;
// if (centeredScaling && this._movingAnchorName.indexOf('left') >= 0) { // if (centeredScaling && this._movingAnchorName.indexOf('left') >= 0) {
// var topLeft = this.findOne('.top-left'); // var topLeft = this.findOne('.top-left');
// var bottomRight = this.findOne('.bottom-right'); // var bottomRight = this.findOne('.bottom-right');
@@ -710,8 +754,8 @@ export class Transformer extends Group {
// var bottomOffsetY = this.getHeight() - bottomRight.y() + padding; // var bottomOffsetY = this.getHeight() - bottomRight.y() + padding;
// bottomRight.move({ // bottomRight.move({
// x: -anchorNode.x(), // x: -topOffsetX,
// y: -anchorNode.y() // y: -topOffsetY
// }); // });
// topLeft.move({ // topLeft.move({
@@ -722,8 +766,6 @@ export class Transformer extends Group {
var keepProportion = this.keepRatio() || e.shiftKey; var keepProportion = this.keepRatio() || e.shiftKey;
var padding = 0;
if (this._movingAnchorName === 'top-left') { if (this._movingAnchorName === 'top-left') {
// if (centeredScaling) { // if (centeredScaling) {
// this.findOne('.bottom-right').move({ // this.findOne('.bottom-right').move({
@@ -773,20 +815,20 @@ export class Transformer extends Group {
// } // }
this.findOne('.top-left').y(anchorNode.y()); this.findOne('.top-left').y(anchorNode.y());
} else if (this._movingAnchorName === 'top-right') { } else if (this._movingAnchorName === 'top-right') {
if (centeredScaling) { // if (centeredScaling) {
// this.findOne('.bottom-left').move({ // // this.findOne('.bottom-left').move({
// x: -(anchorNode.x() - this.width()), // // x: -(anchorNode.x() - this.width()),
// y: -anchorNode.y() // // y: -anchorNode.y()
// }); // // });
// this.findOne('.top-left').move({ // // this.findOne('.top-left').move({
// x: -(anchorNode.x() - this.width()), // // x: -(anchorNode.x() - this.width()),
// y: anchorNode.y() // // y: anchorNode.y()
// }); // // });
// this.findOne('.bottom-right').move({ // // this.findOne('.bottom-right').move({
// x: -(anchorNode.x() - this.width()), // // x: -(anchorNode.x() - this.width()),
// y: anchorNode.y() // // y: anchorNode.y()
// }); // // });
} // }
// var center = getCenter({ // var center = getCenter({
// x // x
@@ -920,44 +962,6 @@ export class Transformer extends Group {
this.findOne('.bottom-right').x(x + padding); this.findOne('.bottom-right').x(x + padding);
this.findOne('.bottom-right').y(y + padding); this.findOne('.bottom-right').y(y + padding);
} }
} else if (this._movingAnchorName === 'rotater') {
var attrs = this._getNodeRect();
x = anchorNode.x() - attrs.width / 2;
y = -anchorNode.y() + attrs.height / 2;
var dAlpha = Math.atan2(-y, x) + Math.PI / 2;
if (attrs.height < 0) {
dAlpha -= Math.PI;
}
var rot = Konva.getAngle(this.rotation());
var newRotation = Util._radToDeg(rot) + Util._radToDeg(dAlpha);
var alpha = Konva.getAngle(this.getNode().rotation());
var newAlpha = Util._degToRad(newRotation);
var snaps = this.rotationSnaps();
var offset = Konva.getAngle(this.rotationSnapTolerance());
for (var i = 0; i < snaps.length; i++) {
var angle = Konva.getAngle(snaps[i]);
var dif = Math.abs(angle - Util._degToRad(newRotation)) % (Math.PI * 2);
if (dif < offset) {
newRotation = Util._radToDeg(angle);
newAlpha = angle;
}
}
const delta = newAlpha - attrs.rotation;
var dx = padding;
var dy = padding;
const shape = rotateAroundCenter(attrs, delta);
this._fitNodesInto(shape, e);
} else { } else {
console.error( console.error(
new Error( new Error(
@@ -981,6 +985,9 @@ export class Transformer extends Group {
var bottomOffsetX = this.getWidth() - bottomRight.x() + padding; var bottomOffsetX = this.getWidth() - bottomRight.x() + padding;
var bottomOffsetY = this.getHeight() - bottomRight.y() + padding; var bottomOffsetY = this.getHeight() - bottomRight.y() + padding;
if (Math.abs(topOffsetY) > 10) {
debugger;
}
bottomRight.move({ bottomRight.move({
x: -topOffsetX, x: -topOffsetX,
y: -topOffsetY y: -topOffsetY

View File

@@ -1089,6 +1089,98 @@ suite('Transformer', function() {
}); });
}); });
test('rotation snaps', function() {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var rect = new Konva.Rect({
x: 50,
y: 50,
width: 100,
height: 100,
fill: 'yellow'
});
layer.add(rect);
var tr = new Konva.Transformer({
node: rect,
rotationSnaps: [0, 90, 180, 270],
rotationSnapTolerance: 45
});
layer.add(tr);
layer.draw();
tr.simulateMouseDown({
x: 100,
y: 0
});
// move to almost 45 deg
tr.simulateMouseMove({
x: 199,
y: 0
});
assert.equal(rect.rotation(), 0);
// move to more than 45 deg
tr.simulateMouseMove({
x: 200,
y: 2
});
assert.equal(rect.rotation(), 90);
tr.simulateMouseMove({
x: 200,
y: 199
});
assert.equal(rect.rotation(), 90);
tr.simulateMouseMove({
x: 199,
y: 200
});
assert.equal(rect.rotation(), 180);
tr.simulateMouseMove({
x: 1,
y: 200
});
assert.equal(rect.rotation(), 180);
tr.simulateMouseMove({
x: 0,
y: 199
});
assert.equal(rect.rotation(), 270);
tr.simulateMouseMove({
x: 0,
y: 50
});
assert.equal(rect.rotation(), 270);
tr.simulateMouseMove({
x: 0,
y: 45
});
assert.equal(rect.rotation(), 270);
tr.simulateMouseMove({
x: 0,
y: 1
});
assert.equal(rect.rotation(), 270);
tr.simulateMouseMove({
x: 1,
y: 0
});
assert.equal(rect.rotation(), 0);
tr.simulateMouseUp();
});
test('switch scaling with padding - x', function() { test('switch scaling with padding - x', function() {
var stage = addStage(); var stage = addStage();
var layer = new Konva.Layer(); var layer = new Konva.Layer();
@@ -2542,7 +2634,7 @@ suite('Transformer', function() {
}); });
}); });
// TODO: fix it!!! // TODO: fix
test.skip('centered scaling on flip + keep ratio', function() { test.skip('centered scaling on flip + keep ratio', function() {
var stage = addStage(); var stage = addStage();
var layer = new Konva.Layer(); var layer = new Konva.Layer();
@@ -2564,7 +2656,7 @@ suite('Transformer', function() {
rect.setAttrs({ rect.setAttrs({
x: 0, x: 0,
y: 0, y: 0,
width: 200, width: 100,
height: 100, height: 100,
scaleX: 1, scaleX: 1,
scaleY: 1 scaleY: 1
@@ -2578,31 +2670,36 @@ suite('Transformer', function() {
y: 0 y: 0
}); });
tr.simulateMouseMove({ tr.simulateMouseMove({
x: 200, x: 100,
y: 0 y: 0
}); });
assert.equal(isClose(rect.x(), 200), true);
assert.equal(isClose(rect.y(), 0), true); var box = rect.getClientRect();
assert.equal(rect.width(), 200); console.log(box);
assert.equal(Math.round(rect.scaleY()), 1); // assert.equal(rect.x(), 110);
assert.equal(Math.round(rect.scaleX()), -1); // assert.equal(isClose(rect.y(), 0), true);
assert.equal(rect.height(), 100); // assert.equal(rect.width(), 200);
// assert.equal(Math.round(rect.scaleY()), 1);
// assert.equal(Math.round(rect.scaleX()), -1);
// assert.equal(rect.height(), 100);
tr.simulateMouseMove({ tr.simulateMouseMove({
x: 200, x: 100,
y: 0 y: 0
}); });
var box = rect.getClientRect();
console.log(box);
tr.simulateMouseUp({ tr.simulateMouseUp({
x: 0, x: 0,
y: 0 y: 0
}); });
layer.draw(); layer.draw();
assert.equal(isClose(rect.x(), 200), true); // assert.equal(isClose(rect.x(), 200), true);
assert.equal(isClose(rect.y(), 0), true); // assert.equal(isClose(rect.y(), 0), true);
assert.equal(rect.width(), 200); // assert.equal(rect.width(), 200);
assert.equal(Math.round(rect.scaleY()), -1); // assert.equal(Math.round(rect.scaleY()), -1);
assert.equal(Math.round(rect.scaleX()), 1); // assert.equal(Math.round(rect.scaleX()), 1);
assert.equal(rect.height(), 100); // assert.equal(rect.height(), 100);
}); });
test('transform scaled (in one direction) node', function() { test('transform scaled (in one direction) node', function() {
@@ -3231,7 +3328,7 @@ suite('Transformer', function() {
}); });
layer.add(tr1); layer.add(tr1);
const rect2 = rect1.clone({ var rect2 = rect1.clone({
fill: 'red', fill: 'red',
x: stage.width() / 3, x: stage.width() / 3,
y: stage.height() / 3 y: stage.height() / 3
@@ -3240,7 +3337,7 @@ suite('Transformer', function() {
tr1.destroy(); tr1.destroy();
let tr2 = new Konva.Transformer({ var tr2 = new Konva.Transformer({
node: rect2 node: rect2
}); });
layer.add(tr2); layer.add(tr2);