From b30b1cb301ed742a128d5e17eea9c954d1fe0c22 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Oct 2021 20:40:58 -0400
Subject:  fix to ink rotation to preserve rotation center between rotations.

---
 src/client/views/nodes/DocumentView.tsx | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 949e0e168..cbb77f369 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -97,6 +97,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getScrollHeight?: () => number;
+    getCenter?: (xf: Transform) => { X: number, Y: number };
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
 export interface DocumentViewSharedProps {
@@ -1179,9 +1180,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
         if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
-            if (docuBox.length) return docuBox[0].getBoundingClientRect();
+            if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
-        return { left, top, right, bottom };
+        return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
     }
 
     public iconify() {
-- 
cgit v1.2.3-70-g09d2


From b33abf746e82cabf973ae3b8a81fd6b0781b2bfe Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Oct 2021 22:38:05 -0400
Subject: adding snapping code between ink strokes.  added some componentView
 API functions to convert local (eg ink) points to/from screen space and for
 snapping

---
 src/client/views/InkStrokeProperties.ts | 74 ++++++++++++++++++---------------
 src/client/views/InkingStroke.tsx       | 26 ++++++++++++
 src/client/views/nodes/DocumentView.tsx |  3 ++
 3 files changed, 70 insertions(+), 33 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 156bb1a6a..b3633358d 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;
@@ -211,7 +212,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 +220,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,22 +239,22 @@ export class InkStrokeProperties {
      */
     snapControl = (inkView: DocumentView, controlIndex: number) => {
         const inkDoc = inkView.rootDoc;
-        const ink = Cast(inkDoc.data, InkField)?.inkData;
+        const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData;
 
         if (ink) {
-            const { near, nearestPt, ptsXscale, ptsYscale } = this.snapWithinCurve(ink, inkDoc, controlIndex);
-            if (near / (inkView.props.ScreenToLocalTransform().Scale || 1) < 10) {
-                const deltaX = (nearestPt.X - ink[controlIndex].X) * ptsXscale;
-                const deltaY = (nearestPt.Y - ink[controlIndex].Y) * ptsYscale;
+            const snapData = this.snapWithinCurve(ink, inkView, controlIndex);
+            if (snapData && snapData.distance < 10) {
+                const deltaX = (snapData.nearestPt.X - ink[controlIndex].X);
+                const deltaY = (snapData.nearestPt.Y - ink[controlIndex].Y);
                 return this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex);
             } else {
-                return this.snapBetweenCurves(ink, inkDoc, controlIndex);
+                return this.snapBetweenCurves(ink, inkView, controlIndex);
             }
         }
         return false;
     }
 
-    snapWithinCurve = (ink: InkData, inkDoc: Doc, controlIndex: number) => {
+    snapWithinCurve = (ink: InkData, inkView: DocumentView, 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)
@@ -261,30 +262,37 @@ export class InkStrokeProperties {
         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]);
-
-        // 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 { ptsXscale, ptsYscale } = this.inkToScreenScale(ink, inkDoc);
-        const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale +
-            (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale);
-
-        return { near, nearestPt, ptsXscale, ptsYscale };
+        const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]);
+        if (screenDragPt) {
+            const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(ink, ink[controlIndex], [thisseg, prevseg, nextseg]);
+            return {
+                nearestPt,
+                distance: distance * inkView.props.ScreenToLocalTransform().inverse().Scale
+            };
+        }
     }
 
