aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx152
1 files changed, 102 insertions, 50 deletions
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<Partial<collection
private _panZoomTransitionTimer: any;
private _lastX: number = 0;
private _lastY: number = 0;
- @observable _eraserX: number = 0;
- @observable _eraserY: number = 0;
private _downX: number = 0;
private _downY: number = 0;
private _downTime = 0;
@@ -107,6 +105,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _batch: UndoManager.Batch | undefined = undefined;
private _brushtimer: any;
private _brushtimer1: any;
+ private prevZoom: number = 0;
public get isAnnotationOverlay() {
return this._props.isAnnotationOverlay;
@@ -134,6 +133,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _marqueeViewRef = React.createRef<MarqueeView>();
@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<Partial<collection
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.
+ // if (this._eraserLock) return false; // leaving this commented out in case the idea is revisited in the future
this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
if (!this._deleteList.includes(intersect.inkView)) {
this._deleteList.push(intersect.inkView);
@@ -755,7 +757,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
SetActiveInkColor(StrCast(intersect.inkView.Document.color?.toString()) || 'black');
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
if (Doc.ActiveTool !== InkTool.StrokeEraser) {
- this._eraserLock++;
+ // this._eraserLock++;
const segments = this.segmentErase(intersect.inkView, intersect.t); // intersect.t is where the eraser intersected the ink stroke - want to remove the segment that starts at the intersection just before this t value and goes to the one just after it
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[]);
@@ -784,23 +786,34 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return false;
};
+ /**
+ * Erases strokes by intersecting them with a circle of variable radius. Essentially creates an InkField for the
+ * eraser circle, then determines its intersections with other ink strokes. Each stroke's DocumentView and its
+ * intersection t-values are put into a map, which gets looped through to take out the erased parts.
+ * @param e
+ * @param down
+ * @param delta
+ * @returns
+ */
@action
onRadiusEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
- // 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<DocumentView, number[]> = 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<DocumentView, number[]> = 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<Partial<collection
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;
});
+ // } 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<Partial<collection
return false;
};
- createEraserOutline = (startInkCoordsIn: { X: number; Y: number }, endInkCoordsIn: { X: number; Y: number }) => {
- 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<Partial<collection
);
};
+ /**
+ * Same as getEraserIntersections but specific to the radius eraser. Populates a Map of each intersected DocumentView
+ * to the t-values where the eraser intersected it, then returns this map.
+ * @returns
+ */
getRadiusEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => {
- 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<DocumentView, number[]>();
@@ -976,12 +1006,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
eraserMax.X >= 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<Partial<collection
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) {
@@ -1006,7 +1039,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// 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 tValOffset = ActiveEraserWidth() / 1000; // 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));
@@ -1019,33 +1052,43 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
});
- return strokeToTVals;
+ return [strokeToTVals, erasers];
};
/**
- * 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.
+ * Splits the passed in ink stroke at the intersection t values, taking out the erased parts.
+ * 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, tVals: number[]): Segment[] => {
+ 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<Partial<collection
return segments;
}
+ // loop through all segments of an ink stroke, string together the pieces, excluding the erased parts,
+ // and push each piece we want to keep to the return list
for (var i = 0; i < inkData.length - 3; i += 4) {
const currCurveT = Math.floor(i / 4);
const inkBezier: Bezier = InkField.Segment(inkData, i);
+ // filter to this segment's t-values
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) {
+ 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<Partial<collection
}
}
}
+
if (currSegment.length > 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<Partial<collection
}
}
+ // add the first segment onto the second one for closed curves, so they don't get fragmented into two pieces
if (isClosedCurve && segment1.length > 0 && segment2.length > 0) {
segment2 = segment2.concat(segment1);
segment1 = [];
@@ -1439,7 +1484,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const maxPanY =
minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
- (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
+ (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrollaing is handled via a scrollbar, not panning
let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) {
const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop;
@@ -2000,6 +2045,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onCursorMove = (e: React.PointerEvent) => {
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<Partial<collection
width: `${100 / (this.nativeDimScaling || 1)}%`,
height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
- {/* <div
- onPointerMove={this.onCursorMove}
- style={{
- position: 'fixed',
- left: this._eraserX,
- top: this._eraserY,
- width: '30px',
- height: '30px',
- borderRadius: '50%',
- // backgroundColor: 'transparent',
- border: '1px solid black',
- }}
- /> */}
+ {Doc.ActiveTool === InkTool.RadiusEraser && (
+ <div
+ onPointerMove={this.onCursorMove}
+ style={{
+ position: 'fixed',
+ left: this._eraserX - 60,
+ top: this._eraserY - 100,
+ width: ActiveEraserWidth() * 2,
+ height: ActiveEraserWidth() * 2,
+ borderRadius: '50%',
+ border: '1px solid gray',
+ transform: 'translate(-50%, -50%)',
+ }}
+ />
+ )}
{this.paintFunc ? (
<FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads
) : this._lightboxDoc ? (