diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/GestureOverlay.tsx | 7 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 136 | ||||
-rw-r--r-- | src/fields/InkField.ts | 6 |
3 files changed, 117 insertions, 32 deletions
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 99829139f..e2193c9ac 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -490,18 +490,11 @@ export class GestureOverlay extends Touchable { } @action - checkInkIntersection = (point: PointData) => { - - } - - @action onPointerDown = (e: React.PointerEvent) => { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); - } else if (InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) { - this.checkInkIntersection({ X: e.clientX, Y: e.clientY }); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6f79c5eab..ce4acb7ad 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -5,7 +5,7 @@ import { DateField } from "../../../../fields/DateField"; import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas"; import { Id } from "../../../../fields/FieldSymbols"; -import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField"; +import { InkData, InkField, InkTool, PointData, Intersection } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { ObjectField } from "../../../../fields/ObjectField"; import { RichTextField } from "../../../../fields/RichTextField"; @@ -52,6 +52,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { ColorScheme } from "../../../util/SettingsManager"; import { Bezier } from "bezier-js"; +import { GestureOverlay } from "../../GestureOverlay"; export const panZoomSchema = createSchema({ _panX: "number", @@ -647,19 +648,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; if (CurrentUserUtils.SelectedTool !== InkTool.None) { this._currPoint = { X: e.clientX, Y: e.clientY }; - const intersections: { [id: number]: DocumentView[] } = this.getEraserIntersections(); - if (intersections) { - for (const t in intersections) { - const inks = intersections[t]; - inks.forEach(ink => { - if (!this._deleteList.includes(ink)) { - this._deleteList.push(ink); - // Lowering ink opacity to give the user a visual indicator of deletion. - ink.Document.opacity = 0.5; - } - }); - } - } + const eraserIntersections: Intersection[] = this.getEraserIntersections(); + if (eraserIntersections) this.eraseInkStrokes(eraserIntersections); } if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { if (this.props.isContentActive(true)) e.stopPropagation(); @@ -676,12 +666,79 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } + @action + eraseInkStrokes = (eraserIntersections: Intersection[]) => { + eraserIntersections.forEach(intersect => { + var t1, t2; + var distT1: number, distT2: number; + distT1 = distT2 = Number.MAX_SAFE_INTEGER; + const t = intersect.t; + const ink = intersect.ink; + const ctrlPoints = Cast(ink?.dataDoc[this.props.fieldKey], InkField)?.inkData ?? []; + for (var i = 0; i < ctrlPoints.length - 3; i += 4) { + const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]]; + const curve = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))); + const inkIntersections: number[] = this.getInkIntersections(curve); + inkIntersections.forEach(currentT => { + const diff = t - currentT; + if (diff > 0 && diff < distT1) { + distT1 = diff; + t1 = currentT; + } else if (diff < 0 && diff < distT2) { + distT2 = -diff; + t2 = currentT; + } + }); + + // Normal case --> deletion of entire stroke + if (!t1 && !t2) { + if (!this._deleteList.includes(ink)) { + this._deleteList.push(ink); + // Lowering ink opacity to give the user a visual indicator of deletion. + ink.Document.opacity = 0.5; + } + } else if (t1 && !t2) { + const splitCurve = curve.split(t1); + const leftInkData: PointData[] = splitCurve.left.points.map(p => ({ X: p.x, Y: p.y })); + GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, leftInkData); + if (!this._deleteList.includes(ink)) { + this._deleteList.push(ink); + // Lowering ink opacity to give the user a visual indicator of deletion. + ink.Document.opacity = 0.5; + } + } else if (!t1 && t2) { + const splitCurve = curve.split(t2); + const rightInkData: PointData[] = splitCurve.right.points.map(p => ({ X: p.x, Y: p.y })); + GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, rightInkData); + if (!this._deleteList.includes(ink)) { + this._deleteList.push(ink); + // Lowering ink opacity to give the user a visual indicator of deletion. + ink.Document.opacity = 0.5; + } + } else if (t1 && t2) { + const splitCurve1 = curve.split(t1); + const leftInkData: PointData[] = splitCurve1.left.points.map(p => ({ X: p.x, Y: p.y })); + GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, leftInkData); + const splitCurve2 = curve.split(t2); + const rightInkData: PointData[] = splitCurve2.right.points.map(p => ({ X: p.x, Y: p.y })); + GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, rightInkData); + + if (!this._deleteList.includes(ink)) { + this._deleteList.push(ink); + // Lowering ink opacity to give the user a visual indicator of deletion. + ink.Document.opacity = 0.5; + } + } + } + }); + } + /** * Determines if the Eraser tool has intersected with an ink stroke in the current freeform collection. - * @returns A dictionary mapping the t-value intersection of the eraser with a list of ink DocumentViews. + * @returns A dictionary mapping the t-value intersection of the eraser with the corresponding ink DocumentView. */ - getEraserIntersections = (): { [id: number]: DocumentView[] } => { - const intersections: { [id: number]: DocumentView[] } = {}; + getEraserIntersections = (): Intersection[] => { + const intersections: Intersection[] = []; this.childDocs .filter(doc => doc.type === DocumentType.INK) .forEach(doc => { @@ -694,14 +751,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const currPointInkSpace = inkView?.ComponentView?.ptFromScreen?.(this._currPoint); if (prevPointInkSpace && currPointInkSpace) { const curve = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))); - const t = curve.intersects( - { - p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, - p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y } - }); - if (inkView && t) { - t.forEach(val => { - intersections[+t[0]] ? intersections[+t[0]].push(inkView) : intersections[+t[0]] = [inkView]; + const intersects = curve.intersects({ + p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, + p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y } + }); + if (inkView && intersects) { + intersects.forEach(t => { + intersections.push({ t: +t.toString(), ink: inkView }); }); } } @@ -710,6 +766,36 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return intersections; } + getInkIntersections = (curve: Bezier): number[] => { + const intersections: number[] = []; + const selfIntersect = curve.selfintersects(); + if (selfIntersect) { + selfIntersect.forEach(t => { + intersections.push(+t.toString()); + }); + } + + this.childDocs + .filter(doc => doc.type === DocumentType.INK) + .forEach(doc => { + const currInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView); + const currCtrls = Cast(currInk?.dataDoc[this.props.fieldKey], InkField)?.inkData ?? []; + for (var i = 0; i < currCtrls.length - 3; i += 4) { + const currArray = [currCtrls[i], currCtrls[i + 1], currCtrls[i + 2], currCtrls[i + 3]]; + const currCurve = new Bezier(currArray.map(p => ({ x: p.X, y: p.Y }))); + const intersect = curve.intersects(currCurve); + if (intersect) { + intersect.forEach(t => { + intersections.push(+this.parseTValue(t.toString())); + }); + } + } + }); + return intersections; + } + + parseTValue = (t: string) => t.split("/", 1)[0]; + handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => { if (!e.cancelBubble) { const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index f16e143d8..1d50b5e0d 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -3,6 +3,7 @@ import { Scripting } from "../client/util/Scripting"; import { Deserializable } from "../client/util/SerializationHelper"; import { Copy, ToScriptString, ToString } from "./FieldSymbols"; import { ObjectField } from "./ObjectField"; +import { DocumentView } from "../client/views/nodes/DocumentView"; // Helps keep track of the current ink tool in use. export enum InkTool { @@ -20,6 +21,11 @@ export interface PointData { Y: number; } +export interface Intersection { + t: number; + ink: DocumentView; +} + // Defines an ink as an array of points. export type InkData = Array<PointData>; |