-    snapBetweenCurves = (ink: InkData, inkDoc: Doc, controlIndex: number) => {
-        const inkContext = Cast(inkDoc.context, Doc, null);
-        // Cast(inkContext.data)
-
-        // .filter(doc => doc.type === DocumentType.INK)
-        // .map(doc => {
-        //     if (InkStrokeProperties.Instance?._lock) {
-        //         Doc.SetNativeHeight(doc, NumCast(doc._height));
-        //         Doc.SetNativeWidth(doc, NumCast(doc._width));
-        //     }
-        //     return ({ doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
-        // });
+    snapBetweenCurves = (ink: InkData, inkView: DocumentView, controlIndex: number) => {
+        const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]);
+        const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+        screenDragPt && containingCollection?.childDocs
+            .filter(doc => doc.type === DocumentType.INK && doc !== inkView.rootDoc)
+            .forEach(doc => {
+                const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView);
+                const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt);
+                if (snapped) {
+                    const { nearestPt, distance } = snapped;
+                    if (distance < 10 /* refactor out snapping threshold and test that this is closer than any other curve */) {
+                        const snappedScrPt = testInkView?.ComponentView?.ptToScreen?.(nearestPt);  // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
+                        const snappedInkPt = snappedScrPt && inkView.ComponentView?.ptFromScreen?.(snappedScrPt);
+                        if (snappedInkPt) {
+                            const deltaX = (snappedInkPt.X - ink[controlIndex].X);
+                            const deltaY = (snappedInkPt.Y - ink[controlIndex].Y);
+                            return this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex);
+                        }
+                    }
+                }
+            });
         return false;
     }
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 24c2d7651..b258ea741 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -124,6 +124,32 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
+    ptFromScreen = (scrPt: { X: number, Y: number }) => {
+        const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+        const docPt = this.props.ScreenToLocalTransform().transformPoint(scrPt.X, scrPt.Y);
+        const inkPt = {
+            X: (docPt[0] - inkStrokeWidth / 2) / inkScaleX + inkStrokeWidth / 2 + inkLeft,
+            Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop,
+        }
+        return inkPt;
+    }
+    ptToScreen = (inkPt: { X: number, Y: number }) => {
+        const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+        const docPt = {
+            X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
+            Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2
+        };
+        const scrPt = this.props.ScreenToLocalTransform().inverse().transformPoint(docPt.X, docPt.Y);
+        return { X: scrPt[0], Y: scrPt[1] };
+    }
+
+    snapPt = (scrPt: { X: number, Y: number }) => {
+        const { inkData } = this.inkScaledData();
+        const inkPt = this.ptFromScreen(scrPt);
+        const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, inkPt, []);
+        return { nearestPt, distance: distance * this.props.ScreenToLocalTransform().inverse().Scale };
+    }
+
     inkScaledData = () => {
         const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cbb77f369..d7d886667 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -98,6 +98,9 @@ export interface DocComponentView {
     getTitle?: () => string;
     getScrollHeight?: () => number;
     getCenter?: (xf: Transform) => { X: number, Y: number };
+    ptToScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
+    ptFromScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
+    snapPt?: (pt: { X: number, Y: number }) => { nearestPt: { X: number, Y: number }, distance: number };
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
 export interface DocumentViewSharedProps {
-- 
cgit v1.2.3-70-g09d2


From 67ad73e9acc56d5b0951711b05426289c60a06c1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Oct 2021 23:26:49 -0400
Subject: simplified snapping

---
 src/client/views/InkStrokeProperties.ts | 52 +++++++++++++--------------------
 src/client/views/InkingStroke.tsx       |  4 +--
 src/client/views/nodes/DocumentView.tsx |  2 +-
 3 files changed, 23 insertions(+), 35 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 4ecedbfd6..2048b9a19 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -242,19 +242,20 @@ export class InkStrokeProperties {
         const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData;
 
         if (ink) {
-            const snapData = this.snapWithinCurve(ink, inkView, controlIndex);
-            if (snapData && snapData.distance < 10) {
-                const deltaX = (snapData.nearestPt.X - ink[controlIndex].X);
-                const deltaY = (snapData.nearestPt.Y - ink[controlIndex].Y);
-                return this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex);
-            } else {
-                return this.snapBetweenCurves(ink, inkView, controlIndex);
+            const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]);
+            if (screenDragPt) {
+                var 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);
+                    return this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex);
+                }
             }
         }
         return false;
     }
 
-    snapWithinCurve = (ink: InkData, inkView: DocumentView, controlIndex: number) => {
+    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)
@@ -262,38 +263,25 @@ export class InkStrokeProperties {
         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 screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]);
-        if (screenDragPt) {
-            const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(ink, ink[controlIndex], [thisseg, prevseg, nextseg]);
-            return {
-                nearestPt,
-                distance: distance * inkView.props.ScreenToLocalTransform().inverse().Scale
-            };
-        }
+        return [thisseg, prevseg, nextseg];
     }
 
-    snapBetweenCurves = (ink: InkData, inkView: DocumentView, controlIndex: number) => {
-        const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]);
+    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;
-        screenDragPt && containingCollection?.childDocs
-            .filter(doc => doc.type === DocumentType.INK && doc !== inkView.rootDoc)
+        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);
-                if (snapped) {
-                    const { nearestPt, distance } = snapped;
-                    if (distance < 10 /* refactor out snapping threshold and test that this is closer than any other curve */) {
-                        const snappedScrPt = testInkView?.ComponentView?.ptToScreen?.(nearestPt);  // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
-                        const snappedInkPt = snappedScrPt && inkView.ComponentView?.ptFromScreen?.(snappedScrPt);
-                        if (snappedInkPt) {
-                            const deltaX = (snappedInkPt.X - ink[controlIndex].X);
-                            const deltaY = (snappedInkPt.Y - ink[controlIndex].Y);
-                            return this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex);
-                        }
+                const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
+                if (snapped && snapped.distance < snapData.distance) {
+                    const snappedScrPt = testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt);  // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
+                    const snappedInkPt = snappedScrPt && inkView.ComponentView?.ptFromScreen?.(snappedScrPt);
+                    if (snappedInkPt) {
+                        snapData = { nearestPt: snappedInkPt, distance: snapped.distance };
                     }
                 }
             });
-        return false;
+        return snapData;
     }
 
     /**
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index b258ea741..81a888256 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -143,10 +143,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         return { X: scrPt[0], Y: scrPt[1] };
     }
 
-    snapPt = (scrPt: { X: number, Y: number }) => {
+    snapPt = (scrPt: { X: number, Y: number }, excludeSegs?: number[]) => {
         const { inkData } = this.inkScaledData();
         const inkPt = this.ptFromScreen(scrPt);
-        const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, inkPt, []);
+        const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, inkPt, excludeSegs ?? []);
         return { nearestPt, distance: distance * this.props.ScreenToLocalTransform().inverse().Scale };
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d7d886667..c9b246c10 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -100,7 +100,7 @@ export interface DocComponentView {
     getCenter?: (xf: Transform) => { X: number, Y: number };
     ptToScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
     ptFromScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
-    snapPt?: (pt: { X: number, Y: number }) => { nearestPt: { X: number, Y: number }, distance: number };
+    snapPt?: (pt: { X: number, Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number, Y: number }, distance: number };
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
 export interface DocumentViewSharedProps {
-- 
cgit v1.2.3-70-g09d2

