aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/CurrentUserUtils.ts10
-rw-r--r--src/client/views/MainView.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx347
-rw-r--r--src/client/views/global/globalScripts.ts10
4 files changed, 214 insertions, 154 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b1673ff1c..3ee1b42aa 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -752,13 +752,13 @@ pie title Minerals in my tap water
return [
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
- { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType:"eraser", scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'},
+ { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {toolType:"activeEraserTool()"},
subMenu: [
{ title: "Stroke", toolTip: "Stroke Erase", btnType: ButtonType.ToggleButton, icon: "eraser", toolType:InkTool.StrokeEraser, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
- { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "xmarks-lines",toolType:InkTool.SegmentEraser,ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
+ { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "xmark",toolType:InkTool.SegmentEraser,ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
{ title: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkTool.RadiusEraser, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
- ]},
- { title: "Eraser Width", toolTip: "Eraser Width", btnType: ButtonType.NumberSliderButton, toolType: "eraserWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1, funcs: {hidden:"isRadiusEraser()" }},
+ ]},
+ { title: "Eraser Width", toolTip: "Eraser Width", btnType: ButtonType.NumberSliderButton, toolType: "eraserWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1, funcs: {hidden:"NotRadiusEraser()"}},
{ title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
@@ -1119,7 +1119,7 @@ pie title Minerals in my tap water
ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
ScriptingGlobals.add(function IsExploreMode() { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
-ScriptingGlobals.add(function isRadiusEraser() { return !(Doc.ActiveTool === InkTool.RadiusEraser); }, "is the eraser selected");
+ScriptingGlobals.add(function NotRadiusEraser() { return Doc.ActiveTool !== InkTool.RadiusEraser; }, "is the active tool anything but the radius eraser");
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 56db0c488..d0b3221b4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -361,6 +361,7 @@ export class MainView extends ObservableReactComponent<{}> {
fa.faDeleteLeft,
fa.faXmarksLines,
fa.faCircleXmark,
+ fa.faXmark,
fa.faExclamation,
fa.faFileAlt,
fa.faFileAudio,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eedee0b18..2e174be30 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,5 +1,7 @@
+import { inside } from '@turf/turf';
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
+import { validationResult } from 'express-validator';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
@@ -35,7 +37,7 @@ import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
import { GestureOverlay } from '../../GestureOverlay';
import { CtrlKey } from '../../GlobalKeyHandler';
-import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { ActiveEraserWidth, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
import { LightboxView } from '../../LightboxView';
import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
@@ -618,11 +620,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
case InkTool.StrokeEraser:
case InkTool.SegmentEraser:
- case InkTool.RadiusEraser:
this._batch = UndoManager.StartBatch('collectionErase');
this._eraserPts.length = 0;
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
break;
+ case InkTool.RadiusEraser:
+ this._batch = UndoManager.StartBatch('collectionErase');
+ this._eraserPts.length = 0;
+ setupMoveUpEvents(this, e, this.onRadiusEraserMove, this.onEraserUp, emptyFunction);
+ break;
case InkTool.None:
if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
this._hitCluster = this.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
@@ -734,7 +740,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
* Erases strokes by intersecting them with an invisible "eraser stroke".
* By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
* and deletes the original stroke.
- * However, if Shift is held, then no segmentation is done -- instead any intersected stroke is deleted in its entirety.
*/
@action
onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
@@ -750,10 +755,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
if (Doc.ActiveTool !== InkTool.StrokeEraser) {
this._eraserLock++;
- const segments =
- Doc.ActiveTool === InkTool.SegmentEraser
- ? this.segmentErase(intersect.inkView, intersect.t) // intersect.t is where the eraser intersected the ink stroke - want to remove the segment that starts at the intersection just before this t value and goes to the one just after it
- : this.radiusErase(intersect.inkView, { X: this._eraserPts[0][0], Y: this._eraserPts[0][1] }, currPoint);
+ const segments = this.segmentErase(intersect.inkView, intersect.t); // intersect.t is where the eraser intersected the ink stroke - want to remove the segment that starts at the intersection just before this t value and goes to the one just after it
segments?.forEach(segment =>
this.forceStrokeGesture(
e,
@@ -764,12 +766,44 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
setTimeout(() => this._eraserLock--);
}
// Lower ink opacity to give the user a visual indicator of deletion.
- intersect.inkView.layoutDoc.opacity = 0.2;
+ intersect.inkView.layoutDoc.opacity = 0;
intersect.inkView.layoutDoc.dontIntersect = true;
}
});
return false;
};
+
+ @action
+ onRadiusEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ const currPoint = { X: e.clientX, Y: e.clientY };
+ this._eraserPts.push([currPoint.X, currPoint.Y]);
+ this._eraserPts = this._eraserPts.slice(Math.max(0, this._eraserPts.length - 5));
+ if (this._eraserLock) return false; // bcz: should be fixed by putting it on a queue to be processed after the last eraser movement is processed.
+ const strokeMap: Map<DocumentView, number[]> = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint);
+ strokeMap.forEach((intersects, stroke) => {
+ if (!this._deleteList.includes(stroke)) {
+ this._deleteList.push(stroke);
+ SetActiveInkWidth(StrCast(stroke.Document.stroke_width?.toString()) || '1');
+ SetActiveInkColor(StrCast(stroke.Document.color?.toString()) || 'black');
+ this._eraserLock++;
+ // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
+ const segments = this.radiusErase(stroke, intersects.sort());
+ segments?.forEach(segment =>
+ this.forceStrokeGesture(
+ e,
+ GestureUtils.Gestures.Stroke,
+ segment.reduce((data, curve) => [...data, ...curve.points.map(p => stroke.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
+ )
+ );
+ setTimeout(() => this._eraserLock--);
+ }
+ // Lower ink opacity to give the user a visual indicator of deletion.
+ stroke.layoutDoc.opacity = 0;
+ stroke.layoutDoc.dontIntersect = true;
+ });
+ return false;
+ };
+
forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => {
this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
};
@@ -788,7 +822,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
createEraserOutline = (startInkCoordsIn: { X: number; Y: number }, endInkCoordsIn: { X: number; Y: number }) => {
- const radius = ActiveEraserWidth() / 2 + 3; // reduce values to avoid extreme radii
+ const radius = ActiveEraserWidth() + 3; // add 3 to avoid eraser being too thin
const c = 0.551915024494; // circle tangent length to side ratio
const movement = { x: endInkCoordsIn.X - startInkCoordsIn.X, y: endInkCoordsIn.Y - startInkCoordsIn.Y };
const moveLen = Math.sqrt(movement.x ** 2 + movement.y ** 2);
@@ -837,6 +871,39 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
/**
+ * Ray-tracing algorithm to determine whether a point is inside the eraser outline
+ * @param eraserOutline
+ * @param point
+ * @returns
+ */
+ insideEraserOutline = (eraserOutline: InkData, point: { X: number; Y: number }) => {
+ var isInside = false;
+ if (!isNaN(eraserOutline[0].X) && !isNaN(eraserOutline[0].Y)) {
+ var minX = eraserOutline[0].X, maxX = eraserOutline[0].X;
+ var minY = eraserOutline[0].Y, maxY = eraserOutline[0].Y;
+ for (var i = 1; i < eraserOutline.length; i++) {
+ const currPoint: {X: number, Y: number} = eraserOutline[i];
+ minX = Math.min(currPoint.X, minX);
+ maxX = Math.max(currPoint.X, maxX);
+ minY = Math.min(currPoint.Y, minY);
+ maxY = Math.max(currPoint.Y, maxY);
+ }
+
+ if (point.X < minX || point.X > maxX || point.Y < minY || point.Y > maxY) {
+ return false;
+ }
+
+ for (var i = 0, j = eraserOutline.length - 1; i < eraserOutline.length; j = i, i++) {
+ if ((eraserOutline[i].Y > point.Y) != (eraserOutline[j].Y > point.Y) &&
+ point.X < (eraserOutline[j].X - eraserOutline[i].X) * (point.Y - eraserOutline[i].Y) / (eraserOutline[j].Y - eraserOutline[i].Y) + eraserOutline[i].X ) {
+ isInside = !isInside;
+ }
+ }
+ }
+ return isInside;
+ };
+
+ /**
* Determines if the Eraser tool has intersected with an ink stroke in the current freeform collection.
* @returns an array of tuples containing the intersected ink DocumentView and the t-value where it was intersected
*/
@@ -880,94 +947,141 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
);
};
+ getRadiusEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => {
+ const eraserRadius = ActiveEraserWidth() + 3;
+ const eraserMin = { X: Math.min(lastPoint.X, currPoint.X) - eraserRadius, Y: Math.min(lastPoint.Y, currPoint.Y) - eraserRadius };
+ const eraserMax = { X: Math.max(lastPoint.X, currPoint.X) + eraserRadius, Y: Math.max(lastPoint.Y, currPoint.Y) + eraserRadius};
+ const strokeToTVals = new Map<DocumentView, number[]>();
+ const intersectingStrokes = this.childDocs
+ .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
+ .filter(inkView => inkView?.ComponentView instanceof InkingStroke) // filter to all inking strokes
+ .map(inkView => ({ inkViewBounds: inkView!.getBounds, inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
+ .filter(
+ ({ inkViewBounds }) =>
+ inkViewBounds && // bounding box of eraser segment and ink stroke overlap
+ eraserMin.X <= inkViewBounds.right &&
+ eraserMin.Y <= inkViewBounds.bottom &&
+ eraserMax.X >= inkViewBounds.left &&
+ eraserMax.Y >= inkViewBounds.top
+ );
+ console.log("itersectnig strokes", intersectingStrokes);
+ intersectingStrokes.forEach(({ inkStroke, inkView }) => {
+ const { inkData } = inkStroke.inkScaledData();
+ const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
+ const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
+ const eraserInkData = this.createEraserOutline(prevPointInkSpace, currPointInkSpace).inkData;
+ // add the ends of the stroke in as "intersections"
+ if (this.insideEraserOutline(eraserInkData, inkData[0])) {
+ strokeToTVals.set(inkView, [0]);
+ }
+ if (this.insideEraserOutline(eraserInkData, inkData[inkData.length - 1])) {
+ const inkList = strokeToTVals.get(inkView);
+ if (inkList !== undefined) {
+ inkList.push(Math.floor(inkData.length / 4) + 1);
+ } else {
+ strokeToTVals.set(inkView, [Math.floor(inkData.length / 4) + 1]);
+ }
+ }
+ for (var i = 0; i < inkData.length - 3; i += 4) {
+ // iterate over each segment of bezier curve
+ for (var j = 0; j < eraserInkData.length - 3; j += 4) {
+ const intersectCurve: Bezier = InkField.Segment(inkData, i); // other curve
+ const eraserCurve: Bezier = InkField.Segment(eraserInkData, j); // eraser curve
+ this.bintersects(intersectCurve, eraserCurve).forEach((val: string | number, k: number) => {
+ // Converting the Bezier.js Split type to a t-value number.
+ const t = +val.toString().split('/')[0];
+ if (k % 2 === 0) {
+ // here, add to the map
+ const inkList = strokeToTVals.get(inkView);
+ if (inkList !== undefined) {
+ const tValOffset = ActiveEraserWidth() / 1030; // to prevent tVals from being added when too close, but scaled by eraser width
+ const inList = inkList.some(val => Math.abs(val - (t + Math.floor(i / 4))) <= tValOffset);
+ if (!inList) {
+ inkList.push(t + Math.floor(i / 4));
+ }
+ } else {
+ strokeToTVals.set(inkView, [t + Math.floor(i / 4)]);
+ }
+ }
+ });
+ }
+ }
+ });
+ console.log("strokeToTVals", strokeToTVals);
+ return strokeToTVals;
+ };
+
+ /**
+ * Splits the passed in ink stroke at the intersection t values. Generally operates in pairs of t values, where
+ * the first t value is the start of the erased portion and the following t value is the end.
+ * @param ink the ink stroke DocumentView to split
+ * @param tVals all the t values to split the ink stroke at
+ * @returns a list of the new segments with the erased part removed
+ */
@action
- radiusErase = (ink: DocumentView, startPt: { X: number; Y: number }, screenEraserPt: { X: number; Y: number }): Segment[] => {
+ radiusErase = (ink: DocumentView, tVals: number[]): Segment[] => {
const segments: Segment[] = [];
- const startInkCoords = ink.ComponentView?.ptFromScreen?.(startPt);
- const inkCoords = ink.ComponentView?.ptFromScreen?.(screenEraserPt); // coordinates in ink space
- if (!inkCoords || !startInkCoords) return [];
-
- var eraseSegment: Segment = []; // for eraser visualization
const inkStroke = ink?.ComponentView as InkingStroke;
-
- const eraserStroke: InkData = inkStroke.splitByEraser(startInkCoords, inkCoords).inkData;
- const strokeToTVals: Map<InkingStroke, number[]> = new Map<InkingStroke, number[]>();
- for (var i = 0; i < eraserStroke.length - 3; i += 4) {
- eraseSegment.push(InkField.Segment(eraserStroke, i)); // for eraser visualization
- this.getOtherInkIntersections(ink, i, eraserStroke, strokeToTVals);
+ const { inkData } = inkStroke.inkScaledData();
+ var currSegment: Segment = [];
+ if (tVals.length % 2 !== 0) { // should always have even tVals
+ for (var i = 0; i < inkData.length - 3; i +=4) {
+ currSegment.push(InkField.Segment(inkData, i));
+ }
+ if (currSegment.length > 0) {
+ segments.push(currSegment);
+ return segments;
+ }
}
- strokeToTVals.forEach((tVals, inkStroke) => {
- var segment1: Segment = [];
- var segment2: Segment = [];
- const { inkData } = inkStroke.inkScaledData();
- tVals.sort();
- console.log('TVALS', inkStroke, tVals);
- var hasSplit = false;
- var continueErasing = false;
-
- // below is curve splitting logic
- if (tVals.length) {
- for (var i = 0; i < inkData.length - 3; i += 4) {
- const inkSegment: Bezier = InkField.Segment(inkData, i);
- const currCurveT = Math.floor(i / 4);
+ var continueErasing = false;
+ var firstSegment: Segment = [];
+ // early return if nothing to split on
+ if (tVals.length === 0 || (tVals.length === 1 && tVals[0] === 0)) {
+ return segments;
+ }
- if (tVals.length === 2) {
- if (tVals[0] > currCurveT && tVals[0] < currCurveT + 1) {
- segment1.push(inkSegment.split(0, tVals[0] - currCurveT));
+ for (var i = 0; i < inkData.length - 3; i += 4) {
+ const currCurveT = Math.floor(i/4);
+ const inkBezier: Bezier = InkField.Segment(inkData, i);
+ const segmentTs = tVals.filter(t => t >= currCurveT && t < currCurveT + 1);
+
+ if (segmentTs.length > 0) {
+ for (var j = 0; j < segmentTs.length; j++) {
+ // if the first end of the segment is within the eraser
+ if (segmentTs[j] === 0 ) {
+ continueErasing = true;
+ } else if (segmentTs[j] === Math.floor(inkData.length / 4) + 1) {
+ break;
+ }else {
+ if (!continueErasing) {
+ currSegment.push(inkBezier.split(0, segmentTs[j] - currCurveT));
continueErasing = true;
- if (tVals[1] > currCurveT && tVals[1] < currCurveT + 1) {
- segment2.push(inkSegment.split(tVals[1] - currCurveT, 1));
- continueErasing = false;
- hasSplit = true;
- }
- } else if (tVals[1] > currCurveT && tVals[1] < currCurveT + 1) {
- segment2.push(inkSegment.split(tVals[1] - currCurveT, 1));
- continueErasing = false;
- hasSplit = true;
- } else if (!continueErasing) {
- if (hasSplit) {
- segment2.push(inkSegment);
- } else {
- segment1.push(inkSegment);
- }
- }
- } else if (tVals.length === 1) {
- if (tVals[0] > currCurveT && tVals[0] < currCurveT + 1) {
- // this heuristic for determine which segment to keep is not quite right even though it will work most of the time.
- // We should really store the eraser intersection normal in getOtherInkIntersections,
- // and then test its dot product with the tangent of the stroke at the intersection point.
- // if the dot product is positive, then erase the first part of the stroke, otherwise the second.
- const leftDist = Utils.ptDistance({ x: inkCoords.X, y: inkCoords.Y }, inkSegment.points.lastElement());
- const rightDist = Utils.ptDistance({ x: inkCoords.X, y: inkCoords.Y }, inkSegment.points[0]);
- const splits = inkSegment.split(tVals[0] - currCurveT);
- if (leftDist < rightDist) {
- // if it's on the first end
- segment1.push(splits.left);
- hasSplit = true;
- } else {
- segment1.push(splits.right);
- hasSplit = true;
- }
} else {
- if (tVals[0] < Math.floor(inkData.length / 4) - tVals[0] && hasSplit) {
- segment1.push(inkSegment);
- } else if (tVals[0] >= Math.floor(inkData.length / 4) - tVals[0] && !hasSplit) {
- segment1.push(inkSegment);
+ continueErasing = false;
+ if (currSegment.length > 0) {
+ segments.push(currSegment);
+ if (firstSegment.length === 0) {
+ firstSegment = currSegment;
+ }
+ currSegment = [];
}
+ currSegment.push(inkBezier.split(segmentTs[j] - currCurveT, 1));
}
}
}
+ } else {
+ if (!continueErasing) { // push the bezier piece if not in the eraser circle
+ currSegment.push(inkBezier);
+ }
}
- if (segment1.length && (Math.abs(segment1[0].points[0].x - segment1[0].points.lastElement().x) > 0.5 || Math.abs(segment1[0].points[0].y - segment1[0].points.lastElement().y) > 0.5)) {
- segments.push(segment1);
- }
- if (segment2.length && (Math.abs(segment2[0].points[0].x - segment2[0].points.lastElement().x) > 0.5 || Math.abs(segment2[0].points[0].y - segment2[0].points.lastElement().y) > 0.5)) {
- segments.push(segment2);
+ }
+ if (currSegment.length > 0) {
+ if (InkingStroke.IsClosed(inkData)) {
+ currSegment = currSegment.concat(firstSegment);
}
- });
-
- segments.push(eraseSegment); // for eraser visualization
+ segments.push(currSegment);
+ }
return segments;
};
@@ -993,7 +1107,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const inkSegment: Bezier = InkField.Segment(inkData, i);
var currIntersects = this.getInkIntersections(i, ink, inkSegment).sort();
// get current segments intersections (if any) and add the curve index
- currIntersects = currIntersects.map(tVal => tVal + Math.floor(i / 4));
+ currIntersects = currIntersects.filter(tVal => tVal > 0 && tVal < 1).map(tVal => tVal + Math.floor(i / 4));
if (currIntersects.length) {
intersections = [...intersections, ...currIntersects];
for (var j = 0; j < currIntersects.length; j++) {
@@ -1002,19 +1116,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
- // for closed curve bookkeeping
var isClosedCurve = false;
- if (intersections[0] === 0) {
+ if (InkingStroke.IsClosed(inkData)) {
isClosedCurve = true;
-
- // now reduce intersections list to the actual intersections because closed curves have additional ones with itself
- const indices = intersections.map((value, index) => (value > 0.0001 && value < Math.floor(inkData.length / 4) ? index : -1)).filter(index => index !== -1);
-
- // Filter intersections and segmentIndexes based on validIndices
- intersections = indices.map(index => intersections[index]);
- segmentIndexes = indices.map(index => segmentIndexes[index]);
- // intersections = intersections.slice(1, intersections.length ); // take the 0 intersection out
- // segmentIndexes = segmentIndexes.slice(1, segmentIndexes.length); // same for indexes
}
if (intersections.length) {
@@ -1068,7 +1172,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
} else if (splitSegment1 === -1) {
- // case where left end is erased
+ // case where first end is erased
if (currCurveT === splitSegment2) {
segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1));
hasSplit = true;
@@ -1081,15 +1185,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
} else {
- // case where right end is erased
+ // case where last end is erased
if (currCurveT === segmentIndexes[0] && isClosedCurve) {
segment1.push(inkSegment.split(intersections[0] - currCurveT, 1));
hasSplit = true;
} else if (currCurveT === splitSegment1) {
segment1.push(inkSegment.split(0, intersections[closestTs[0]] - currCurveT));
- hasSplit = false; // although this is not technically true, we set to false so the segments after don't get added
- // } else if (isClosedCurve && currCurveT === segmentIndexes[1]) {
- // segment1.push(inkSegment.split(intersections[0] - currCurveT, 1));
+ hasSplit = true;
} else {
if ((isClosedCurve && hasSplit) || (!isClosedCurve && !hasSplit)) {
segment1.push(inkSegment);
@@ -1099,6 +1201,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
+ if (isClosedCurve && segment1.length > 0 && segment2.length > 0) {
+ segment2 = segment2.concat(segment1);
+ segment1 = [];
+ }
+
// push 1 or both segments if they are not empty
if (segment1.length && (Math.abs(segment1[0].points[0].x - segment1[0].points.lastElement().x) > 0.5 || Math.abs(segment1[0].points[0].y - segment1[0].points.lastElement().y) > 0.5)) {
segments.push(segment1);
@@ -1203,46 +1310,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return tVals;
};
- /**
- * For radius erase, a function to find an eraser's intersections with a single ink stroke
- * @param otherInkDocVIew the ink document that is being erased
- * @param i the index of the eraser's segment
- * @param points the eraser's ink data points
- * @param strokeToTVals Map of InkingStroke to the tVals of its intersections with the eraser
- * @returns
- */
- getOtherInkIntersections = (otherInkDocView: DocumentView, i: number, points: InkData, strokeToTVals: Map<InkingStroke, number[]>): Map<InkingStroke, number[]> => {
- this.childDocs
- .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
- .forEach(doc => {
- // InkingStroke of other ink strokes
- const otherInk = otherInkDocView.ComponentView as InkingStroke;
- // ink Data of other ink strokes
- const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
- for (var j = 0; j < otherInkData.length - 3; j += 4) {
- const curve: Bezier = InkField.Segment(points, i); // eraser curve
- const otherCurve: Bezier = InkField.Segment(otherInkData, j); // other curve
- this.bintersects(otherCurve, curve).forEach((val: string | number, k: number) => {
- // Converting the Bezier.js Split type to a t-value number.
- const t = +val.toString().split('/')[0];
- if (k % 2 === 0) {
- // here, add to the map
- const inkList = strokeToTVals.get(otherInk);
- if (inkList !== undefined) {
- const inList = inkList.some(val => Math.abs(val - (t + Math.floor(j / 4))) <= 0.01);
- if (!inList) {
- inkList.push(t + Math.floor(j / 4));
- }
- } else {
- strokeToTVals.set(otherInk, [t + Math.floor(j / 4)]);
- }
- }
- });
- }
- });
- return strokeToTVals;
- };
-
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 5c5f8de03..19741e2e0 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -341,10 +341,7 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
Doc.UserDoc().activeEraserTool = tool;
}
// pen or eraser
- if (Doc.ActiveTool === tool && tool === InkTool.Eraser) {
- Doc.ActiveTool = InkTool.SegmentEraser;
- console.log("erase click twice")
- } else if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) {
+ if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) {
Doc.ActiveTool = InkTool.None;
} else {
Doc.ActiveTool = tool as any;
@@ -362,11 +359,6 @@ ScriptingGlobals.add(function activeEraserTool() {
return StrCast(Doc.UserDoc().activeEraserTool, InkTool.StrokeEraser);
}, 'returns the current eraser tool');
-// ScriptingGlobals.add(function setEraserProperty(option: 'eraseWidth', value: any, checkResult?: boolean) {
-// setInk: (doc: Doc) => (doc[DocData].eraserWidth = NumCast(value))
-// InkingStroke.setEraserRadius(NumCast(value));
-// })
-
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', value: any, checkResult?: boolean) {
const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc();