aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkStrokeProperties.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/InkStrokeProperties.ts')
-rw-r--r--src/client/views/InkStrokeProperties.ts104
1 files changed, 60 insertions, 44 deletions
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 33e25bbbb..6687b2bc7 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,6 +1,6 @@
import { Bezier } from "bezier-js";
import { action, observable, reaction } from "mobx";
-import { Doc, Opt } from "../../fields/Doc";
+import { Doc, Opt, DocListCast } from "../../fields/Doc";
import { InkData, InkField, InkTool, PointData } from "../../fields/InkField";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
@@ -10,6 +10,7 @@ import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { undoBatch } from "../util/UndoManager";
import { InkingStroke } from "./InkingStroke";
import { DocumentView } from "./nodes/DocumentView";
+import { DocumentManager } from "../util/DocumentManager";
export class InkStrokeProperties {
static Instance: InkStrokeProperties | undefined;
@@ -155,29 +156,24 @@ export class InkStrokeProperties {
}, true)
/**
- * Rotates the entire selected ink instance.
+ * Rotates ink stroke(s) about a point
+ * @param inkStrokes set of ink documentViews to rotate
* @param angle The angle at which to rotate the ink in radians.
+ * @param scrpt The center point of the rotation in screen coordinates
*/
@undoBatch
@action
rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: { x: number, y: number }) => {
this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => {
- const oldXrangeMin = Math.min(...ink.map(p => p.X));
- const oldYrangeMin = Math.min(...ink.map(p => p.Y));
- const docViewCenterPt = view.screenToLocalTransform().transformPoint(scrpt.x, scrpt.y);
- const inkCenterPt = {
- X: (docViewCenterPt[0] - inkStrokeWidth / 2) / xScale + oldXrangeMin,
- Y: (docViewCenterPt[1] - inkStrokeWidth / 2) / yScale + oldYrangeMin
- };
- const newPoints = ink.map(i => {
- const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y };
- const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale;
- const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y;
- return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y };
- });
- const doc = view.rootDoc;
- doc.rotation = NumCast(doc.rotation) + angle;
- return newPoints;
+ view.rootDoc.rotation = NumCast(view.rootDoc.rotation) + angle;
+ const inkCenterPt = view.ComponentView?.ptFromScreen?.({ X: scrpt.x, Y: scrpt.y });
+ return !inkCenterPt ? ink :
+ ink.map(i => {
+ const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y };
+ const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale;
+ const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y;
+ return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y };
+ });
});
}
@@ -203,7 +199,7 @@ export class InkStrokeProperties {
(order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) ||
(order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) ||
((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) {
- return ({ X: pt.X + deltaX / xScale, Y: pt.Y + deltaY / yScale });
+ return ({ X: pt.X + deltaX, Y: pt.Y + deltaY });
}
return pt;
});
@@ -211,7 +207,7 @@ export class InkStrokeProperties {
})
- public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refPt: { X: number, Y: number }, excludeSegs?: number[]) {
+ public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refInkSpacePt: { X: number, Y: number }, excludeSegs?: number[]) {
var distance = Number.MAX_SAFE_INTEGER;
var nearestT = -1;
var nearestSeg = -1;
@@ -219,9 +215,9 @@ export class InkStrokeProperties {
for (var i = 0; i < ctrlPoints.length - 3; i += 4) {
if (excludeSegs?.includes(i)) continue;
const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]];
- const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refPt.X, y: refPt.Y });
+ const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refInkSpacePt.X, y: refInkSpacePt.Y });
if (point.t !== undefined) {
- const dist = Math.sqrt((point.x - refPt.X) * (point.x - refPt.X) + (point.y - refPt.Y) * (point.y - refPt.Y));
+ const dist = Math.sqrt((point.x - refInkSpacePt.X) * (point.x - refInkSpacePt.X) + (point.y - refInkSpacePt.Y) * (point.y - refInkSpacePt.Y));
if (dist < distance) {
distance = dist;
nearestT = point.t;
@@ -238,34 +234,54 @@ export class InkStrokeProperties {
*/
snapControl = (inkView: DocumentView, controlIndex: number) => {
const inkDoc = inkView.rootDoc;
- const ink = Cast(inkDoc.data, InkField)?.inkData;
- if (ink) {
- const closed = InkingStroke.IsClosed(ink);
-
- // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve)
- const thisseg = Math.floor(controlIndex / 4) * 4;
- const which = controlIndex % 4;
- const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1;
- const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1;
- const refPt = ink[controlIndex];
- const { nearestPt } = InkStrokeProperties.nearestPtToStroke(ink, refPt, [thisseg, prevseg, nextseg]);
+ const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData;
- // nearestPt is in inkDoc coordinates -- we need to compute the distance in screen coordinates.
- // so we scale the X & Y distances by the internal ink scale factor and then transform the final distance by the ScreenToLocal.Scale of the inkDoc itself.
- const oldXrange = (xs => ({ coord: NumCast(inkDoc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
- const oldYrange = (ys => ({ coord: NumCast(inkDoc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
- const ptsXscale = ((NumCast(inkDoc._width) - NumCast(inkDoc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1;
- const ptsYscale = ((NumCast(inkDoc._height) - NumCast(inkDoc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1;
- const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale +
- (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale);
-
- if (near / (inkView.props.ScreenToLocalTransform().Scale || 1) < 10) {
- return this.moveControlPtHandle(inkView, (nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex);
+ if (ink) {
+ const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]);
+ if (screenDragPt) {
+ const snapData = this.snapToAllCurves(screenDragPt, inkView, { nearestPt: { X: 0, Y: 0 }, distance: 10 }, ink, controlIndex);
+ if (snapData.distance < 10) {
+ const deltaX = (snapData.nearestPt.X - ink[controlIndex].X);
+ const deltaY = (snapData.nearestPt.Y - ink[controlIndex].Y);
+ const res = this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex);
+ console.log("X= " + snapData.nearestPt.X + " " + snapData.nearestPt.Y);
+ return res;
+ }
}
}
return false;
}
+ excludeSelfSnapSegs = (ink: InkData, controlIndex: number) => {
+ const closed = InkingStroke.IsClosed(ink);
+
+ // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve)
+ const thisseg = Math.floor(controlIndex / 4) * 4;
+ const which = controlIndex % 4;
+ const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1;
+ const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1;
+ return [thisseg, prevseg, nextseg];
+ }
+
+ snapToAllCurves = (screenDragPt: { X: number, Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number, Y: number }, distance: number }, ink: InkData, controlIndex: number) => {
+ const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ containingCollection?.childDocs
+ .filter(doc => doc.type === DocumentType.INK)
+ .forEach(doc => {
+ const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView);
+ const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
+ if (snapped && snapped.distance < snapData.distance) {
+ const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt :
+ inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
+
+ if (snappedInkPt) {
+ snapData = { nearestPt: snappedInkPt, distance: snapped.distance };
+ }
+ }
+ });
+ return snapData;
+ }
+
/**
* Snaps a control point with broken tangency back to synced rotation.
* @param handleIndexA The handle point that retains its current position.