diff options
Diffstat (limited to 'src/client/views/GestureOverlay.tsx')
-rw-r--r-- | src/client/views/GestureOverlay.tsx | 206 |
1 files changed, 73 insertions, 133 deletions
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 5fddaec9a..777a34ebc 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,9 +2,9 @@ import * as fitCurve from 'fit-curve'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; +import { setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction, intersectRect } from '../../Utils'; -import { Doc, Opt, returnEmptyDoclist } from '../../fields/Doc'; +import { Doc } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; import { NumCast } from '../../fields/Types'; import { Gestures } from '../../pen-gestures/GestureTypes'; @@ -14,27 +14,25 @@ import { DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; import { InteractionUtils } from '../util/InteractionUtils'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { Transform } from '../util/Transform'; +import { SnappingManager } from '../util/SnappingManager'; import { undoable } from '../util/UndoManager'; import './GestureOverlay.scss'; import { InkingStroke } from './InkingStroke'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { returnEmptyDocViewList } from './StyleProvider'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { - ActiveArrowEnd, - ActiveArrowScale, - ActiveArrowStart, - ActiveDash, - ActiveFillColor, - ActiveInkBezierApprox, + ActiveInkArrowEnd, + ActiveInkArrowScale, + ActiveInkArrowStart, ActiveInkColor, + ActiveInkDash, + ActiveInkFillColor, ActiveInkWidth, DocumentView, - SetActiveArrowStart, - SetActiveDash, - SetActiveFillColor, + SetActiveInkArrowStart, SetActiveInkColor, + SetActiveInkDash, + SetActiveInkFillColor, SetActiveInkWidth, } from './nodes/DocumentView'; export enum ToolglassTools { @@ -57,20 +55,17 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil // eslint-disable-next-line no-use-before-define static Instances: GestureOverlay[] = []; - @observable public InkShape: Opt<Gestures> = undefined; @observable public SavedColor?: string = undefined; @observable public SavedWidth?: number = undefined; @observable public Tool: ToolglassTools = ToolglassTools.None; - @observable public KeepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode @observable private _thumbX?: number = undefined; @observable private _thumbY?: number = undefined; @observable private _pointerY?: number = undefined; @observable private _points: { X: number; Y: number }[] = []; - @observable private _strokes: InkData[] = []; - @observable private _palette?: JSX.Element = undefined; @observable private _clipboardDoc?: JSX.Element = undefined; - @observable private _possibilities: JSX.Element[] = []; + @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); @@ -96,8 +91,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil } @action onPointerDown = (e: React.PointerEvent) => { + (document.activeElement as HTMLElement)?.blur(); if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) { - if ([InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (Doc.ActiveTool === InkTool.Ink) { this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); } @@ -123,13 +119,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil }; @action primCreated() { - if (!this.KeepPrimitiveMode) { - this.InkShape = undefined; - // get out of ink mode after each stroke= - // if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor); + if (!SnappingManager.KeepGestureMode) { + SnappingManager.SetInkShape(undefined); Doc.ActiveTool = InkTool.None; - // SetActiveArrowStart('none'); - // SetActiveArrowEnd('none'); } } /** @@ -180,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 @@ -192,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 @@ -209,8 +201,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil const intersectArray: string[] = []; const scribbleBounds = InkField.getBounds(scribble); for (let i = 0; i < scribble.length - 3; i += 4) { // for each segment of scribble + const scribbleSeg = InkField.Segment(scribble, i); for (let j = 0; j < inkStroke.length - 3; j += 4) { // for each segment of ink stroke - const scribbleSeg = InkField.Segment(scribble, i); const strokeSeg = InkField.Segment(inkStroke, j); const strokeBounds = InkField.getBounds(strokeSeg.points.map(pt => ({ X: pt.x, Y: pt.y }))); if (intersectRect(scribbleBounds, strokeBounds)) { @@ -245,27 +237,29 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil this.dispatchGesture(Gestures.Stroke); }; @action - onPointerUp = () => { - const ffView = DocumentView.DownDocView?.ComponentView instanceof CollectionFreeFormView && DocumentView.DownDocView.ComponentView; - DocumentView.DownDocView = undefined; + onPointerUp = (e: PointerEvent) => { + 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 })); const { Name, Score } = - (this.InkShape - ? new Result(this.InkShape, 1, Date.now) + (SnappingManager.InkShape + ? new Result(SnappingManager.InkShape, 1, Date.now) : Doc.UserDoc().recognizeGestures && points.length > 2 ? GestureUtils.GestureRecognizer.Recognize([points]) : undefined) ?? 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) => { switch (name) { case Gestures.Line: - if (cuspArray.length > 2) return undefined; + if (cuspArray.length > 2 && Score < 1) return undefined; // eslint-disable-next-line no-fallthrough case Gestures.Triangle: case Gestures.Rectangle: @@ -281,13 +275,17 @@ 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 { + ffView?._marqueeViewRef?.current?.setPreviewCursor?.(this._points[0].X, this._points[0].Y, false, false, undefined); + e.preventDefault(); } + this.primCreated(); this._points.length = 0; }; /** @@ -341,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]); @@ -543,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); @@ -553,97 +552,38 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil B.bottom += width / 2; B.width += width; B.height += width; - const fillColor = ActiveFillColor(); + const fillColor = ActiveInkFillColor(); const strokeColor = fillColor && fillColor !== 'transparent' ? fillColor : ActiveInkColor(); return [ this.props.children, - this._palette, - [ - this._strokes.map((l, i) => { - const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true); - return ( - <svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}> - {InteractionUtils.CreatePolyline( - l, - b.left, - b.top, - strokeColor, - width, - width, - 'miter', - 'round', - ActiveInkBezierApprox(), - 'none' /* ActiveFillColor() */, - ActiveArrowStart(), - ActiveArrowEnd(), - ActiveArrowScale(), - ActiveDash(), - 1, - 1, - this.InkShape as Gestures, - 'none', - 1.0, - false - )} - </svg> - ); - }), - this._points.length <= 1 ? null : ( - <svg key="svg" width={B.width} height={B.height} style={{ top: 0, left: 0, transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}> - {InteractionUtils.CreatePolyline( - this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })), - B.left, - B.top, - ActiveInkColor(), - width, - width, - 'miter', - 'round', - '', - 'none' /* ActiveFillColor() */, - ActiveArrowStart(), - ActiveArrowEnd(), - ActiveArrowScale(), - ActiveDash(), - 1, - 1, - this.InkShape as Gestures, - 'none', - 1.0, - false - )} - </svg> - ), - ], + this._points.length <= 1 ? null : ( + <svg key="svg" width={B.width} height={B.height} style={{ top: 0, left: 0, transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}> + {InteractionUtils.CreatePolyline( + this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })), + B.left, + B.top, + strokeColor, + width, + width, + 'miter', + 'round', + '', + 'none' /* ActiveFillColor() */, + ActiveInkArrowStart(), + ActiveInkArrowEnd(), + ActiveInkArrowScale(), + ActiveInkDash(), + 1, + 1, + SnappingManager.InkShape, + 'none', + 1.0, + false + )} + </svg> + ), ]; } - screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1); - return300 = () => 300; - @action - public openFloatingDoc = (doc: Doc) => { - this._clipboardDoc = ( - <DocumentView - Document={doc} - addDocument={undefined} - addDocTab={returnFalse} - pinToPres={emptyFunction} - removeDocument={undefined} - ScreenToLocalTransform={this.screenToLocalTransform} - PanelWidth={this.return300} - PanelHeight={this.return300} - isDocumentActive={returnFalse} - isContentActive={returnFalse} - renderDepth={0} - styleProvider={returnEmptyString} - containerViewPath={returnEmptyDocViewList} - focus={emptyFunction} - whenChildContentsActiveChanged={emptyFunction} - childFiltersByRanges={returnEmptyFilter} - childFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - /> - ); - }; @action public closeFloatingDoc = () => { @@ -654,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={{ @@ -690,10 +630,10 @@ ScriptingGlobals.add(function setPen(width: string, color: string, fill: string, SetActiveInkColor(color); GestureOverlay.Instance.SavedWidth = ActiveInkWidth(); SetActiveInkWidth(width); - SetActiveFillColor(fill); - SetActiveArrowStart(arrowStart); - SetActiveArrowStart(arrowEnd); - SetActiveDash(dash); + SetActiveInkFillColor(fill); + SetActiveInkArrowStart(arrowStart); + SetActiveInkArrowStart(arrowEnd); + SetActiveInkDash(dash); }); }); // eslint-disable-next-line prefer-arrow-callback |