From 33cb02498a330ede6a90e67bdaf0ff56aed21819 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Tue, 12 Mar 2024 16:26:00 -0400 Subject: i have an error --- .../collectionFreeForm/CollectionFreeFormView.tsx | 181 +++++++++++++++++++-- 1 file changed, 171 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e4c71a086..70b21309e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,3 +1,4 @@ +import { findSegment } from '@turf/turf'; import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; @@ -739,7 +740,25 @@ export class CollectionFreeFormView extends CollectionSubView segment.map(pt => pt)) + // segments[0][3].x === segments[1][0].x + + // 3 == "3" true + // 3 === "3" falsen !== + + // for (var i = 0; i < segments.length; i ++) { + // if (segments[i][0].points.lastElement().x === segments[i+1][0].points.lastElement().x && segments[i][0].points.lastElement().y === segments[i+1][0].points.lastElement().y) { + // segments[i][0].points.pop(); + // segments[i][0].points = segments[i][0].points.concat(segments[i+1][0].points); + // segments.splice(i+1, 1); + // } + // i++; + // } segments.forEach(segment => this.forceStrokeGesture( e, @@ -750,7 +769,7 @@ export class CollectionFreeFormView extends CollectionSubView this._eraserLock--); } // Lower ink opacity to give the user a visual indicator of deletion. - intersect.inkView.layoutDoc.opacity = 0.5; + intersect.inkView.layoutDoc.opacity = 0.0; intersect.inkView.layoutDoc.dontIntersect = true; } }); @@ -796,12 +815,14 @@ export class CollectionFreeFormView extends CollectionSubView { - const { inkData } = inkStroke.inkScaledData(); + const { inkData } = inkStroke.inkScaledData(); // get bezier curve as set of control points // Convert from screen space to ink space for the intersection. const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); const currPointInkSpace = inkStroke.ptFromScreen(currPoint); for (var i = 0; i < inkData.length - 3; i += 4) { + // iterate over each segment of bezier curve const rawIntersects = InkField.Segment(inkData, i).intersects({ + // segment's are indexed by 0, 4, 8, // compute all unique intersections p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, @@ -825,9 +846,106 @@ export class CollectionFreeFormView extends CollectionSubView { + // const segments: Segment[] = []; + // var segment: Segment = []; + // var segment2: Segment = []; + // var startSegmentT = 0; + // const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); + // This iterates through all segments of the curve and splits them where they intersect another curve. + // if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted) + + // var hasSplit = false; + // var continueErasing = false; + // for (var i = 0; i < inkData.length - 3; i += 4) { + // const inkSegment: Bezier = InkField.Segment(inkData, i); + // const tVals: number[] = this.getInkIntersections(i, ink, inkSegment).sort(); + // console.log("TVALS", tVals); + // if (tVals.length) { + // if ((!hasSplit) && ((excludeT <= tVals[0]) || + // (excludeT > tVals[0] && excludeT < tVals.lastElement()) || continueErasing)) { + + // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); + // if (closestTs[0] != -1 && closestTs[1] != -1) { + // segment1.push(inkSegment.split(0, closestTs[0])); + // segment2.push(inkSegment.split(closestTs[1]).right); + // } else if (closestTs[0] == -1) { + // segment2.push(inkSegment.split(closestTs[1]).right); + // continueErasing = !continueErasing; + // } else { + // segment1.push(inkSegment.split(0, closestTs[0])); + // continueErasing = !continueErasing; + // } + // if (!continueErasing) { + // hasSplit = true; + // } + // // segment1 = []; + // // segment2 = []; + // } + // else { + // if (hasSplit) { + // segment2.push(inkSegment); + // } else { + // segment1.push(inkSegment); + // } + // } + // } else if (hasSplit) { + // segment2.push(inkSegment); + // } else { + // continueErasing = true; + // } + // } + + // 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 (excludeT < startSegmentT || excludeT > inkData.length / 4) { + // segment1.length && segments.push(segment1); + // } + // const tVals = [] + // for (var i = 0; i < inkData.length - 3; i += 4) { + // const inkSegment = InkField.Segment(inkData, i); + // // Getting all t-value intersections of the current curve with all other curves. + // tVals.push(this.getInkIntersections(i, ink, inkSegment)); + // tVals.sort(); + + // if (tVals.length) { + // const docCurveTVal = tVals.lastElement() + Math.floor(i / 4); + // if (excludeT > docCurveTVal) { + // continue; + // } else { + // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); + // segment.push(inkSegment.split(0, closestTs[0])); + // if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) { + // segments.push(segment); + // } + // } + + // const closestT = this.getClosestT(tVals, excludeT, 0, tVals.length - 1); + // if (closestT != undefined) { + // var startSegmentT = closestT[0] + // const localStartTVal = startSegmentT - Math.floor(i / 4); + // segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, startSegmentT)) + // if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) { + // segments.push(segment); + // } + // const split = inkSegment.split(closestT[1]).right; + // if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) { + // segments.push([split]); + // } + // } + // } else { + // segment.push(inkSegment); + // segments.push(segment); + // } + // } const segments: Segment[] = []; - var segment: Segment = []; + var segment1: Segment = []; + var segment2: Segment = []; var startSegmentT = 0; + var hasSplit = false; const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); // This iterates through all segments of the curve and splits them where they intersect another curve. // if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted) @@ -840,23 +958,66 @@ export class CollectionFreeFormView extends CollectionSubView docCurveTVal) { const localStartTVal = startSegmentT - Math.floor(i / 4); - t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); - segment.length && segments.push(segment); + if (!hasSplit) { + t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment1.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); + } else { + t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment2.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); + } + // if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) segments.push(segment); } // start a new segment from the intersection t value - segment = tVals.length - 1 === index ? [inkSegment.split(t).right] : []; + if (tVals.length - 1 === index) { + const split = inkSegment.split(t).right; + if (!hasSplit) { + if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment1.push(split); + } else { + if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment2.push(split); + } + } startSegmentT = docCurveTVal; }); } else { - segment.push(inkSegment); + if (!hasSplit) { + segment1.push(inkSegment); + } else { + segment2.push(inkSegment); + } } } - if (excludeT < startSegmentT || excludeT > inkData.length / 4) { - segment.length && segments.push(segment); + 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); } return segments; }; + getClosestTs = (tVals: number[], excludeT: number, startIndex: number, endIndex: number): number[] => { + if (tVals[startIndex] >= excludeT) { + return [-1, tVals[startIndex]]; + } + else if (tVals[endIndex] < excludeT) { + return [tVals[endIndex], -1]; + } + else { + const mid = Math.floor(startIndex + endIndex / 2); + if (excludeT >= tVals[mid]) { + if (mid + 1 <= endIndex && tVals[mid + 1] > excludeT) { + return [tVals[mid], tVals[mid + 1]]; + } else { + return this.getClosestTs(tVals, excludeT, mid + 1, endIndex); + } + } else { + if (mid - 1 <= startIndex && tVals[mid - 1] < excludeT) { + return [tVals[mid - 1], tVals[mid]]; + } else { + return this.getClosestTs(tVals, excludeT, startIndex, mid - 1); + } + } + } + }; + // for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection // call in a test for linearity bintersects = (curve: Bezier, otherCurve: Bezier) => { -- cgit v1.2.3-70-g09d2 From f4158b1a3249e500a1031d9edb91f6769777c5d2 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Tue, 19 Mar 2024 16:27:29 -0400 Subject: functional erase code complete --- .../collectionFreeForm/CollectionFreeFormView.tsx | 357 ++++++++++++++------- 1 file changed, 239 insertions(+), 118 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 70b21309e..76e1646e2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -846,171 +846,292 @@ export class CollectionFreeFormView extends CollectionSubView { - // const segments: Segment[] = []; - // var segment: Segment = []; - // var segment2: Segment = []; - // var startSegmentT = 0; - // const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); + + const segments: Segment[] = []; + var segment1: Segment = []; + var segment2: Segment = []; + const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); // This iterates through all segments of the curve and splits them where they intersect another curve. // if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted) - // var hasSplit = false; - // var continueErasing = false; + // find all t values of the curve's intersections + var intersections: number[] = []; + var segmentIndexes: number[] = []; + for (var i = 0; i < inkData.length - 3; i += 4) { + 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)); + if (currIntersects.length) { + intersections = [...intersections, ...currIntersects]; + // segmentIndexes is to keep track of the segment index that corresponds to an intersection + for (var j = 0; j < currIntersects.length; j++) { + segmentIndexes.push(Math.floor(i / 4)); + } + } + } + + if (intersections.length) { + // this is the indexes of the closest Ts + const closestTs = this.getClosestTs(intersections, excludeT, 0, intersections.length - 1); + + // find the segments that need to be split + var splitSegment1 = -1; // stays -1 if left end is deleted + var splitSegment2 = -1; // stays -1 if right end is deleted + if (closestTs[0] !== -1 && closestTs[1] !== -1) { + // if not on the ends + splitSegment1 = segmentIndexes[closestTs[0]]; + splitSegment2 = segmentIndexes[closestTs[1]]; + } else if (closestTs[0] === -1) { // for a curve left of intersection + splitSegment2 = segmentIndexes[closestTs[1]]; + } else { // for a curve right of intersection + splitSegment1 = segmentIndexes[closestTs[0]]; + } + + // so here splitSegment1 and splitSegment2 will be the index(es) of the segment(s) to split + + var hasSplit = false; + var continueErasing = false; + // loop through segments again and split if they match the split segments + for (var i = 0; i < inkData.length - 3; i += 4) { + const currCurveT = Math.floor(i / 4); + const inkSegment: Bezier = InkField.Segment(inkData, i); + + // case where the current curve is the first to split + if (splitSegment1 !== -1 && splitSegment2 !== -1) { + if (splitSegment1 === splitSegment2 && splitSegment1 === currCurveT) { + // if it's the same segment + segment1.push(inkSegment.split(0, intersections[closestTs[0]] - currCurveT)); + segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1)); + hasSplit = true; + } else if (splitSegment1 === currCurveT) { + segment1.push(inkSegment.split(0, intersections[closestTs[0]] - currCurveT)); + continueErasing = true; + } else if (splitSegment2 === currCurveT) { + segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1)); + continueErasing = false; + hasSplit = true; + } else { + if (!continueErasing && !hasSplit) { + // segment doesn't get pushed if continueErasing is true + segment1.push(inkSegment); + } else if (!continueErasing && hasSplit) { + segment2.push(inkSegment); + } + } + } else if (splitSegment1 === -1) { // case where left end is erased + if (currCurveT === splitSegment2) { + segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1)); + hasSplit = true; + } else { + if (hasSplit) { + segment2.push(inkSegment); + } + } + } else { // case where right end is erased + if (currCurveT === splitSegment1) { + segment1.push(inkSegment.split(0, intersections[closestTs[0]] - currCurveT)); + hasSplit = true; + } else { + if (!hasSplit) { + segment1.push(inkSegment); + } + } + } + } + } + + // 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); + } + 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); + } + + return segments; + + // var deletedSegments = []; // for (var i = 0; i < inkData.length - 3; i += 4) { + // const segmentStartT = Math.floor(i/4); // const inkSegment: Bezier = InkField.Segment(inkData, i); // const tVals: number[] = this.getInkIntersections(i, ink, inkSegment).sort(); - // console.log("TVALS", tVals); - // if (tVals.length) { - // if ((!hasSplit) && ((excludeT <= tVals[0]) || - // (excludeT > tVals[0] && excludeT < tVals.lastElement()) || continueErasing)) { + // if (excludeT > segmentStartT && excludeT < segmentStartT + 1) { + // if (tVals.length) { // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); // if (closestTs[0] != -1 && closestTs[1] != -1) { // segment1.push(inkSegment.split(0, closestTs[0])); // segment2.push(inkSegment.split(closestTs[1]).right); + // deletedSegments.push(inkSegment.split(closestTs[0], closestTs[1])); // } else if (closestTs[0] == -1) { // segment2.push(inkSegment.split(closestTs[1]).right); - // continueErasing = !continueErasing; + // deletedSegments.push(inkSegment.split(closestTs[1]).left); + // if (excludeT > tVals[0]) { + // continueErasing = !continueErasing; + // } // } else { - // segment1.push(inkSegment.split(0, closestTs[0])); + // continueErasing ? segment2.push(inkSegment.split(closestTs[0]).right) : segment1.push(inkSegment.split(0, closestTs[0])); + // continueErasing ? deletedSegments.push(inkSegment.split(closestTs[0]).left) : deletedSegments.push(inkSegment.split(closestTs[0]).right); // continueErasing = !continueErasing; // } // if (!continueErasing) { // hasSplit = true; // } - // // segment1 = []; - // // segment2 = []; + // } else if (hasSplit) { + // segment2.push(inkSegment); + // } else { + // continueErasing = true; // } - // else { - // if (hasSplit) { - // segment2.push(inkSegment); - // } else { - // segment1.push(inkSegment); - // } - // } - // } else if (hasSplit) { - // segment2.push(inkSegment); // } else { - // continueErasing = true; + // hasSplit ? segment1.push(inkSegment) : segment2.push(inkSegment); // } - // } + + // if (tVals.length) { + // if (!hasSplit) { + // if (excludeT <= tVals[0] || (excludeT > tVals[0] && excludeT < tVals.lastElement()) || excludeT > tVals.lastElement() || continueErasing) { + // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); + // if (closestTs[0] != -1 && closestTs[1] != -1) { + // segment1.push(inkSegment.split(0, closestTs[0])); + // segment2.push(inkSegment.split(closestTs[1]).right); + // deletedSegments.push(inkSegment.split(closestTs[0], closestTs[1])); + // } else if (closestTs[0] == -1) { + // segment2.push(inkSegment.split(closestTs[1]).right); + // deletedSegments.push(inkSegment.split(closestTs[1]).left); + // if (excludeT > tVals[0]) { + // continueErasing = !continueErasing; + // } + // } else { + // continueErasing ? segment2.push(inkSegment.split(closestTs[0]).right) : segment1.push(inkSegment.split(0, closestTs[0])); + // continueErasing ? deletedSegments.push(inkSegment.split(closestTs[0]).left) : deletedSegments.push(inkSegment.split(closestTs[0]).right); + // continueErasing = !continueErasing; + // } + // if (!continueErasing) { + // hasSplit = true; + // } + // } else { + // if (hasSplit) { + // segment2.push(inkSegment); + // } else { + // segment1.push(inkSegment); + // } + // } + // } else if (excludeT > tVals[0] && excludeT < tVals.lastElement()) { + // // here we know hasSplit is true (but it shouldnt be!) + // segment1 = [...deletedSegments]; + // deletedSegments = []; + // hasSplit = false; + // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); + // if (closestTs[0] != -1 && closestTs[1] != -1) { + // segment1.push(inkSegment.split(0, closestTs[0])); + // segment2.push(inkSegment.split(closestTs[1]).right); + // } else if (closestTs[0] == -1) { + // segment2.push(inkSegment.split(closestTs[1]).right); + // if (excludeT > tVals[0]) { + // continueErasing = !continueErasing; + // } + // } + // } else { + // segment2.push(inkSegment); + // } + // } else if (hasSplit) { + // segment2.push(inkSegment); + // } else { + // continueErasing = true; + // } - // 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 (excludeT < startSegmentT || excludeT > inkData.length / 4) { - // segment1.length && segments.push(segment1); + + + + // if (hasSplit && i + 4 >= lastIteration && ((excludeT > tVals[0] && excludeT < tVals.lastElement()) || (excludeT > tVals.lastElement()))) { + // // here we know hasSplit is true (but it shouldnt be!) + // segment1 = [...deletedSegments]; + // deletedSegments = []; + // hasSplit = false; + // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); + // if (closestTs[0] != -1 && closestTs[1] != -1) { + // segment1.push(inkSegment.split(0, closestTs[0])); + // segment2.push(inkSegment.split(closestTs[1]).right); + // } else if (closestTs[0] == -1) { + // segment2.push(inkSegment.split(closestTs[1]).right); + // if (excludeT > tVals[0]) { + // continueErasing = !continueErasing; + // } + // } + // } // } - // const tVals = [] + + + // const segments: Segment[] = []; + // var segment1: Segment = []; + // var segment2: Segment = []; + // var startSegmentT = 0; + // var hasSplit = false; + // const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); + // // This iterates through all segments of the curve and splits them where they intersect another curve. + // // if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted) // for (var i = 0; i < inkData.length - 3; i += 4) { // const inkSegment = InkField.Segment(inkData, i); // // Getting all t-value intersections of the current curve with all other curves. - // tVals.push(this.getInkIntersections(i, ink, inkSegment)); - // tVals.sort(); - - // if (tVals.length) { - // const docCurveTVal = tVals.lastElement() + Math.floor(i / 4); - // if (excludeT > docCurveTVal) { - // continue; - // } else { - // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); - // segment.push(inkSegment.split(0, closestTs[0])); - // if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) { - // segments.push(segment); - // } - // } - - // const closestT = this.getClosestT(tVals, excludeT, 0, tVals.length - 1); - // if (closestT != undefined) { - // var startSegmentT = closestT[0] - // const localStartTVal = startSegmentT - Math.floor(i / 4); - // segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, startSegmentT)) - // if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) { - // segments.push(segment); + // const tVals = this.getInkIntersections(i, ink, inkSegment).sort(); + // if (tVals.length) { + // tVals.forEach((t, index) => { + // const docCurveTVal = t + Math.floor(i / 4); + // if (excludeT < startSegmentT || excludeT > docCurveTVal) { + // const localStartTVal = startSegmentT - Math.floor(i / 4); + // if (!hasSplit) { + // t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment1.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); + // } else { + // t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment2.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); + // } + // // if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) segments.push(segment); // } - // const split = inkSegment.split(closestT[1]).right; - // if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) { - // segments.push([split]); + // // start a new segment from the intersection t value + // if (tVals.length - 1 === index) { + // const split = inkSegment.split(t).right; + // if (!hasSplit) { + // if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment1.push(split); + // } else { + // if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment2.push(split); + // } // } - // } + // startSegmentT = docCurveTVal; + // }); // } else { - // segment.push(inkSegment); - // segments.push(segment); + // if (!hasSplit) { + // segment1.push(inkSegment); + // } else { + // segment2.push(inkSegment); + // } // } // } - const segments: Segment[] = []; - var segment1: Segment = []; - var segment2: Segment = []; - var startSegmentT = 0; - var hasSplit = false; - const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); - // This iterates through all segments of the curve and splits them where they intersect another curve. - // if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted) - for (var i = 0; i < inkData.length - 3; i += 4) { - const inkSegment = InkField.Segment(inkData, i); - // Getting all t-value intersections of the current curve with all other curves. - const tVals = this.getInkIntersections(i, ink, inkSegment).sort(); - if (tVals.length) { - tVals.forEach((t, index) => { - const docCurveTVal = t + Math.floor(i / 4); - if (excludeT < startSegmentT || excludeT > docCurveTVal) { - const localStartTVal = startSegmentT - Math.floor(i / 4); - if (!hasSplit) { - t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment1.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); - } else { - t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment2.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); - } - // if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) segments.push(segment); - } - // start a new segment from the intersection t value - if (tVals.length - 1 === index) { - const split = inkSegment.split(t).right; - if (!hasSplit) { - if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment1.push(split); - } else { - if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment2.push(split); - } - } - startSegmentT = docCurveTVal; - }); - } else { - if (!hasSplit) { - segment1.push(inkSegment); - } else { - segment2.push(inkSegment); - } - } - } - 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 (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); + // } return segments; }; getClosestTs = (tVals: number[], excludeT: number, startIndex: number, endIndex: number): number[] => { if (tVals[startIndex] >= excludeT) { - return [-1, tVals[startIndex]]; + return [-1, startIndex]; } else if (tVals[endIndex] < excludeT) { - return [tVals[endIndex], -1]; + return [endIndex, -1]; } else { - const mid = Math.floor(startIndex + endIndex / 2); + const mid = Math.floor((startIndex + endIndex) / 2); if (excludeT >= tVals[mid]) { if (mid + 1 <= endIndex && tVals[mid + 1] > excludeT) { - return [tVals[mid], tVals[mid + 1]]; + return [mid, mid + 1]; } else { return this.getClosestTs(tVals, excludeT, mid + 1, endIndex); } } else { - if (mid - 1 <= startIndex && tVals[mid - 1] < excludeT) { - return [tVals[mid - 1], tVals[mid]]; + if (mid - 1 >= startIndex && tVals[mid - 1] < excludeT) { + return [mid - 1, mid]; } else { return this.getClosestTs(tVals, excludeT, startIndex, mid - 1); } -- cgit v1.2.3-70-g09d2 From 627e436b16745df6982dec3954f2b385f180bad8 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Wed, 20 Mar 2024 23:27:21 -0400 Subject: tried to start closed curve management --- .../collectionFreeForm/CollectionFreeFormView.tsx | 53 +++++++++++++++------- 1 file changed, 36 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 76e1646e2..b8d74dff2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -838,8 +838,9 @@ export class CollectionFreeFormView extends CollectionSubView tVal + Math.floor(i/4)); if (currIntersects.length) { - intersections = [...intersections, ...currIntersects]; - // segmentIndexes is to keep track of the segment index that corresponds to an intersection + intersections = [...intersections, ...currIntersects] for (var j = 0; j < currIntersects.length; j++) { segmentIndexes.push(Math.floor(i / 4)); } } } + // for closed curve bookkeeping + var isClosedCurve = false; + if (intersections[0] === 0) { + isClosedCurve = true; + intersections = intersections.slice(1, intersections.length ); // take the 0 intersection out + segmentIndexes = segmentIndexes.slice(1, segmentIndexes.length); // same for indexes + } + if (intersections.length) { - // this is the indexes of the closest Ts - const closestTs = this.getClosestTs(intersections, excludeT, 0, intersections.length - 1); + // this is the indexes of the closest intersection(s) + var closestTs = this.getClosestTs(intersections, excludeT, 0, intersections.length - 1); // find the segments that need to be split var splitSegment1 = -1; // stays -1 if left end is deleted @@ -882,9 +888,9 @@ export class CollectionFreeFormView extends CollectionSubView { if (tVals[startIndex] >= excludeT) { return [-1, startIndex]; -- cgit v1.2.3-70-g09d2 From 3c6707abda0054335a4924a453b945bb87723a59 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 31 Mar 2024 14:13:35 -0400 Subject: some cleanup --- .../collectionFreeForm/CollectionFreeFormView.tsx | 111 --------------------- 1 file changed, 111 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b8d74dff2..18b75d8ff 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -961,116 +961,6 @@ export class CollectionFreeFormView extends CollectionSubView segmentStartT && excludeT < segmentStartT + 1) { - // if (tVals.length) { - // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); - // if (closestTs[0] != -1 && closestTs[1] != -1) { - // segment1.push(inkSegment.split(0, closestTs[0])); - // segment2.push(inkSegment.split(closestTs[1]).right); - // deletedSegments.push(inkSegment.split(closestTs[0], closestTs[1])); - // } else if (closestTs[0] == -1) { - // segment2.push(inkSegment.split(closestTs[1]).right); - // deletedSegments.push(inkSegment.split(closestTs[1]).left); - // if (excludeT > tVals[0]) { - // continueErasing = !continueErasing; - // } - // } else { - // continueErasing ? segment2.push(inkSegment.split(closestTs[0]).right) : segment1.push(inkSegment.split(0, closestTs[0])); - // continueErasing ? deletedSegments.push(inkSegment.split(closestTs[0]).left) : deletedSegments.push(inkSegment.split(closestTs[0]).right); - // continueErasing = !continueErasing; - // } - // if (!continueErasing) { - // hasSplit = true; - // } - // } else if (hasSplit) { - // segment2.push(inkSegment); - // } else { - // continueErasing = true; - // } - // } else { - // hasSplit ? segment1.push(inkSegment) : segment2.push(inkSegment); - // } - - // if (tVals.length) { - // if (!hasSplit) { - // if (excludeT <= tVals[0] || (excludeT > tVals[0] && excludeT < tVals.lastElement()) || excludeT > tVals.lastElement() || continueErasing) { - // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); - // if (closestTs[0] != -1 && closestTs[1] != -1) { - // segment1.push(inkSegment.split(0, closestTs[0])); - // segment2.push(inkSegment.split(closestTs[1]).right); - // deletedSegments.push(inkSegment.split(closestTs[0], closestTs[1])); - // } else if (closestTs[0] == -1) { - // segment2.push(inkSegment.split(closestTs[1]).right); - // deletedSegments.push(inkSegment.split(closestTs[1]).left); - // if (excludeT > tVals[0]) { - // continueErasing = !continueErasing; - // } - // } else { - // continueErasing ? segment2.push(inkSegment.split(closestTs[0]).right) : segment1.push(inkSegment.split(0, closestTs[0])); - // continueErasing ? deletedSegments.push(inkSegment.split(closestTs[0]).left) : deletedSegments.push(inkSegment.split(closestTs[0]).right); - // continueErasing = !continueErasing; - // } - // if (!continueErasing) { - // hasSplit = true; - // } - // } else { - // if (hasSplit) { - // segment2.push(inkSegment); - // } else { - // segment1.push(inkSegment); - // } - // } - // } else if (excludeT > tVals[0] && excludeT < tVals.lastElement()) { - // // here we know hasSplit is true (but it shouldnt be!) - // segment1 = [...deletedSegments]; - // deletedSegments = []; - // hasSplit = false; - // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); - // if (closestTs[0] != -1 && closestTs[1] != -1) { - // segment1.push(inkSegment.split(0, closestTs[0])); - // segment2.push(inkSegment.split(closestTs[1]).right); - // } else if (closestTs[0] == -1) { - // segment2.push(inkSegment.split(closestTs[1]).right); - // if (excludeT > tVals[0]) { - // continueErasing = !continueErasing; - // } - // } - // } else { - // segment2.push(inkSegment); - // } - // } else if (hasSplit) { - // segment2.push(inkSegment); - // } else { - // continueErasing = true; - // } - - - - - // if (hasSplit && i + 4 >= lastIteration && ((excludeT > tVals[0] && excludeT < tVals.lastElement()) || (excludeT > tVals.lastElement()))) { - // // here we know hasSplit is true (but it shouldnt be!) - // segment1 = [...deletedSegments]; - // deletedSegments = []; - // hasSplit = false; - // const closestTs = this.getClosestTs(tVals, excludeT, 0, tVals.length - 1); - // if (closestTs[0] != -1 && closestTs[1] != -1) { - // segment1.push(inkSegment.split(0, closestTs[0])); - // segment2.push(inkSegment.split(closestTs[1]).right); - // } else if (closestTs[0] == -1) { - // segment2.push(inkSegment.split(closestTs[1]).right); - // if (excludeT > tVals[0]) { - // continueErasing = !continueErasing; - // } - // } - // } - // } // const segments: Segment[] = []; @@ -1122,7 +1012,6 @@ export class CollectionFreeFormView extends CollectionSubView 0.5 || Math.abs(segment2[0].points[0].y - segment2[0].points.lastElement().y) > 0.5)) { // segments.push(segment2); // } - return segments; }; /** -- cgit v1.2.3-70-g09d2 From ba58b46b55527c23cd23b34c2ca13be945c4bf4e Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 7 Apr 2024 13:35:40 -0400 Subject: radius erase changes --- src/client/util/CurrentUserUtils.ts | 10 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 165 ++++++++++----------- src/fields/InkField.ts | 3 + 3 files changed, 94 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 714e33d25..96edce177 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -650,7 +650,15 @@ export class CurrentUserUtils { 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.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, + // { title: "Eraser", toolTip: "Eraser (Ctrl+E)", width: 50, btnType: ButtonType.DropdownList, icon: "eraser", toolType: "eraser", ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }, + // btnList: new List(["Stroke Erase", "Segment Erase", "Radius Erase"]) }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType:"eraser", scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}, + subMenu: [ + { title: "Stroke", toolTip: "Stroke Erase", btnType: ButtonType.ToggleButton, icon: "eraser", toolType:"strokeeraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, + { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "minus", toolType:"segmenteraser",ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, + { title: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle", toolType:"radiuseraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, + // { title: "Width", toolTip: "Eraser Width", btnType: ButtonType.NumberSliderButton, toolType:"strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1}, + ]}, { 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_);}`} }, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 18b75d8ff..da4c53d66 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -610,10 +610,21 @@ export class CollectionFreeFormView extends CollectionSubView segment.map(pt => pt)) - // segments[0][3].x === segments[1][0].x - - // 3 == "3" true - // 3 === "3" falsen !== - - // for (var i = 0; i < segments.length; i ++) { - // if (segments[i][0].points.lastElement().x === segments[i+1][0].points.lastElement().x && segments[i][0].points.lastElement().y === segments[i+1][0].points.lastElement().y) { - // segments[i][0].points.pop(); - // segments[i][0].points = segments[i][0].points.concat(segments[i+1][0].points); - // segments.splice(i+1, 1); - // } - // i++; + var segments; + if (Doc.ActiveTool === InkTool.SegmentEraser) { + 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 + } else if (Doc.ActiveTool === InkTool.RadiusEraser) { + console.log("RADIUS"); + segments = this.radiusErase(intersect.inkView, intersect.t); + } + // } else if (Doc.ActiveTool === InkTool.RadiusEraser) { + // segments = undefined; // } - segments.forEach(segment => + segments?.forEach(segment => this.forceStrokeGesture( e, GestureUtils.Gestures.Stroke, @@ -837,6 +839,42 @@ export class CollectionFreeFormView extends CollectionSubView { + const segments: Segment[] = []; + var segment1: Segment = []; + var segment2: Segment = []; + const eraseWidth = ActiveInkWidth() / 100; + const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); + for (var i = 0; i < inkData.length - 3; i += 4) { + const currCurveT = Math.floor(i/4); + const inkSegment: Bezier = InkField.Segment(inkData, i); + if (eraseT >= currCurveT && eraseT < currCurveT+1) { + if (eraseT - eraseWidth > currCurveT && eraseT + eraseWidth < currCurveT + 1) { + segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); + segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); + } else if (eraseT - eraseWidth < currCurveT) { + segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); + } else if (eraseT + eraseWidth > currCurveT + 1) { + segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); + } + } else if (eraseT > currCurveT + 1) { + segment1.push(inkSegment); + } else { + segment2.push(inkSegment); + } + } + + // 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); + } + 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); + } + return segments; + } + /** * Erases ink strokes by segments. Locates intersections of the current ink stroke with all other ink strokes (including itself), * then erases the segment that was intersected by the eraser. This is done by creating either 1 or two resulting segments @@ -846,7 +884,7 @@ export class CollectionFreeFormView extends CollectionSubView { + segmentErase = (ink: DocumentView, excludeT: number): Segment[] => { const segments: Segment[] = []; var segment1: Segment = []; @@ -873,8 +911,17 @@ export class CollectionFreeFormView extends CollectionSubView 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) { @@ -938,13 +985,16 @@ export class CollectionFreeFormView extends CollectionSubView { - // const docCurveTVal = t + Math.floor(i / 4); - // if (excludeT < startSegmentT || excludeT > docCurveTVal) { - // const localStartTVal = startSegmentT - Math.floor(i / 4); - // if (!hasSplit) { - // t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment1.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); - // } else { - // t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment2.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); - // } - // // if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) segments.push(segment); - // } - // // start a new segment from the intersection t value - // if (tVals.length - 1 === index) { - // const split = inkSegment.split(t).right; - // if (!hasSplit) { - // if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment1.push(split); - // } else { - // if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment2.push(split); - // } - // } - // startSegmentT = docCurveTVal; - // }); - // } else { - // if (!hasSplit) { - // segment1.push(inkSegment); - // } else { - // segment2.push(inkSegment); - // } - // } - // } - // 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); - // } }; /** diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index b3e01229a..9c8c5df2c 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -11,6 +11,9 @@ export enum InkTool { Pen = 'pen', Highlighter = 'highlighter', Eraser = 'eraser', + StrokeEraser = 'strokeeraser', + SegmentEraser = 'segmenteraser', + RadiusEraser = 'radiuseraser', Stamp = 'stamp', Write = 'write', PresentationPin = 'presentationpin', -- cgit v1.2.3-70-g09d2 From 172d791a95126cbdb2ebf59fd8c28a63698002d1 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Mon, 8 Apr 2024 23:20:18 -0400 Subject: radius erase re-implement started --- src/client/documents/Documents.ts | 2 +- src/client/views/InkingStroke.tsx | 22 +++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 38 ++++++++++++++++++---- 3 files changed, 54 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2f5fe4a..78a28ed62 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1104,7 +1104,7 @@ export namespace Docs { I.color = color; I.fillColor = fillColor; I.stroke = new InkField(points); - I.stroke_width = strokeWidth; + I.stroke_xwidth = strokeWidth; I.stroke_bezier = stroke_bezier; I.stroke_startMarker = arrowStart; I.stroke_endMarker = arrowEnd; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 92644d3c5..a91fc5995 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -330,6 +330,28 @@ export class InkingStroke extends ViewBoxBaseComponent() impleme ); }; + splitByEraser = (inkCoords: {X: number; Y: number}, radius: number) => { + // const ptsXscale = (NumCast(radius) - NumCast(radius)) / (oldXrange.max - oldXrange.min || 1) || 1; + // const ptsYscale = (NumCast(doc._height) - NumCast(doc.stroke_width)) / (oldYrange.max - oldYrange.min || 1) || 1; + // const newPoints = func(this.DocumentView?.(), ink, ptsXscale, ptsYscale, NumCast(radius)); + const points: { X: number; Y: number }[] = [ + { X: inkCoords.X + radius, Y: inkCoords.Y }, + { X: inkCoords.X + radius, Y: inkCoords.Y - radius }, + { X: inkCoords.X + radius, Y: inkCoords.Y - radius }, + { X: inkCoords.X, Y: inkCoords.Y - radius }, + { X: inkCoords.X - radius, Y: inkCoords.Y - radius }, + { X: inkCoords.X - radius, Y: inkCoords.Y - radius }, + { X: inkCoords.X - radius, Y: inkCoords.Y }, + { X: inkCoords.X - radius, Y: inkCoords.Y + radius }, + { X: inkCoords.X - radius, Y: inkCoords.Y + radius }, + { X: inkCoords.X, Y: inkCoords.Y + radius }, + { X: inkCoords.X + radius, Y: inkCoords.Y + radius }, + { X: inkCoords.X + radius, Y: inkCoords.Y + radius } + ]; + const eraserCircle = new InkField(points); + return points; + } + _subContentView: ViewBoxInterface | undefined; setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc); @computed get fillColor() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index da4c53d66..26afa5297 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,4 +1,4 @@ -import { findSegment } from '@turf/turf'; +import { findSegment, intersect } from '@turf/turf'; import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; @@ -755,8 +755,11 @@ export class CollectionFreeFormView extends CollectionSubView { + radiusErase = (ink: DocumentView, eraseT: number, inkCoords: {X: number, Y: number}): Segment[] => { const segments: Segment[] = []; var segment1: Segment = []; var segment2: Segment = []; - const eraseWidth = ActiveInkWidth() / 100; - const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); + const eraseWidth = ActiveInkWidth(); + const inkStroke = ink?.ComponentView as InkingStroke; + const { inkData } = (inkStroke).inkScaledData(); + + const eraserInkData = inkStroke.splitByEraser(inkCoords, eraseWidth) as InkData; + + const tVals: number[] = []; // should be the tvals of the intersections + + for (var i = 0; i < eraserInkData.length - 3; i += 4) { + const currCurveT = Math.floor(i/4); + const eraserBezier = InkField.Segment(eraserInkData, i); + for (var j = 0; j < inkData.length; j +=4) { + const inkSegment: Bezier = InkField.Segment(inkData, i); + this.bintersects(inkSegment, eraserBezier).forEach((val: string | number, i: number) => { + // Converting the Bezier.js Split type to a t-value number. + const t = +val.toString().split('/')[0]; + if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). + }); + } + } + for (var i = 0; i < inkData.length - 3; i += 4) { const currCurveT = Math.floor(i/4); const inkSegment: Bezier = InkField.Segment(inkData, i); - if (eraseT >= currCurveT && eraseT < currCurveT+1) { + if (tVals[0] >= currCurveT && tVals[0] < currCurveT+1) { + tVals.shift() + i -= 4; if (eraseT - eraseWidth > currCurveT && eraseT + eraseWidth < currCurveT + 1) { segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); -- cgit v1.2.3-70-g09d2 From 3bba8465a3d823596e7f8cfab24b9afa9c05cf2c Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Wed, 10 Apr 2024 22:04:55 -0400 Subject: eraser circled created --- src/client/views/InkingStroke.tsx | 31 ++++++----- .../collectionFreeForm/CollectionFreeFormView.tsx | 65 +++++++++++----------- 2 files changed, 51 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index a91fc5995..764062682 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -334,22 +334,27 @@ export class InkingStroke extends ViewBoxBaseComponent() impleme // const ptsXscale = (NumCast(radius) - NumCast(radius)) / (oldXrange.max - oldXrange.min || 1) || 1; // const ptsYscale = (NumCast(doc._height) - NumCast(doc.stroke_width)) / (oldYrange.max - oldYrange.min || 1) || 1; // const newPoints = func(this.DocumentView?.(), ink, ptsXscale, ptsYscale, NumCast(radius)); + const controlPointDistance = 0.552284749831 * radius; // tan(pi / 8) * radius const points: { X: number; Y: number }[] = [ - { X: inkCoords.X + radius, Y: inkCoords.Y }, - { X: inkCoords.X + radius, Y: inkCoords.Y - radius }, - { X: inkCoords.X + radius, Y: inkCoords.Y - radius }, - { X: inkCoords.X, Y: inkCoords.Y - radius }, - { X: inkCoords.X - radius, Y: inkCoords.Y - radius }, - { X: inkCoords.X - radius, Y: inkCoords.Y - radius }, - { X: inkCoords.X - radius, Y: inkCoords.Y }, - { X: inkCoords.X - radius, Y: inkCoords.Y + radius }, - { X: inkCoords.X - radius, Y: inkCoords.Y + radius }, - { X: inkCoords.X, Y: inkCoords.Y + radius }, - { X: inkCoords.X + radius, Y: inkCoords.Y + radius }, - { X: inkCoords.X + radius, Y: inkCoords.Y + radius } + { X: inkCoords.X + radius, Y: inkCoords.Y }, // right point + { X: inkCoords.X + radius, Y: inkCoords.Y - controlPointDistance }, // right's top ctrl point + { X: inkCoords.X + controlPointDistance, Y: inkCoords.Y - radius }, // top's right ctrl point + { X: inkCoords.X, Y: inkCoords.Y - radius }, // top + { X: inkCoords.X, Y: inkCoords.Y - radius }, // top again + { X: inkCoords.X - controlPointDistance, Y: inkCoords.Y - radius }, // top's left ctrl point + { X: inkCoords.X - radius, Y: inkCoords.Y - controlPointDistance }, // left's top ctrl point + { X: inkCoords.X - radius, Y: inkCoords.Y }, // left + { X: inkCoords.X - radius, Y: inkCoords.Y }, // left again + { X: inkCoords.X - radius, Y: inkCoords.Y + controlPointDistance }, // left's bottom ctrl point + { X: inkCoords.X - controlPointDistance, Y: inkCoords.Y + radius }, // bottom's left ctrl point + { X: inkCoords.X, Y: inkCoords.Y + radius }, // bottom + { X: inkCoords.X, Y: inkCoords.Y + radius }, // bottom again + { X: inkCoords.X + controlPointDistance, Y: inkCoords.Y + radius }, // bottom's right ctrl point + { X: inkCoords.X + radius, Y: inkCoords.Y + controlPointDistance }, // right's bottom ctrl point + { X: inkCoords.X + radius, Y: inkCoords.Y }, // right again ]; const eraserCircle = new InkField(points); - return points; + return eraserCircle; } _subContentView: ViewBoxInterface | undefined; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 26afa5297..a72b499b2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -625,8 +625,8 @@ export class CollectionFreeFormView extends CollectionSubView { - // Converting the Bezier.js Split type to a t-value number. - const t = +val.toString().split('/')[0]; - if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). - }); - } - } - - for (var i = 0; i < inkData.length - 3; i += 4) { - const currCurveT = Math.floor(i/4); + const eraserBezier: Bezier = InkField.Segment(eraserInkData, i); + segment1.push(eraserBezier); + for (var j = 0; j < inkData.length; j += 4) { const inkSegment: Bezier = InkField.Segment(inkData, i); - if (tVals[0] >= currCurveT && tVals[0] < currCurveT+1) { - tVals.shift() - i -= 4; - if (eraseT - eraseWidth > currCurveT && eraseT + eraseWidth < currCurveT + 1) { - segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); - segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); - } else if (eraseT - eraseWidth < currCurveT) { - segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); - } else if (eraseT + eraseWidth > currCurveT + 1) { - segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); - } - } else if (eraseT > currCurveT + 1) { - segment1.push(inkSegment); - } else { - segment2.push(inkSegment); + // this.bintersects(inkSegment, eraserBezier).forEach((val: string | number, i: number) => { + // // Converting the Bezier.js Split type to a t-value number. + // const t = +val.toString().split('/')[0]; + // if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). + // }); } } + // segment1.push(eraserBezier); + + // for (var i = 0; i < inkData.length - 3; i += 4) { + // const currCurveT = Math.floor(i/4); + // const inkSegment: Bezier = InkField.Segment(inkData, i); + // if (tVals[0] >= currCurveT && tVals[0] < currCurveT+1) { + // tVals.shift() + // i -= 4; + // if (eraseT - eraseWidth > currCurveT && eraseT + eraseWidth < currCurveT + 1) { + // segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); + // segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); + // } else if (eraseT - eraseWidth < currCurveT) { + // segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); + // } else if (eraseT + eraseWidth > currCurveT + 1) { + // segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); + // } + // } else if (eraseT > currCurveT + 1) { + // segment1.push(inkSegment); + // } else { + // segment2.push(inkSegment); + // } + // } // 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)) { -- cgit v1.2.3-70-g09d2 From d938cd08650279f5c7894793d5fd78ec4068694c Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 14 Apr 2024 14:25:29 -0400 Subject: working on radius eraser bug --- src/client/documents/Documents.ts | 2 +- src/client/views/InkingStroke.tsx | 2 +- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 78a28ed62..2d2f5fe4a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1104,7 +1104,7 @@ export namespace Docs { I.color = color; I.fillColor = fillColor; I.stroke = new InkField(points); - I.stroke_xwidth = strokeWidth; + I.stroke_width = strokeWidth; I.stroke_bezier = stroke_bezier; I.stroke_startMarker = arrowStart; I.stroke_endMarker = arrowEnd; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 764062682..7e0b2ba92 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -334,7 +334,7 @@ export class InkingStroke extends ViewBoxBaseComponent() impleme // const ptsXscale = (NumCast(radius) - NumCast(radius)) / (oldXrange.max - oldXrange.min || 1) || 1; // const ptsYscale = (NumCast(doc._height) - NumCast(doc.stroke_width)) / (oldYrange.max - oldYrange.min || 1) || 1; // const newPoints = func(this.DocumentView?.(), ink, ptsXscale, ptsYscale, NumCast(radius)); - const controlPointDistance = 0.552284749831 * radius; // tan(pi / 8) * radius + const controlPointDistance = 0.552284749831 * radius; // (4/3) * tan(pi / 8) * radius const points: { X: number; Y: number }[] = [ { X: inkCoords.X + radius, Y: inkCoords.Y }, // right point { X: inkCoords.X + radius, Y: inkCoords.Y - controlPointDistance }, // right's top ctrl point diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a72b499b2..ef4ab55db 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -625,8 +625,8 @@ export class CollectionFreeFormView extends CollectionSubView Date: Sun, 14 Apr 2024 14:45:53 -0400 Subject: repushing --- src/client/views/InkingStroke.tsx | 2 +- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 6bee0d20d..44c7b3a6b 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -354,7 +354,7 @@ export class InkingStroke extends ViewBoxBaseComponent() impleme { X: inkCoords.X + radius, Y: inkCoords.Y }, // right again ]; const eraserCircle = new InkField(points); - return eraserCircle; + return points; } _subContentView: ViewBoxInterface | undefined; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2bf4f3c1e..1fd52bd91 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -856,7 +856,7 @@ export class CollectionFreeFormView extends CollectionSubView { -- cgit v1.2.3-70-g09d2 From e722df73b65f7c995faf397629839d2a72641b9b Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 14 Apr 2024 15:01:23 -0400 Subject: slight fix --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1fd52bd91..35bb147cf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -857,14 +857,14 @@ export class CollectionFreeFormView extends CollectionSubView { // // Converting the Bezier.js Split type to a t-value number. // const t = +val.toString().split('/')[0]; // if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). // }); - } + // } } // segment1.push(eraserBezier); -- cgit v1.2.3-70-g09d2 From 1b4665378cd93aa49388d92831ce5785b4423bbf Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 14 Apr 2024 16:19:55 -0400 Subject: fixed coordinate system transform for ink --- .../collectionFreeForm/CollectionFreeFormView.tsx | 60 +++++++++++----------- 1 file changed, 29 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2bf4f3c1e..57868825e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -755,13 +755,12 @@ export class CollectionFreeFormView extends CollectionSubView @@ -842,15 +841,15 @@ export class CollectionFreeFormView extends CollectionSubView { + radiusErase = (ink: DocumentView, eraseT: number, inkCoords: { X: number; Y: number }): Segment[] => { const segments: Segment[] = []; var segment1: Segment = []; var segment2: Segment = []; const eraseRadius = ActiveInkWidth() / 2; const inkStroke = ink?.ComponentView as InkingStroke; - const { inkData } = (inkStroke).inkScaledData(); + const { inkData } = inkStroke.inkScaledData(); - const eraserInkData = inkStroke.splitByEraser(inkCoords, eraseRadius); + const eraserInkData = inkStroke.splitByEraser(inkCoords, eraseRadius).inkData; const tVals: number[] = []; // should be the tvals of the intersections @@ -858,16 +857,16 @@ export class CollectionFreeFormView extends CollectionSubView { - // // Converting the Bezier.js Split type to a t-value number. - // const t = +val.toString().split('/')[0]; - // if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). - // }); + const inkSegment: Bezier = InkField.Segment(inkData, i); + // this.bintersects(inkSegment, eraserBezier).forEach((val: string | number, i: number) => { + // // Converting the Bezier.js Split type to a t-value number. + // const t = +val.toString().split('/')[0]; + // if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). + // }); } } // segment1.push(eraserBezier); - + // for (var i = 0; i < inkData.length - 3; i += 4) { // const currCurveT = Math.floor(i/4); // const inkSegment: Bezier = InkField.Segment(inkData, i); @@ -897,7 +896,7 @@ export class CollectionFreeFormView extends CollectionSubView { - const segments: Segment[] = []; var segment1: Segment = []; var segment2: Segment = []; const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); var intersections: number[] = []; // list of the ink stroke's intersections var segmentIndexes: number[] = []; // list of indexes of the curve's segment where each intersection occured - + // loops through each segment and adds intersections to the list for (var i = 0; i < inkData.length - 3; i += 4) { 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.map(tVal => tVal + Math.floor(i / 4)); if (currIntersects.length) { - intersections = [...intersections, ...currIntersects] + intersections = [...intersections, ...currIntersects]; for (var j = 0; j < currIntersects.length; j++) { segmentIndexes.push(Math.floor(i / 4)); } @@ -937,9 +935,7 @@ export class CollectionFreeFormView extends CollectionSubView value > 0.0001 && value < Math.floor(inkData.length / 4) ? index : -1) - .filter(index => index !== -1); + 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]); @@ -959,9 +955,11 @@ export class CollectionFreeFormView extends CollectionSubView { if (tVals[startIndex] >= excludeT) { return [-1, startIndex]; - } - else if (tVals[endIndex] < excludeT) { + } else if (tVals[endIndex] < excludeT) { return [endIndex, -1]; - } - else { + } else { const mid = Math.floor((startIndex + endIndex) / 2); if (excludeT >= tVals[mid]) { if (mid + 1 <= endIndex && tVals[mid + 1] > excludeT) { -- cgit v1.2.3-70-g09d2 From 76c815c248093320510de9af4c959921800e8d34 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 14 Apr 2024 16:22:49 -0400 Subject: from last --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 57868825e..4654de427 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -849,7 +849,7 @@ export class CollectionFreeFormView extends CollectionSubView Date: Sun, 14 Apr 2024 16:28:49 -0400 Subject: from last --- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4654de427..23fa227c2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -751,15 +751,10 @@ export class CollectionFreeFormView extends CollectionSubView { + radiusErase = (ink: DocumentView, eraseT: number, screenEraserPt: { X: number; Y: number }): Segment[] => { const segments: Segment[] = []; + const inkCoords = ink.ComponentView?.ptFromScreen?.(screenEraserPt); // coordinates in ink space + if (!inkCoords) return []; + var segment1: Segment = []; var segment2: Segment = []; const eraseRadius = ActiveInkWidth() / 2; -- cgit v1.2.3-70-g09d2 From d8e4ff91b55736608a02d1ac68cb5c165841d6bb Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 14 Apr 2024 16:30:32 -0400 Subject: clean up imports --- .../collectionFreeForm/CollectionFreeFormView.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 23fa227c2..4e8c2badf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,10 +1,10 @@ -import { findSegment, intersect } from '@turf/turf'; import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; -import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; +import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; +import { DashColor, OmitKeys, Utils, aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; @@ -18,10 +18,9 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../ import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, numberValue, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; -import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { DocUtils, Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { ReplayMovements } from '../../../util/ReplayMovements'; @@ -31,26 +30,26 @@ import { SelectionManager } from '../../../util/SelectionManager'; import { freeformScrollMode } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; -import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; -import { Timeline } from '../../animationtimeline/Timeline'; +import { UndoManager, undoBatch, undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { GestureOverlay } from '../../GestureOverlay'; import { CtrlKey } from '../../GlobalKeyHandler'; import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; +import { StyleProp } from '../../StyleProvider'; +import { Timeline } from '../../animationtimeline/Timeline'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; +import { CreateImage } from '../../nodes/WebBoxRenderer'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../../nodes/trails/PresBox'; -import { CreateImage } from '../../nodes/WebBoxRenderer'; -import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI'; -import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; +import { PoolData, ViewDefBounds, ViewDefResult, computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; -- cgit v1.2.3-70-g09d2 From a374ab9992962ebb6c4af43b82348c706330b58e Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 14 Apr 2024 16:37:27 -0400 Subject: from last --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 6 ------ 1 file changed, 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9a0f34074..60f7662ff 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -617,13 +617,7 @@ export class CollectionFreeFormView extends CollectionSubView Date: Tue, 23 Apr 2024 15:16:06 -0400 Subject: erase multiple segments bug --- package-lock.json | 3 +- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 7 +- src/client/views/InkingStroke.tsx | 110 +++++++++++--- src/client/views/MainView.tsx | 3 + .../collectionFreeForm/CollectionFreeFormView.tsx | 167 ++++++++++++++------- src/client/views/global/globalScripts.ts | 19 ++- 7 files changed, 228 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index 9be0d6cb9..eda529cad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32074,7 +32074,8 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/textarea-caret": { "version": "3.1.0", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b160379df..61a58caaa 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -26,7 +26,7 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { UndoManager, undoable } from '../util/UndoManager'; import { ContextMenu } from '../views/ContextMenu'; import { ContextMenuProps } from '../views/ContextMenuItem'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveEraserWidth, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveEraserWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionView } from '../views/collections/CollectionView'; import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; @@ -1123,6 +1123,7 @@ export namespace Docs { points: PointData[], options: DocumentOptions = {}, strokeWidth = ActiveInkWidth(), + eraserWidth = ActiveEraserWidth(), color = ActiveInkColor(), stroke_bezier = ActiveInkBezierApprox(), fillColor = ActiveFillColor(), @@ -1138,6 +1139,7 @@ export namespace Docs { I.fillColor = fillColor; I.stroke = new InkField(points); I.stroke_width = strokeWidth; + I.eraser_width = eraserWidth; I.stroke_bezier = stroke_bezier; I.stroke_startMarker = arrowStart; I.stroke_endMarker = arrowEnd; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 031b79b25..59fc30635 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -34,6 +34,7 @@ import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; import { LabelBox } from "../views/nodes/LabelBox"; import { ImageBox } from "../views/nodes/ImageBox"; +import { PiEraser } from "react-icons/pi"; interface Button { // DocumentOptions fields a button can set @@ -756,9 +757,8 @@ pie title Minerals in my tap water { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType:"eraser", scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}, subMenu: [ { title: "Stroke", toolTip: "Stroke Erase", btnType: ButtonType.ToggleButton, icon: "eraser", toolType:"strokeeraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, - { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "minus", toolType:"segmenteraser",ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, - { title: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle", toolType:"radiuseraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, - // { title: "Width", toolTip: "Eraser Width", btnType: ButtonType.NumberSliderButton, toolType:"strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1}, + { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "xmarks-lines", toolType:"segmenteraser",ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, + { title: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle-xmark", toolType:"radiuseraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, ]}, { 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_);}`} }, @@ -766,6 +766,7 @@ pie title Minerals in my tap water { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } }, { title: "Labels", toolTip: "Lab els", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1}, + { title: "Eraser Width", toolTip: "Eraser Width", btnType: ButtonType.NumberSliderButton, toolType: "eraserWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1}, { title: "Ink", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} }, ]; } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 1c24a4903..1ae96edfd 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -45,6 +45,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { PinProps, PresBox } from './nodes/trails'; import { StyleProp } from './StyleProvider'; const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore + @observer export class InkingStroke extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big) @@ -330,31 +331,86 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() ); }; - splitByEraser = (inkCoords: {X: number; Y: number}, radius: number) => { - // const ptsXscale = (NumCast(radius) - NumCast(radius)) / (oldXrange.max - oldXrange.min || 1) || 1; - // const ptsYscale = (NumCast(doc._height) - NumCast(doc.stroke_width)) / (oldYrange.max - oldYrange.min || 1) || 1; - // const newPoints = func(this.DocumentView?.(), ink, ptsXscale, ptsYscale, NumCast(radius)); - const controlPointDistance = 0.552284749831 * radius; // (4/3) * tan(pi / 8) * radius - const points: { X: number; Y: number }[] = [ - { X: inkCoords.X + radius, Y: inkCoords.Y }, // right point - { X: inkCoords.X + radius, Y: inkCoords.Y - controlPointDistance }, // right's top ctrl point - { X: inkCoords.X + controlPointDistance, Y: inkCoords.Y - radius }, // top's right ctrl point - { X: inkCoords.X, Y: inkCoords.Y - radius }, // top - { X: inkCoords.X, Y: inkCoords.Y - radius }, // top again - { X: inkCoords.X - controlPointDistance, Y: inkCoords.Y - radius }, // top's left ctrl point - { X: inkCoords.X - radius, Y: inkCoords.Y - controlPointDistance }, // left's top ctrl point - { X: inkCoords.X - radius, Y: inkCoords.Y }, // left - { X: inkCoords.X - radius, Y: inkCoords.Y }, // left again - { X: inkCoords.X - radius, Y: inkCoords.Y + controlPointDistance }, // left's bottom ctrl point - { X: inkCoords.X - controlPointDistance, Y: inkCoords.Y + radius }, // bottom's left ctrl point - { X: inkCoords.X, Y: inkCoords.Y + radius }, // bottom - { X: inkCoords.X, Y: inkCoords.Y + radius }, // bottom again - { X: inkCoords.X + controlPointDistance, Y: inkCoords.Y + radius }, // bottom's right ctrl point - { X: inkCoords.X + radius, Y: inkCoords.Y + controlPointDistance }, // right's bottom ctrl point - { X: inkCoords.X + radius, Y: inkCoords.Y }, // right again - ]; + coordWithSlope = (distance: number, slope: number) => { + return Math.sqrt((distance * distance) / (1 + slope * slope)) + } + + + splitByEraser = (startInkCoords: {X: number; Y: number}, inkCoords: {X: number; Y: number}) => { + + const radius = ActiveEraserWidth() / 2 + 3; // reduce values to avoid extreme radii + console.log("radius", radius); + const ctrlPtDist = 0.552284749831 * radius; // (4/3) * tan(pi / 8) * radius + + var perpSlope = (inkCoords.Y - startInkCoords.Y) / (inkCoords.X - startInkCoords.X); + if (perpSlope > 500) { + perpSlope = 500; // avoid ridiculously high/infinity slopes + } else if (perpSlope < -500) { + perpSlope = -500 + } else if (perpSlope === 0) { + perpSlope = 0.002 + } + const slope = - 1 / perpSlope + console.log("slope", slope); + var points: { X: number; Y: number }[] = [] + if (startInkCoords.X > inkCoords.X) { + points = [ + { X: startInkCoords.X, Y: startInkCoords.Y }, + { X: startInkCoords.X, Y: startInkCoords.Y }, + // { X: startInkCoords.X + this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y + slope * this.coordWithSlope(ctrlPtDist, slope)}, + + { X: inkCoords.X + this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) }, + { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) }, + { X: inkCoords.X + this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + + // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) + this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) }, + { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) }, + { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) }, + { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) }, + // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) - this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + + { X: inkCoords.X - this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) }, + { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) }, + { X: inkCoords.X - this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + + // { X: startInkCoords.X - this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y - slope * this.coordWithSlope(ctrlPtDist, slope) }, + { X: startInkCoords.X, Y: startInkCoords.Y }, + { X: startInkCoords.X, Y: startInkCoords.Y }, + ]; + } else { + points = [ + { X: startInkCoords.X, Y: startInkCoords.Y }, + { X: startInkCoords.X, Y: startInkCoords.Y }, + // { X: startInkCoords.X + this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y + slope * this.coordWithSlope(ctrlPtDist, slope)}, + + { X: inkCoords.X + this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) }, + { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) }, + { X: inkCoords.X + this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + + // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) + this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) }, + { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) }, + { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) }, + { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) }, + // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) - this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + + { X: inkCoords.X - this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) }, + { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) }, + { X: inkCoords.X - this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, + + // { X: startInkCoords.X - this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y - slope * this.coordWithSlope(ctrlPtDist, slope) }, + { X: startInkCoords.X, Y: startInkCoords.Y }, + { X: startInkCoords.X, Y: startInkCoords.Y }, + ]; + } + const eraserCircle = new InkField(points); - return points; + return eraserCircle; } _subContentView: ViewBoxInterface | undefined; @@ -533,6 +589,9 @@ export function SetActiveArrowScale(value: number) { export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); } +export function SetEraserWidth(radius: string): void { + !isNaN(parseInt(radius)) && ActiveInkPen() && (ActiveInkPen().eraserWidth = radius); +} export function ActiveInkPen(): Doc { return Doc.UserDoc(); } @@ -566,3 +625,6 @@ export function ActiveInkWidth(): number { export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } +export function ActiveEraserWidth(): number { + return Number(ActiveInkPen()?.eraserWidth); +} \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 58b8d255a..56db0c488 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -358,6 +358,9 @@ export class MainView extends ObservableReactComponent<{}> { fa.faCut, fa.faEllipsisV, fa.faEraser, + fa.faDeleteLeft, + fa.faXmarksLines, + fa.faCircleXmark, 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 60f7662ff..78dae87c3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -749,10 +749,7 @@ export class CollectionFreeFormView extends CollectionSubView this.forceStrokeGesture( e, @@ -763,7 +760,7 @@ export class CollectionFreeFormView extends CollectionSubView this._eraserLock--); } // Lower ink opacity to give the user a visual indicator of deletion. - intersect.inkView.layoutDoc.opacity = 0.0; + intersect.inkView.layoutDoc.opacity = 0.2; intersect.inkView.layoutDoc.dontIntersect = true; } }); @@ -831,63 +828,86 @@ export class CollectionFreeFormView extends CollectionSubView { + radiusErase = (ink: DocumentView, startPt: { X: number; Y: number }, screenEraserPt: { X: number; Y: number }): Segment[] => { const segments: Segment[] = []; + const startInkCoords = ink.ComponentView?.ptFromScreen?.(startPt); const inkCoords = ink.ComponentView?.ptFromScreen?.(screenEraserPt); // coordinates in ink space if (!inkCoords) return []; - var segment1: Segment = []; - var segment2: Segment = []; - const eraseRadius = ActiveInkWidth() / 2; + var eraseSegment: Segment = []; // for eraser visualization const inkStroke = ink?.ComponentView as InkingStroke; - const { inkData } = inkStroke.inkScaledData(); - - const eraserInkData = inkStroke.splitByEraser(inkCoords, eraseRadius); - const tVals: number[] = []; // should be the tvals of the intersections + const eraserStroke: InkData = inkStroke.splitByEraser(startInkCoords, inkCoords).inkData; + const strokeToTVals: Map = new Map(); + for (var i = 0; i < eraserStroke.length - 3; i +=4) { + eraseSegment.push(InkField.Segment(eraserStroke, i)); // for eraser visualization + this.getOtherInkIntersections(i, eraserStroke, strokeToTVals); + } + strokeToTVals.forEach((tVals, inkStroke) => { + var segment1: Segment = []; + var segment2: Segment = []; + const { inkData } = inkStroke.inkScaledData(); + tVals.sort(); + console.log('TVALS', inkStroke, tVals); - for (var i = 0; i < eraserInkData.length - 3; i += 4) { - const eraserBezier: Bezier = InkField.Segment(eraserInkData, i); - segment1.push(eraserBezier); - for (var j = 0; j < inkData.length; j += 4) { - const inkSegment: Bezier = InkField.Segment(inkData, i); - // this.bintersects(inkSegment, eraserBezier).forEach((val: string | number, i: number) => { - // // Converting the Bezier.js Split type to a t-value number. - // const t = +val.toString().split('/')[0]; - // if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). - // }); + 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); + + if (tVals.length === 2) { + if (tVals[0] > currCurveT && tVals[0] < currCurveT + 1) { + segment1.push(inkSegment.split(0, tVals[0] - 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) { + if (tVals[0] < Math.floor(inkData.length / 4) - tVals[0]) { + // if it's on the first end + segment1.push(inkSegment.split(tVals[0] - currCurveT, 1)); + hasSplit = true; + } else { + segment1.push(inkSegment.split(0, tVals[0] - currCurveT)); + 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); + } + } + } + } } - } - // segment1.push(eraserBezier); - - // for (var i = 0; i < inkData.length - 3; i += 4) { - // const currCurveT = Math.floor(i/4); - // const inkSegment: Bezier = InkField.Segment(inkData, i); - // if (tVals[0] >= currCurveT && tVals[0] < currCurveT+1) { - // tVals.shift() - // i -= 4; - // if (eraseT - eraseWidth > currCurveT && eraseT + eraseWidth < currCurveT + 1) { - // segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); - // segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); - // } else if (eraseT - eraseWidth < currCurveT) { - // segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1)); - // } else if (eraseT + eraseWidth > currCurveT + 1) { - // segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth)); - // } - // } else if (eraseT > currCurveT + 1) { - // segment1.push(inkSegment); - // } else { - // segment2.push(inkSegment); - // } - // } + 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); + } + }); - // 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); - } - 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); - } + segments.push(eraseSegment); // for eraser visualization return segments; }; @@ -1114,6 +1134,47 @@ export class CollectionFreeFormView extends CollectionSubView): Map => { + // const tVals: [InkingStroke, number[]] = []; + // Iterating through all ink strokes in the current freeform collection. + this.childDocs + .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect) + .forEach(doc => { + // InkingStroke of other ink strokes + const otherInk = DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())?.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 2a5732708..279d3c24c 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -12,7 +12,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; -import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke'; +import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, ActiveEraserWidth, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask, SetEraserWidth } from '../InkingStroke'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; // import { InkTranscription } from '../InkTranscription'; import { DocData } from '../../../fields/DocSymbols'; @@ -338,11 +338,19 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); +// 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', value: any, checkResult?: boolean) { +ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', value: any, checkResult?: boolean) { const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc(); + if (option === 'eraserWidth') { + console.log('eraserWidth', value) + } // prettier-ignore - const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ + const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ ['inkMask', { checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())), setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask), @@ -368,6 +376,11 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil setInk: (doc: Doc) => (doc[DocData].color = String(value)), setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, }], + [ 'eraserWidth', { + checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].eraser_width) : ActiveEraserWidth()), + setInk: (doc: Doc) => (doc[DocData].eraserWidth = NumCast(value)), + setMode: () => { SetEraserWidth(value.toString());}, + }] ]); if (checkResult) { -- cgit v1.2.3-70-g09d2 From 6ddfdb20e6a0b2c896594ad2c20292c6cfea0c1a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 24 Apr 2024 00:36:30 -0400 Subject: several fixes to radius ink eraser to fix eraser shap, intersections with straight lines --- src/Utils.ts | 4 + src/client/views/InkingStroke.tsx | 129 ++++++++------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 91 +++++++++------ 3 files changed, 105 insertions(+), 119 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 291d7c799..226051f7d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -348,6 +348,10 @@ export namespace Utils { return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]); } + export function ptDistance(p1: { x: number; y: number }, p2: { x: number; y: number }) { + return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2); + } + export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) { const r = l + w, b = t + h; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 1ae96edfd..03acd5393 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -331,87 +331,54 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() ); }; - coordWithSlope = (distance: number, slope: number) => { - return Math.sqrt((distance * distance) / (1 + slope * slope)) - } - - - splitByEraser = (startInkCoords: {X: number; Y: number}, inkCoords: {X: number; Y: number}) => { - + splitByEraser = (startInkCoordsIn: { X: number; Y: number }, endInkCoordsIn: { X: number; Y: number }) => { const radius = ActiveEraserWidth() / 2 + 3; // reduce values to avoid extreme radii - console.log("radius", radius); - const ctrlPtDist = 0.552284749831 * radius; // (4/3) * tan(pi / 8) * radius - - var perpSlope = (inkCoords.Y - startInkCoords.Y) / (inkCoords.X - startInkCoords.X); - if (perpSlope > 500) { - perpSlope = 500; // avoid ridiculously high/infinity slopes - } else if (perpSlope < -500) { - perpSlope = -500 - } else if (perpSlope === 0) { - perpSlope = 0.002 - } - const slope = - 1 / perpSlope - console.log("slope", slope); - var points: { X: number; Y: number }[] = [] - if (startInkCoords.X > inkCoords.X) { - points = [ - { X: startInkCoords.X, Y: startInkCoords.Y }, - { X: startInkCoords.X, Y: startInkCoords.Y }, - // { X: startInkCoords.X + this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y + slope * this.coordWithSlope(ctrlPtDist, slope)}, - - { X: inkCoords.X + this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) }, - { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) }, - { X: inkCoords.X + this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - - // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) + this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) }, - { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) }, - { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) }, - { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) }, - // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) - this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - - { X: inkCoords.X - this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) }, - { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) }, - { X: inkCoords.X - this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - - // { X: startInkCoords.X - this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y - slope * this.coordWithSlope(ctrlPtDist, slope) }, - { X: startInkCoords.X, Y: startInkCoords.Y }, - { X: startInkCoords.X, Y: startInkCoords.Y }, - ]; - } else { - points = [ - { X: startInkCoords.X, Y: startInkCoords.Y }, - { X: startInkCoords.X, Y: startInkCoords.Y }, - // { X: startInkCoords.X + this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y + slope * this.coordWithSlope(ctrlPtDist, slope)}, - - { X: inkCoords.X + this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) }, - { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) }, - { X: inkCoords.X + this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - - // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) + this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) }, - { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) }, - { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) }, - { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) }, - // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) - this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - - { X: inkCoords.X - this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) }, - { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) }, - { X: inkCoords.X - this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) }, - - // { X: startInkCoords.X - this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y - slope * this.coordWithSlope(ctrlPtDist, slope) }, - { X: startInkCoords.X, Y: startInkCoords.Y }, - { X: startInkCoords.X, Y: startInkCoords.Y }, - ]; - } - - const eraserCircle = new InkField(points); - return eraserCircle; - } + 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); + const direction = { x: (movement.x / moveLen) * radius, y: (movement.y / moveLen) * radius }; + const normal = { x: -direction.y, y: direction.x }; // prettier-ignore + + const startCoords = { X: startInkCoordsIn.X - direction.x, Y: startInkCoordsIn.Y - direction.y }; + const endCoords = { X: endInkCoordsIn.X + direction.x, Y: endInkCoordsIn.Y + direction.y }; + return new InkField([ + // left bot arc + { X: startCoords.X, Y: startCoords.Y }, // prettier-ignore + { X: startCoords.X + normal.x * c, Y: startCoords.Y + normal.y * c }, // prettier-ignore + { X: startCoords.X + direction.x + normal.x - direction.x * c, Y: startCoords.Y + direction.y + normal.y - direction.y * c }, + { X: startCoords.X + direction.x + normal.x, Y: startCoords.Y + direction.y + normal.y }, // prettier-ignore + + // bot + { X: startCoords.X + direction.x + normal.x, Y: startCoords.Y + direction.y + normal.y }, // prettier-ignore + { X: startCoords.X + direction.x + normal.x + direction.x * c, Y: startCoords.Y + direction.y + normal.y + direction.y * c }, + { X: endCoords.X - direction.x + normal.x - direction.x * c, Y: endCoords.Y - direction.y + normal.y - direction.y * c }, // prettier-ignore + { X: endCoords.X - direction.x + normal.x, Y: endCoords.Y - direction.y + normal.y }, // prettier-ignore + + // right bot arc + { X: endCoords.X - direction.x + normal.x, Y: endCoords.Y - direction.y + normal.y }, // prettier-ignore + { X: endCoords.X - direction.x + normal.x + direction.x * c, Y: endCoords.Y - direction.y + normal.y + direction.y * c}, // prettier-ignore + { X: endCoords.X + normal.x * c, Y: endCoords.Y + normal.y * c }, // prettier-ignore + { X: endCoords.X, Y: endCoords.Y }, // prettier-ignore + + // right top arc + { X: endCoords.X, Y: endCoords.Y }, // prettier-ignore + { X: endCoords.X - normal.x * c, Y: endCoords.Y - normal.y * c }, // prettier-ignore + { X: endCoords.X - direction.x - normal.x + direction.x * c, Y: endCoords.Y - direction.y - normal.y + direction.y * c}, // prettier-ignore + { X: endCoords.X - direction.x - normal.x, Y: endCoords.Y - direction.y - normal.y }, // prettier-ignore + + // top + { X: endCoords.X - direction.x - normal.x, Y: endCoords.Y - direction.y - normal.y }, // prettier-ignore + { X: endCoords.X - direction.x - normal.x - direction.x * c, Y: endCoords.Y - direction.y - normal.y - direction.y * c}, // prettier-ignore + { X: startCoords.X + direction.x - normal.x + direction.x * c, Y: startCoords.Y + direction.y - normal.y + direction.y * c }, + { X: startCoords.X + direction.x - normal.x, Y: startCoords.Y + direction.y - normal.y }, // prettier-ignore + + // left top arc + { X: startCoords.X + direction.x - normal.x, Y: startCoords.Y + direction.y - normal.y }, // prettier-ignore + { X: startCoords.X + direction.x - normal.x - direction.x * c, Y: startCoords.Y + direction.y - normal.y - direction.y * c }, // prettier-ignore + { X: startCoords.X - normal.x * c, Y: startCoords.Y - normal.y * c }, // prettier-ignore + { X: startCoords.X, Y: startCoords.Y }, // prettier-ignore + ]); + }; _subContentView: ViewBoxInterface | undefined; setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc); @@ -627,4 +594,4 @@ export function ActiveInkBezierApprox(): string { } export function ActiveEraserWidth(): number { return Number(ActiveInkPen()?.eraserWidth); -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 78dae87c3..9df9c5492 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -620,6 +620,7 @@ export class CollectionFreeFormView extends CollectionSubView { 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. this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => { if (!this._deleteList.includes(intersect.inkView)) { @@ -749,7 +753,7 @@ export class CollectionFreeFormView extends CollectionSubView this.forceStrokeGesture( e, @@ -832,16 +836,16 @@ export class CollectionFreeFormView extends CollectionSubView = new Map(); - for (var i = 0; i < eraserStroke.length - 3; i +=4) { + for (var i = 0; i < eraserStroke.length - 3; i += 4) { eraseSegment.push(InkField.Segment(eraserStroke, i)); // for eraser visualization - this.getOtherInkIntersections(i, eraserStroke, strokeToTVals); + this.getOtherInkIntersections(ink, i, eraserStroke, strokeToTVals); } strokeToTVals.forEach((tVals, inkStroke) => { var segment1: Segment = []; @@ -852,7 +856,7 @@ export class CollectionFreeFormView extends CollectionSubView currCurveT && tVals[0] < currCurveT + 1) { - if (tVals[0] < Math.floor(inkData.length / 4) - tVals[0]) { + // 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(inkSegment.split(tVals[0] - currCurveT, 1)); + segment1.push(splits.left); hasSplit = true; } else { - segment1.push(inkSegment.split(0, tVals[0] - currCurveT)); + segment1.push(splits.right); hasSplit = true; } } else { @@ -1084,6 +1095,15 @@ export class CollectionFreeFormView extends CollectionSubView { + if ((curve as any)._linear) { + // bezier.js doesn't intersect properly if the curve is actually a line -- so get intersect other curve against this line, then figure out the t coordinates of the intersection on this line + const intersections = otherCurve.lineIntersects({ p1: curve.points[0], p2: curve.points[3] }); + if (intersections.length) { + const intPt = otherCurve.get(intersections[0]); + const intT = curve.project(intPt).t; + return intT ? [intT] : []; + } + } if ((otherCurve as any)._linear) { return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); } @@ -1135,43 +1155,38 @@ export class CollectionFreeFormView extends CollectionSubView): Map => { - // const tVals: [InkingStroke, number[]] = []; - // Iterating through all ink strokes in the current freeform collection. - this.childDocs - .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect) - .forEach(doc => { - // InkingStroke of other ink strokes - const otherInk = DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())?.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)]); - } + getOtherInkIntersections = (otherInkDocView: DocumentView, i: number, points: InkData, strokeToTVals: Map): Map => { + // 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; }; -- cgit v1.2.3-70-g09d2 From af67724dc3a1390cd0c2aded4aeda69f948a0f66 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Apr 2024 01:32:57 -0400 Subject: fixed eraser buttons so that you can switch to the previous eraser, then click again to choose a different eraser --- package-lock.json | 11 +++++----- package.json | 2 +- src/client/util/CurrentUserUtils.ts | 10 ++++----- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/global/globalScripts.ts | 25 ++++++++++++++++++++-- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 17 +++++++++------ src/fields/InkField.ts | 1 - 7 files changed, 45 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index eda529cad..cf4317fb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,7 @@ "body-parser": "^1.20.2", "bootstrap": "^5.3.2", "brotli": "^1.3.3", - "browndash-components": "^0.1.36", + "browndash-components": "^0.1.44", "browser-assert": "^1.2.1", "bson": "^6.2.0", "canvas": "^2.11.2", @@ -10868,9 +10868,9 @@ } }, "node_modules/browndash-components": { - "version": "0.1.36", - "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.36.tgz", - "integrity": "sha512-++Xxn67r9ETz7pQr9dE09dMgPJ6rltEzwYWifpI98AQct4RmEBfiPp5eLG94+143XllxZy++KBf9YJ8AYpTEkQ==", + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.44.tgz", + "integrity": "sha512-+QYEHDqd3iauijdKiVA0jXGFkOMEKn+qb7Y2iG+VK0xAh8Fho3rAUoSMv6snfg+kMc1oMGhACzJa5XauTtvguQ==", "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -10880,8 +10880,7 @@ "npm": "^9.8.1", "react-color": "^2.19.3", "react-icons": "^4.3.1", - "react-measure": "^2.5.2", - "styled-components": "^6.0.3" + "react-measure": "^2.5.2" } }, "node_modules/browndash-components/node_modules/npm": { diff --git a/package.json b/package.json index a29f39444..56fdb2920 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "body-parser": "^1.20.2", "bootstrap": "^5.3.2", "brotli": "^1.3.3", - "browndash-components": "^0.1.36", + "browndash-components": "^0.1.44", "browser-assert": "^1.2.1", "bson": "^6.2.0", "canvas": "^2.11.2", diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 59fc30635..ed81862c2 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -752,13 +752,11 @@ 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)", width: 50, btnType: ButtonType.DropdownList, icon: "eraser", toolType: "eraser", ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }, - // btnList: new List(["Stroke Erase", "Segment Erase", "Radius Erase"]) }, - { 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:"strokeeraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, - { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "xmarks-lines", toolType:"segmenteraser",ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, - { title: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle-xmark", toolType:"radiuseraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, + { 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: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkTool.RadiusEraser, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, ]}, { 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_);}`} }, diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 2f64ea28c..3349fb5dc 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -273,7 +273,7 @@ export class KeyManager { } break; case 'e': - Doc.ActiveTool = Doc.ActiveTool === InkTool.Eraser ? InkTool.None : InkTool.Eraser; + Doc.ActiveTool = [InkTool.StrokeEraser, InkTool.SegmentEraser, InkTool.RadiusEraser].includes(Doc.ActiveTool) ? InkTool.None : InkTool.StrokeEraser; break; case 'p': Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 279d3c24c..d565a530b 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -12,7 +12,21 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; -import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, ActiveEraserWidth, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask, SetEraserWidth } from '../InkingStroke'; +import { + ActiveFillColor, + ActiveInkColor, + ActiveInkHideTextLabels, + ActiveInkWidth, + ActiveIsInkMask, + ActiveEraserWidth, + InkingStroke, + SetActiveFillColor, + SetActiveInkColor, + SetActiveInkHideTextLabels, + SetActiveInkWidth, + SetActiveIsInkMask, + SetEraserWidth, +} from '../InkingStroke'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; // import { InkTranscription } from '../InkTranscription'; import { DocData } from '../../../fields/DocSymbols'; @@ -323,6 +337,9 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; } } else if (tool) { + if ([InkTool.StrokeEraser, InkTool.RadiusEraser, InkTool.SegmentEraser].includes(tool as any)) { + Doc.UserDoc().activeEraserTool = tool; + } // pen or eraser if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) { Doc.ActiveTool = InkTool.None; @@ -338,6 +355,10 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); +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)); @@ -347,7 +368,7 @@ ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', value: any, checkResult?: boolean) { const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc(); if (option === 'eraserWidth') { - console.log('eraserWidth', value) + console.log('eraserWidth', value); } // prettier-ignore const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 57ae92359..754c6a68c 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -6,7 +6,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; @@ -20,6 +20,7 @@ import { OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import './FontIconBox.scss'; import TrailsIcon from './TrailsIcon'; +import { InkTool } from '../../../../fields/InkField'; export enum ButtonType { TextButton = 'textBtn', @@ -274,24 +275,28 @@ export class FontIconBox extends ViewBoxBaseComponent() { // Determine the type of toggle button const tooltip: string = StrCast(this.Document.toolTip); - const script = ScriptCast(this.Document.onClick); - const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false; + const script = ScriptCast(this.Document.onClick).script; + const toggleStatus = script?.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result; // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); const items = DocListCast(this.dataDoc.data); + const multiDoc = this.Document; return ( script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => script.run({ this: multiDoc, value: undefined, _readOnly_: false }))} + isToggle={script ? true : false} + toggleStatus={toggleStatus} + //background={SettingsManager.userBackgroundColor} label={this.label} items={DocListCast(this.dataDoc.data).map(item => ({ icon: , tooltip: StrCast(item.toolTip), val: StrCast(item.toolType), }))} - selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)} + selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType ?? StrCast(multiDoc.toolType))} setSelectedVal={(val: string | number) => { const itemDoc = items.find(item => item.toolType === val); itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false }); @@ -322,7 +327,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { toggleStatus={toggleStatus} text={buttonText} color={color} - //background={SettingsManager.userBackgroundColor} + // background={SettingsManager.userBackgroundColor} icon={this.Icon(color)!} label={this.label} onPointerDown={e => diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 9c8c5df2c..1c01a5d7c 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -10,7 +10,6 @@ export enum InkTool { None = 'none', Pen = 'pen', Highlighter = 'highlighter', - Eraser = 'eraser', StrokeEraser = 'strokeeraser', SegmentEraser = 'segmenteraser', RadiusEraser = 'radiuseraser', -- cgit v1.2.3-70-g09d2 From 8025b0b38e49a61808a8924382247fe2c9ac1e05 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Apr 2024 01:41:25 -0400 Subject: from last --- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 754c6a68c..ebd6c97bc 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -275,7 +275,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { // Determine the type of toggle button const tooltip: string = StrCast(this.Document.toolTip); - const script = ScriptCast(this.Document.onClick).script; + const script = ScriptCast(this.Document.onClick)?.script; const toggleStatus = script?.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result; // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); -- cgit v1.2.3-70-g09d2 From 82f8c4bac5c9d8a6aef4b42936bd1839fcb8939e Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sat, 27 Apr 2024 17:53:25 -0400 Subject: pull --- src/client/util/CurrentUserUtils.ts | 5 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 95 +++++++++++++++++----- src/client/views/global/globalScripts.ts | 5 +- 3 files changed, 80 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 59fc30635..d5b571141 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -752,21 +752,20 @@ 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)", width: 50, btnType: ButtonType.DropdownList, icon: "eraser", toolType: "eraser", ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }, - // btnList: new List(["Stroke Erase", "Segment Erase", "Radius Erase"]) }, { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType:"eraser", scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}, subMenu: [ { title: "Stroke", toolTip: "Stroke Erase", btnType: ButtonType.ToggleButton, icon: "eraser", toolType:"strokeeraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "xmarks-lines", toolType:"segmenteraser",ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, { title: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle-xmark", toolType:"radiuseraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, ]}, + // { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType:"eraser", 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}, { 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_);}`} }, { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } }, { title: "Labels", toolTip: "Lab els", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1}, - { title: "Eraser Width", toolTip: "Eraser Width", btnType: ButtonType.NumberSliderButton, toolType: "eraserWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1}, { title: "Ink", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} }, ]; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9df9c5492..eedee0b18 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -787,6 +787,55 @@ export class CollectionFreeFormView extends CollectionSubView { + const radius = ActiveEraserWidth() / 2 + 3; // reduce values to avoid extreme radii + 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); + const direction = { x: (movement.x / moveLen) * radius, y: (movement.y / moveLen) * radius }; + const normal = { x: -direction.y, y: direction.x }; // prettier-ignore + + const startCoords = { X: startInkCoordsIn.X - direction.x, Y: startInkCoordsIn.Y - direction.y }; + const endCoords = { X: endInkCoordsIn.X + direction.x, Y: endInkCoordsIn.Y + direction.y }; + return new InkField([ + // left bot arc + { X: startCoords.X, Y: startCoords.Y }, // prettier-ignore + { X: startCoords.X + normal.x * c, Y: startCoords.Y + normal.y * c }, // prettier-ignore + { X: startCoords.X + direction.x + normal.x - direction.x * c, Y: startCoords.Y + direction.y + normal.y - direction.y * c }, + { X: startCoords.X + direction.x + normal.x, Y: startCoords.Y + direction.y + normal.y }, // prettier-ignore + + // bot + { X: startCoords.X + direction.x + normal.x, Y: startCoords.Y + direction.y + normal.y }, // prettier-ignore + { X: startCoords.X + direction.x + normal.x + direction.x * c, Y: startCoords.Y + direction.y + normal.y + direction.y * c }, + { X: endCoords.X - direction.x + normal.x - direction.x * c, Y: endCoords.Y - direction.y + normal.y - direction.y * c }, // prettier-ignore + { X: endCoords.X - direction.x + normal.x, Y: endCoords.Y - direction.y + normal.y }, // prettier-ignore + + // right bot arc + { X: endCoords.X - direction.x + normal.x, Y: endCoords.Y - direction.y + normal.y }, // prettier-ignore + { X: endCoords.X - direction.x + normal.x + direction.x * c, Y: endCoords.Y - direction.y + normal.y + direction.y * c}, // prettier-ignore + { X: endCoords.X + normal.x * c, Y: endCoords.Y + normal.y * c }, // prettier-ignore + { X: endCoords.X, Y: endCoords.Y }, // prettier-ignore + + // right top arc + { X: endCoords.X, Y: endCoords.Y }, // prettier-ignore + { X: endCoords.X - normal.x * c, Y: endCoords.Y - normal.y * c }, // prettier-ignore + { X: endCoords.X - direction.x - normal.x + direction.x * c, Y: endCoords.Y - direction.y - normal.y + direction.y * c}, // prettier-ignore + { X: endCoords.X - direction.x - normal.x, Y: endCoords.Y - direction.y - normal.y }, // prettier-ignore + + // top + { X: endCoords.X - direction.x - normal.x, Y: endCoords.Y - direction.y - normal.y }, // prettier-ignore + { X: endCoords.X - direction.x - normal.x - direction.x * c, Y: endCoords.Y - direction.y - normal.y - direction.y * c}, // prettier-ignore + { X: startCoords.X + direction.x - normal.x + direction.x * c, Y: startCoords.Y + direction.y - normal.y + direction.y * c }, + { X: startCoords.X + direction.x - normal.x, Y: startCoords.Y + direction.y - normal.y }, // prettier-ignore + + // left top arc + { X: startCoords.X + direction.x - normal.x, Y: startCoords.Y + direction.y - normal.y }, // prettier-ignore + { X: startCoords.X + direction.x - normal.x - direction.x * c, Y: startCoords.Y + direction.y - normal.y - direction.y * c }, // prettier-ignore + { X: startCoords.X - normal.x * c, Y: startCoords.Y - normal.y * c }, // prettier-ignore + { X: startCoords.X, Y: startCoords.Y }, // prettier-ignore + ]); + }; + /** * 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 @@ -1163,30 +1212,34 @@ export class CollectionFreeFormView extends CollectionSubView): Map => { - // 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)); + 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)]); + } } - } else { - strokeToTVals.set(otherInk, [t + Math.floor(j / 4)]); - } + }); } }); - } return strokeToTVals; }; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 279d3c24c..86bd684be 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -324,7 +324,10 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, } } else if (tool) { // pen or eraser - if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) { + 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) { Doc.ActiveTool = InkTool.None; } else { Doc.ActiveTool = tool as any; -- cgit v1.2.3-70-g09d2 From 4a01680bd22a0652dbdd0da5c3a7167ca8117440 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Wed, 8 May 2024 22:22:29 -0400 Subject: close curve and bounding box fixes --- src/client/util/CurrentUserUtils.ts | 10 +- src/client/views/MainView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 347 ++++++++++++--------- src/client/views/global/globalScripts.ts | 10 +- 4 files changed, 214 insertions(+), 154 deletions(-) (limited to 'src') 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 { @@ -750,10 +755,7 @@ export class CollectionFreeFormView extends CollectionSubView this.forceStrokeGesture( e, @@ -764,12 +766,44 @@ export class CollectionFreeFormView extends CollectionSubView 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 = 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 { - 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); @@ -836,6 +870,39 @@ export class CollectionFreeFormView extends CollectionSubView { + 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 { + 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(); + 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 = new Map(); - 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 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 (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 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): Map => { - 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(); -- cgit v1.2.3-70-g09d2 From 4a332476db24555260820b8c5e268acc2000041c Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 May 2024 11:15:46 -0400 Subject: tweaks to make eraser deletion more efficient --- .../collectionFreeForm/CollectionFreeFormView.tsx | 67 ++++++++++++---------- 1 file changed, 38 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2e174be30..ca9a9974a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,5 @@ -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'; @@ -693,7 +691,7 @@ export class CollectionFreeFormView extends CollectionSubView { - this._deleteList.forEach(ink => ink._props.removeDocument?.(ink.Document)); + this._deleteList.lastElement()?._props.removeDocument?.(this._deleteList.map(ink => ink.Document)); this._deleteList = []; this._batch?.end(); }; @@ -756,13 +754,23 @@ export class CollectionFreeFormView extends CollectionSubView - this.forceStrokeGesture( - e, - GestureUtils.Gestures.Stroke, - segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) - ) - ); + const newStrokes = segments?.map(segment => { + const points = segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]); + const bounds = GestureOverlay.getBounds(points); + const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height); + const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale; + return Docs.Create.InkDocument( + points, + { title: 'stroke', + x: B.x - inkWidth / 2, + y: B.y - inkWidth / 2, + _width: B.width + inkWidth, + _height: B.height + inkWidth, + stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore + inkWidth + ); + }); + newStrokes && this.addDocument?.(newStrokes); setTimeout(() => this._eraserLock--); } // Lower ink opacity to give the user a visual indicator of deletion. @@ -872,17 +880,17 @@ export class CollectionFreeFormView extends CollectionSubView { 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]; + let minX = eraserOutline[0].X, maxX = eraserOutline[0].X; // prettier-ignore + let minY = eraserOutline[0].Y, maxY = eraserOutline[0].Y; // prettier-ignore + for (let 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); @@ -894,8 +902,7 @@ export class CollectionFreeFormView extends CollectionSubView 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 ) { + 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; } } @@ -950,7 +957,7 @@ export class CollectionFreeFormView extends CollectionSubView { 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 eraserMax = { X: Math.max(lastPoint.X, currPoint.X) + eraserRadius, Y: Math.max(lastPoint.Y, currPoint.Y) + eraserRadius }; const strokeToTVals = new Map(); const intersectingStrokes = this.childDocs .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())) @@ -964,7 +971,7 @@ export class CollectionFreeFormView extends CollectionSubView= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top ); - console.log("itersectnig strokes", intersectingStrokes); + intersectingStrokes.forEach(({ inkStroke, inkView }) => { const { inkData } = inkStroke.inkScaledData(); const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); @@ -1007,7 +1014,6 @@ export class CollectionFreeFormView extends CollectionSubView 0) { @@ -1042,18 +1049,18 @@ export class CollectionFreeFormView extends CollectionSubView 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 ) { + if (segmentTs[j] === 0) { continueErasing = true; } else if (segmentTs[j] === Math.floor(inkData.length / 4) + 1) { break; - }else { + } else { if (!continueErasing) { currSegment.push(inkBezier.split(0, segmentTs[j] - currCurveT)); continueErasing = true; @@ -1071,7 +1078,8 @@ export class CollectionFreeFormView extends CollectionSubView lu.stroke && this._renderCutoffData.set(lu[Id] + '', true)); } this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); }); -- cgit v1.2.3-70-g09d2 From d2c67049891493551e39f8c2423ee6e40109bb65 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 12 May 2024 01:00:42 -0400 Subject: trying out a circle around the cursor --- .../collectionFreeForm/CollectionFreeFormView.tsx | 35 ++++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2e174be30..ab47c105e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -97,6 +97,8 @@ export class CollectionFreeFormView extends CollectionSubView { const currPoint = { X: e.clientX, Y: e.clientY }; @@ -775,6 +778,8 @@ export class CollectionFreeFormView extends CollectionSubView { + // this._eraserX = e.clientX; + // this._eraserY = e.clientY; 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)); @@ -964,7 +969,6 @@ export class CollectionFreeFormView extends CollectionSubView= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top ); - console.log("itersectnig strokes", intersectingStrokes); intersectingStrokes.forEach(({ inkStroke, inkView }) => { const { inkData } = inkStroke.inkScaledData(); const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); @@ -1007,7 +1011,6 @@ export class CollectionFreeFormView extends CollectionSubView 0) { - segments.push(currSegment); - return segments; - } + segments.push(currSegment); + return segments; } var continueErasing = false; var firstSegment: Segment = []; // early return if nothing to split on if (tVals.length === 0 || (tVals.length === 1 && tVals[0] === 0)) { + for (var i = 0; i < inkData.length - 3; i += 4) { + currSegment.push(InkField.Segment(inkData, i)); + } + segments.push(currSegment); return segments; } @@ -1082,6 +1087,9 @@ export class CollectionFreeFormView extends CollectionSubView { + this._eraserX = e.clientX; + this._eraserY = e.clientY; // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); }; @@ -2265,6 +2275,19 @@ export class CollectionFreeFormView extends CollectionSubView + {/*
*/} {this.paintFunc ? ( // need this so that any live dashfieldviews will update the underlying text that the code eval reads ) : this._lightboxDoc ? ( -- cgit v1.2.3-70-g09d2 From f8053c9d1f7d849cc0c3e8c0b8bf4a466ffd85b9 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Tue, 14 May 2024 13:01:51 -0400 Subject: some bug fixes --- .../collectionFreeForm/CollectionFreeFormView.tsx | 152 ++++++++++++++------- 1 file changed, 102 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 622189b80..14d20eb4a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -95,8 +95,6 @@ export class CollectionFreeFormView extends CollectionSubView(); @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined = undefined; // highlighted region of freeform canvas used by presentations to indicate a region @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. + @observable _eraserX: number = 0; + @observable _eraserY: number = 0; + @observable _showEraserCircle: boolean = false; // to determine whether the radius eraser should show @computed get contentViews() { const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); @@ -747,7 +749,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (!this._deleteList.includes(intersect.inkView)) { this._deleteList.push(intersect.inkView); @@ -755,7 +757,7 @@ export class CollectionFreeFormView extends CollectionSubView { const points = segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]); @@ -784,23 +786,34 @@ export class CollectionFreeFormView extends CollectionSubView { - // this._eraserX = e.clientX; - // this._eraserY = e.clientY; + // const currZoom = this.zoomScaling(); + // console.log("curr zoom", currZoom); + // console.log("prev zoom", this.prevZoom); 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 = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint); + const intersections: any[] = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint); + // if (intersections[0].size > 0 && currZoom === this.prevZoom) { + const strokeMap: Map = intersections[0]; + const eraserStroke = intersections[1]; + 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()); + const segments = this.radiusErase(stroke, intersects.sort(), eraserStroke); segments?.forEach(segment => this.forceStrokeGesture( e, @@ -808,12 +821,16 @@ export class CollectionFreeFormView extends CollectionSubView [...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; }); + // } else if (intersections[0].length > 0) { + // this.prevZoom = currZoom; + // console.log("skipping occurred"); + // } else if (this.prevZoom = currZoom) { + // this.prevZoom = currZoom; + // } return false; }; @@ -834,8 +851,16 @@ export class CollectionFreeFormView extends CollectionSubView { - const radius = ActiveEraserWidth() + 3; // add 3 to avoid eraser being too thin + /** + * Creates the eraser outline for a radius eraser. The outline be + * @param startInkCoordsIn + * @param endInkCoordsIn + * @param inkStrokeWidth + * @returns + */ + createEraserOutline = (startInkCoordsIn: { X: number; Y: number }, endInkCoordsIn: { X: number; Y: number }, inkStrokeWidth: number) => { + // increase radius slightly based on the erased stroke's width, added to make eraser look more realistic + var radius = ActiveEraserWidth() + inkStrokeWidth * 0.2; 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); @@ -959,8 +984,13 @@ export class CollectionFreeFormView extends CollectionSubView { - const eraserRadius = ActiveEraserWidth() + 3; + const eraserRadius = ActiveEraserWidth() / this.zoomScaling(); 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(); @@ -976,12 +1006,14 @@ export class CollectionFreeFormView extends CollectionSubView= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top ); - + const erasers: InkData[] = []; intersectingStrokes.forEach(({ inkStroke, inkView }) => { - const { inkData } = inkStroke.inkScaledData(); + const { inkData, inkStrokeWidth } = inkStroke.inkScaledData(); const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); const currPointInkSpace = inkStroke.ptFromScreen(currPoint); - const eraserInkData = this.createEraserOutline(prevPointInkSpace, currPointInkSpace).inkData; + const eraserInkData = this.createEraserOutline(prevPointInkSpace, currPointInkSpace, inkStrokeWidth).inkData; + // erasers.push(eraserInkData); + // add the ends of the stroke in as "intersections" if (this.insideEraserOutline(eraserInkData, inkData[0])) { strokeToTVals.set(inkView, [0]); @@ -994,6 +1026,7 @@ export class CollectionFreeFormView extends CollectionSubView Math.abs(val - (t + Math.floor(i / 4))) <= tValOffset); if (!inList) { inkList.push(t + Math.floor(i / 4)); @@ -1019,33 +1052,43 @@ export class CollectionFreeFormView extends CollectionSubView { + radiusErase = (ink: DocumentView, tVals: number[], erasers: InkData[]): Segment[] => { const segments: Segment[] = []; + var eraser: Segment = []; + // for (var i = 0; i < erasers.length; i ++) { + // for (var j = 0; j < erasers[i].length - 3; j +=4) { + // eraser.push(InkField.Segment(erasers[i], j)); + // } + // segments.push(eraser); + // eraser = []; + // } + const inkStroke = ink?.ComponentView as InkingStroke; const { inkData } = inkStroke.inkScaledData(); var currSegment: Segment = []; if (tVals.length % 2 !== 0) { - // should always have even tVals + // any radius erase stroke will always result in even tVals, since the ends are included for (var i = 0; i < inkData.length - 3; i += 4) { currSegment.push(InkField.Segment(inkData, i)); } segments.push(currSegment); - return segments; + return segments; // want to return the full original stroke } - var continueErasing = false; - var firstSegment: Segment = []; + var continueErasing = false; // used to erase segments if they are completely enclosed in the eraser + var firstSegment: Segment = []; // used to keep track of the first segment for closed curves + // early return if nothing to split on if (tVals.length === 0 || (tVals.length === 1 && tVals[0] === 0)) { for (var i = 0; i < inkData.length - 3; i += 4) { @@ -1055,26 +1098,28 @@ export class CollectionFreeFormView extends CollectionSubView 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) { + if (segmentTs[j] === 0) { // if the first end of the segment is within the eraser continueErasing = true; - } else if (segmentTs[j] === Math.floor(inkData.length / 4) + 1) { + } else if (segmentTs[j] === Math.floor(inkData.length / 4) + 1) { // the last end break; } else { if (!continueErasing) { currSegment.push(inkBezier.split(0, segmentTs[j] - currCurveT)); continueErasing = true; - } else { + } else { // we've reached the end of the part to take out... continueErasing = false; if (currSegment.length > 0) { - segments.push(currSegment); + segments.push(currSegment); // ...so we add it to the list and reset currSegment if (firstSegment.length === 0) { firstSegment = currSegment; } @@ -1091,15 +1136,14 @@ export class CollectionFreeFormView extends CollectionSubView 0) { + // add the first segment onto the last to avoid fragmentation for closed curves if (InkingStroke.IsClosed(inkData)) { currSegment = currSegment.concat(firstSegment); } segments.push(currSegment); } - if (segments.length === 0) { - console.log("segments is 0"); - } return segments; }; @@ -1219,6 +1263,7 @@ export class CollectionFreeFormView extends CollectionSubView 0 && segment2.length > 0) { segment2 = segment2.concat(segment1); segment1 = []; @@ -1439,7 +1484,7 @@ export class CollectionFreeFormView extends CollectionSubView { this._eraserX = e.clientX; this._eraserY = e.clientY; + if (this._eraserX < 0 || this._eraserY < 0) { + this._showEraserCircle = false; + } else { + this._showEraserCircle = true; + } // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); }; @@ -2286,19 +2336,21 @@ export class CollectionFreeFormView extends CollectionSubView - {/*
*/} + {Doc.ActiveTool === InkTool.RadiusEraser && ( +
+ )} {this.paintFunc ? ( // need this so that any live dashfieldviews will update the underlying text that the code eval reads ) : this._lightboxDoc ? ( -- cgit v1.2.3-70-g09d2 From 84098c41aaa4970d43b88645489e64c1cac22934 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Wed, 15 May 2024 14:33:50 -0400 Subject: three eraser modes implemented --- src/client/views/InkingStroke.tsx | 49 ------- .../collectionFreeForm/CollectionFreeFormView.tsx | 141 +++++++++++---------- src/client/views/global/globalScripts.ts | 3 - 3 files changed, 73 insertions(+), 120 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 03acd5393..9e09c0aa9 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -331,55 +331,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() ); }; - splitByEraser = (startInkCoordsIn: { X: number; Y: number }, endInkCoordsIn: { X: number; Y: number }) => { - const radius = ActiveEraserWidth() / 2 + 3; // reduce values to avoid extreme radii - 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); - const direction = { x: (movement.x / moveLen) * radius, y: (movement.y / moveLen) * radius }; - const normal = { x: -direction.y, y: direction.x }; // prettier-ignore - - const startCoords = { X: startInkCoordsIn.X - direction.x, Y: startInkCoordsIn.Y - direction.y }; - const endCoords = { X: endInkCoordsIn.X + direction.x, Y: endInkCoordsIn.Y + direction.y }; - return new InkField([ - // left bot arc - { X: startCoords.X, Y: startCoords.Y }, // prettier-ignore - { X: startCoords.X + normal.x * c, Y: startCoords.Y + normal.y * c }, // prettier-ignore - { X: startCoords.X + direction.x + normal.x - direction.x * c, Y: startCoords.Y + direction.y + normal.y - direction.y * c }, - { X: startCoords.X + direction.x + normal.x, Y: startCoords.Y + direction.y + normal.y }, // prettier-ignore - - // bot - { X: startCoords.X + direction.x + normal.x, Y: startCoords.Y + direction.y + normal.y }, // prettier-ignore - { X: startCoords.X + direction.x + normal.x + direction.x * c, Y: startCoords.Y + direction.y + normal.y + direction.y * c }, - { X: endCoords.X - direction.x + normal.x - direction.x * c, Y: endCoords.Y - direction.y + normal.y - direction.y * c }, // prettier-ignore - { X: endCoords.X - direction.x + normal.x, Y: endCoords.Y - direction.y + normal.y }, // prettier-ignore - - // right bot arc - { X: endCoords.X - direction.x + normal.x, Y: endCoords.Y - direction.y + normal.y }, // prettier-ignore - { X: endCoords.X - direction.x + normal.x + direction.x * c, Y: endCoords.Y - direction.y + normal.y + direction.y * c}, // prettier-ignore - { X: endCoords.X + normal.x * c, Y: endCoords.Y + normal.y * c }, // prettier-ignore - { X: endCoords.X, Y: endCoords.Y }, // prettier-ignore - - // right top arc - { X: endCoords.X, Y: endCoords.Y }, // prettier-ignore - { X: endCoords.X - normal.x * c, Y: endCoords.Y - normal.y * c }, // prettier-ignore - { X: endCoords.X - direction.x - normal.x + direction.x * c, Y: endCoords.Y - direction.y - normal.y + direction.y * c}, // prettier-ignore - { X: endCoords.X - direction.x - normal.x, Y: endCoords.Y - direction.y - normal.y }, // prettier-ignore - - // top - { X: endCoords.X - direction.x - normal.x, Y: endCoords.Y - direction.y - normal.y }, // prettier-ignore - { X: endCoords.X - direction.x - normal.x - direction.x * c, Y: endCoords.Y - direction.y - normal.y - direction.y * c}, // prettier-ignore - { X: startCoords.X + direction.x - normal.x + direction.x * c, Y: startCoords.Y + direction.y - normal.y + direction.y * c }, - { X: startCoords.X + direction.x - normal.x, Y: startCoords.Y + direction.y - normal.y }, // prettier-ignore - - // left top arc - { X: startCoords.X + direction.x - normal.x, Y: startCoords.Y + direction.y - normal.y }, // prettier-ignore - { X: startCoords.X + direction.x - normal.x - direction.x * c, Y: startCoords.Y + direction.y - normal.y - direction.y * c }, // prettier-ignore - { X: startCoords.X - normal.x * c, Y: startCoords.Y - normal.y * c }, // prettier-ignore - { X: startCoords.X, Y: startCoords.Y }, // prettier-ignore - ]); - }; - _subContentView: ViewBoxInterface | undefined; setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc); @computed get fillColor() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 14d20eb4a..e2965c1ba 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -738,12 +738,12 @@ export class CollectionFreeFormView extends CollectionSubView { const currPoint = { X: e.clientX, Y: e.clientY }; @@ -776,7 +776,7 @@ export class CollectionFreeFormView extends CollectionSubView this._eraserLock--); + // setTimeout(() => this._eraserLock--); } // Lower ink opacity to give the user a visual indicator of deletion. intersect.inkView.layoutDoc.opacity = 0; @@ -797,23 +797,17 @@ export class CollectionFreeFormView extends CollectionSubView { - // const currZoom = this.zoomScaling(); - // console.log("curr zoom", currZoom); - // console.log("prev zoom", this.prevZoom); 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)); - const intersections: any[] = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint); - // if (intersections[0].size > 0 && currZoom === this.prevZoom) { - const strokeMap: Map = intersections[0]; - const eraserStroke = intersections[1]; + const strokeMap: Map = 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'); - const segments = this.radiusErase(stroke, intersects.sort(), eraserStroke); + const segments = this.radiusErase(stroke, intersects.sort()); segments?.forEach(segment => this.forceStrokeGesture( e, @@ -825,12 +819,6 @@ export class CollectionFreeFormView extends CollectionSubView 0) { - // this.prevZoom = currZoom; - // console.log("skipping occurred"); - // } else if (this.prevZoom = currZoom) { - // this.prevZoom = currZoom; - // } return false; }; @@ -852,7 +840,8 @@ export class CollectionFreeFormView extends CollectionSubView { // increase radius slightly based on the erased stroke's width, added to make eraser look more realistic - var radius = ActiveEraserWidth() + inkStrokeWidth * 0.2; + var radius = ActiveEraserWidth() + 5 + inkStrokeWidth * 0.1; // add 5 to prevent eraser from being too small 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); @@ -909,7 +898,7 @@ export class CollectionFreeFormView extends CollectionSubView { var isInside = false; if (!isNaN(eraserOutline[0].X) && !isNaN(eraserOutline[0].Y)) { - let minX = eraserOutline[0].X, maxX = eraserOutline[0].X; // prettier-ignore - let minY = eraserOutline[0].Y, maxY = eraserOutline[0].Y; // prettier-ignore + let minX = eraserOutline[0].X, maxX = eraserOutline[0].X; + let minY = eraserOutline[0].Y, maxY = eraserOutline[0].Y; for (let i = 1; i < eraserOutline.length; i++) { const currPoint: { X: number; Y: number } = eraserOutline[i]; minX = Math.min(currPoint.X, minX); @@ -985,14 +974,18 @@ export class CollectionFreeFormView extends CollectionSubView { - const eraserRadius = ActiveEraserWidth() / this.zoomScaling(); - 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 }; + // set distance of the eraser's bounding box based on the zoom + var boundingBoxDist = ActiveEraserWidth() + 5; + this.zoomScaling() < 1 ? boundingBoxDist = boundingBoxDist / (this.zoomScaling() * 1.5) : boundingBoxDist *= this.zoomScaling(); + + const eraserMin = { X: Math.min(lastPoint.X, currPoint.X) - boundingBoxDist, Y: Math.min(lastPoint.Y, currPoint.Y) - boundingBoxDist }; + const eraserMax = { X: Math.max(lastPoint.X, currPoint.X) + boundingBoxDist, Y: Math.max(lastPoint.Y, currPoint.Y) + boundingBoxDist }; const strokeToTVals = new Map(); const intersectingStrokes = this.childDocs .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())) @@ -1006,13 +999,12 @@ export class CollectionFreeFormView extends CollectionSubView= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top ); - const erasers: InkData[] = []; + intersectingStrokes.forEach(({ inkStroke, inkView }) => { const { inkData, inkStrokeWidth } = inkStroke.inkScaledData(); const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); const currPointInkSpace = inkStroke.ptFromScreen(currPoint); const eraserInkData = this.createEraserOutline(prevPointInkSpace, currPointInkSpace, inkStrokeWidth).inkData; - // erasers.push(eraserInkData); // add the ends of the stroke in as "intersections" if (this.insideEraserOutline(eraserInkData, inkData[0])) { @@ -1039,7 +1031,7 @@ export class CollectionFreeFormView extends CollectionSubView Math.abs(val - (t + Math.floor(i / 4))) <= tValOffset); if (!inList) { inkList.push(t + Math.floor(i / 4)); @@ -1052,42 +1044,34 @@ export class CollectionFreeFormView extends CollectionSubView { + radiusErase = (ink: DocumentView, tVals: number[]): Segment[] => { const segments: Segment[] = []; - var eraser: Segment = []; - // for (var i = 0; i < erasers.length; i ++) { - // for (var j = 0; j < erasers[i].length - 3; j +=4) { - // eraser.push(InkField.Segment(erasers[i], j)); - // } - // segments.push(eraser); - // eraser = []; - // } - const inkStroke = ink?.ComponentView as InkingStroke; const { inkData } = inkStroke.inkScaledData(); var currSegment: Segment = []; + + // any radius erase stroke will always result in even tVals, since the ends are included if (tVals.length % 2 !== 0) { - // any radius erase stroke will always result in even tVals, since the ends are included for (var i = 0; i < inkData.length - 3; i += 4) { currSegment.push(InkField.Segment(inkData, i)); } segments.push(currSegment); - return segments; // want to return the full original stroke + return segments; // return the full original stroke } var continueErasing = false; // used to erase segments if they are completely enclosed in the eraser - var firstSegment: Segment = []; // used to keep track of the first segment for closed curves + var firstSegment: Segment = []; // used to keep track of the first segment for closed curves // early return if nothing to split on if (tVals.length === 0 || (tVals.length === 1 && tVals[0] === 0)) { @@ -1108,15 +1092,18 @@ export class CollectionFreeFormView extends CollectionSubView 0) { for (var j = 0; j < segmentTs.length; j++) { - if (segmentTs[j] === 0) { // if the first end of the segment is within the eraser + if (segmentTs[j] === 0) { + // if the first end of the segment is within the eraser continueErasing = true; - } else if (segmentTs[j] === Math.floor(inkData.length / 4) + 1) { // the last end + } else if (segmentTs[j] === Math.floor(inkData.length / 4) + 1) { + // the last end break; } else { if (!continueErasing) { currSegment.push(inkBezier.split(0, segmentTs[j] - currCurveT)); continueErasing = true; - } else { // we've reached the end of the part to take out... + } else { + // we've reached the end of the part to take out... continueErasing = false; if (currSegment.length > 0) { segments.push(currSegment); // ...so we add it to the list and reset currSegment @@ -1168,7 +1155,7 @@ export class CollectionFreeFormView extends CollectionSubView tVal > 0 && tVal < 1).map(tVal => tVal + Math.floor(i / 4)); if (currIntersects.length) { intersections = [...intersections, ...currIntersects]; @@ -1181,6 +1168,9 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -2045,14 +2043,19 @@ export class CollectionFreeFormView extends CollectionSubView { this._eraserX = e.clientX; this._eraserY = e.clientY; - if (this._eraserX < 0 || this._eraserY < 0) { - this._showEraserCircle = false; - } else { - this._showEraserCircle = true; - } // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); }; + @action + onMouseLeave = () => { + this._showEraserCircle = false; + }; + + @action + onMouseEnter = () => { + this._showEraserCircle = true; + }; + @undoBatch promoteCollection = () => { const childDocs = this.childDocs.slice(); @@ -2321,6 +2324,8 @@ export class CollectionFreeFormView extends CollectionSubView - {Doc.ActiveTool === InkTool.RadiusEraser && ( + {(Doc.ActiveTool === InkTool.RadiusEraser && this._showEraserCircle) && (
any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ ['inkMask', { -- cgit v1.2.3-70-g09d2 From 2ab3fd16d01863300bcdb1cf076b6f5c81daf867 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 May 2024 23:42:41 -0400 Subject: from last --- src/fields/Doc.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 2d3dddc8b..1b3d963e8 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -185,6 +185,7 @@ export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.acti export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore +export function ActiveEraserWidth(): number { return Number(ActiveInkPen()?.eraserWidth); } // prettier-ignore export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); @@ -216,6 +217,9 @@ export function SetActiveArrowScale(value: number) { export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); } +export function SetEraserWidth(width: number): void { + ActiveInkPen() && (ActiveInkPen().eraserWidth = width); +} @scriptingGlobal @Deserializable('Doc', updateCachedAcls, ['id']) -- cgit v1.2.3-70-g09d2