aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2021-10-26 22:38:05 -0400
committerbobzel <zzzman@gmail.com>2021-10-26 22:38:05 -0400
commitb33abf746e82cabf973ae3b8a81fd6b0781b2bfe (patch)
treebf5d3e32b16f6fb96e312435ffddfa64c9409487
parentc7aea59ca8cd24bf218be581ea241670fee6cc49 (diff)
adding snapping code between ink strokes. added some componentView API functions to convert local (eg ink) points to/from screen space and for snapping
-rw-r--r--src/client/views/InkStrokeProperties.ts74
-rw-r--r--src/client/views/InkingStroke.tsx26
-rw-r--r--src/client/views/nodes/DocumentView.tsx3
3 files changed, 70 insertions, 33 deletions
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 {