diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/InkStrokeProperties.ts | 74 | ||||
-rw-r--r-- | src/client/views/InkingStroke.tsx | 26 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 3 |
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 { |