diff options
author | bobzel <zzzman@gmail.com> | 2024-11-14 10:48:16 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2024-11-14 10:48:16 -0500 |
commit | 196b92cb84095780d2b36244831cac03e9b66d8e (patch) | |
tree | fb540f5b8e79e6044c4c8fde36360a64084d2c4b /src | |
parent | ca101a6b1899bdeceef8f46a4228d90e43207593 (diff) |
changed isScribble related code to be more robust at determine cusps and deciding if cusps amount to a scribble. enabled undo of scribble erase without undoing scribble. added cusp visualizer for debugging, and cleaned up downDocView to be downFfview on CollectionFreeformView.
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/GestureOverlay.tsx | 34 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 7 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 7 |
3 files changed, 27 insertions, 21 deletions
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index b0a750a9a..777a34ebc 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -64,6 +64,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil @observable private _pointerY?: number = undefined; @observable private _points: { X: number; Y: number }[] = []; @observable private _clipboardDoc?: JSX.Element = undefined; + @observable private _debugCusps: { X: number; Y: number }[] = []; + @observable private _debugGestures = false; @computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100); @@ -170,9 +172,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil * Determines if what the array of cusp/intersection data corresponds to a scribble. * true if there are at least 4 cusps and either: * 1) the initial and final quarters of the array contain objects - * 2) or half of the cusps contain objects + * 2) or a declining percentage (ranges from 0.5 to 0.2 - based on the number of cusps) of cusp lines intersect strokes * @param intersectArray array of booleans coresponding to which scribble sections (regions separated by a cusp) contain Docs - * @returns + * @returns truthy if it's a scribble */ determineIfScribble = (intersectArray: boolean[]) => { const quarterArrayLength = Math.ceil(intersectArray.length / 3.9); // use 3.9 instead of 4 to work better with strokes with only 4 cusps @@ -182,7 +184,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil }), { start: false, end: false }); // prettier-ignore const percentCuspsWithContent = intersectArray.filter(value => value).length / intersectArray.length; - return intersectArray.length > 3 && (percentCuspsWithContent >= 0.5 || (start && end)); + return intersectArray.length > 3 && (percentCuspsWithContent >= Math.max(0.2, 1 / (intersectArray.length - 1)) || (start && end)); }; /** * determines if inks intersect @@ -236,9 +238,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil }; @action onPointerUp = (e: PointerEvent) => { - const ffView = DocumentView.DownDocView?.ComponentView instanceof CollectionFreeFormView && DocumentView.DownDocView.ComponentView; - const downView = DocumentView.DownDocView; - DocumentView.DownDocView = undefined; + const ffView = CollectionFreeFormView.DownFfview; + CollectionFreeFormView.DownFfview = undefined; if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); @@ -251,6 +252,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil new Result(Gestures.Stroke, 1, Date.now); // prettier-ignore const cuspArray = this.getCusps(points); + const rect = this._overlayRef.current?.getBoundingClientRect(); + this._debugCusps = rect ? cuspArray.map(p => ({ X: p.X + B.left - rect?.left, Y: p.Y + B.top - rect.top })) : []; // if any of the shape is activated in the CollectionFreeFormViewChrome // need to decide when to turn gestures back on const actionPerformed = ((name: Gestures) => { @@ -272,14 +275,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil if (!actionPerformed) { const scribbledOver = ffView && this.isScribble(ffView, cuspArray, this._points); + this.dryInk(); if (scribbledOver) { - undoable(() => ffView.removeDocument(scribbledOver), 'scribble erase')(); - } else { - this.dryInk(); + // can undo the erase without undoing the scribble, or undo a second time to undo the scribble + setTimeout(undoable(() => ffView.removeDocument(scribbledOver.concat([ffView.childDocs.lastElement()])), 'scribble erase')); } } } else { - (downView?.ComponentView as CollectionFreeFormView)?._marqueeViewRef?.current?.setPreviewCursor?.(this._points[0].X, this._points[0].Y, false, false, undefined); + ffView?._marqueeViewRef?.current?.setPreviewCursor?.(this._points[0].X, this._points[0].Y, false, false, undefined); e.preventDefault(); } this.primCreated(); @@ -336,13 +339,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil getCusps(points: InkData) { const arrayOfPoints: { X: number; Y: number }[] = []; arrayOfPoints.push(points[0]); - for (let i = 0; i < points.length - 2; i++) { + for (let i = 0; i < points.length - 4; i++) { const point1 = points[i]; - const point2 = points[i + 1]; - const point3 = points[i + 2]; + const point2 = points[i + 2]; + const point3 = points[i + 4]; if (this.find_angle(point1, point2, point3) < 90) { // NOTE: this is not an accurate way to find cusps -- it is highly dependent on sampling rate and doesn't work well with slowly drawn scribbles arrayOfPoints.push(point2); + i += 2; } } arrayOfPoints.push(points[points.length - 1]); @@ -538,7 +542,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil } get elements() { - const selView = DocumentView.DownDocView; + const selView = CollectionFreeFormView.DownFfview; const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.screenToViewTransform().Scale || 1); const rect = this._overlayRef.current?.getBoundingClientRect(); const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(this._points, true); @@ -590,7 +594,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil return ( <div className="gestureOverlay-cont" style={{ pointerEvents: this._props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}> {this.elements} - + {this._debugGestures && this._debugCusps.map(c => <div key={c.toString()} style={{ top: 0, left: 0, position: 'absolute', transform: `translate(${c.X}px, ${c.Y}px)`, width: 4, height: 4, background: 'red' }} />)} <div className="clipboardDoc-cont" style={{ diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3de97f836..49b432d7c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -98,6 +98,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const parent = CollectionFreeFormDocumentView.from(dv)?._props.reactParent; return parent instanceof CollectionFreeFormView ? parent : undefined; } + /** + * The Freeformview below the cursor at the start of a gesture (that receives the pointerDown event). Used by GestureOverlay to determine the doc a gesture should apply to. + */ + // eslint-disable-next-line no-use-before-define + public static DownFfview: CollectionFreeFormView | undefined; // the first DocView that receives a pointerdown event. used by GestureOverlay to determine the doc a gesture should apply to. private _clusters = new CollectionFreeFormClusters(this); private _oldWheel: HTMLDivElement | null = null; @@ -497,6 +502,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action onPointerDown = (e: React.PointerEvent): void => { + if (!CollectionFreeFormView.DownFfview) CollectionFreeFormView.DownFfview = this; + this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; this._downTime = Date.now(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 92e98db87..4bfa7fc92 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -355,7 +355,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document onPointerDown = (e: React.PointerEvent): void => { if (this._props.isGroupActive?.() === GroupActive.child && !this._props.isDocumentActive?.()) return; this._longPressSelector = setTimeout(() => SnappingManager.LongPress && this._props.select(false), 1000); - if (!DocumentView.DownDocView) DocumentView.DownDocView = this._docView; this._downX = e.clientX; this._downY = e.clientY; @@ -459,7 +458,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } if (annoData || this.Document !== linkdrag.linkSourceDoc.embedContainer) { const dropDoc = annoData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.Document; - const linkDoc = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {layout_isSvg: true }, undefined, [de.x, de.y - 50]); + const linkDoc = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, { layout_isSvg: true }, undefined, [de.x, de.y - 50]); if (linkDoc) { de.complete.linkDocument = linkDoc; DocumentView.linkCommonAncestor(linkDoc)?.ComponentView?.addDocument?.(linkDoc); @@ -1075,10 +1074,6 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { ?.ComponentView?.updateIcon?.() .then(() => ImageCast(DocCast(doc).icon)); } - /** - * The DocumentView below the cursor at the start of a gesture (that receives the pointerDown event). Used by GestureOverlay to determine the doc a gesture should apply to. - */ - public static DownDocView: DocumentView | undefined; // the first DocView that receives a pointerdown event. used by GestureOverlay to determine the doc a gesture should apply to. public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore private _htmlOverlayEffect: Opt<Doc>; |