diff options
| author | eleanor-park <eleanor_park@brown.edu> | 2024-06-11 11:15:25 -0400 |
|---|---|---|
| committer | eleanor-park <eleanor_park@brown.edu> | 2024-06-11 11:15:25 -0400 |
| commit | 33761fc2227458acf36a5cc4b1f08eaae6e58695 (patch) | |
| tree | 040cd6388d37f77bd9b3704d1b80443e0b3784ed /src/client/views/collections/collectionFreeForm | |
| parent | 2277349fc4d5460e94a7a6b705b56488c0efb184 (diff) | |
some changes
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 170 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx | 31 |
2 files changed, 121 insertions, 80 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d22b3569e..e66dbd796 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -144,7 +144,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection : this._props.childPointerEvents?.() ?? (this._props.viewDefDivClick || // (this.layoutEngine === computePassLayout.name && !this._props.isSelected()) || - this.isContentActive() === false + this.isContentActive() === false || + Doc.ActiveTool === InkTool.RadiusEraser || + Doc.ActiveTool === InkTool.SegmentEraser || + Doc.ActiveTool === InkTool.StrokeEraser ? 'none' : this._props.pointerEvents?.()); } @@ -501,27 +504,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); switch (Doc.ActiveTool) { case InkTool.Highlighter: - break; case InkTool.Write: - break; case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views case InkTool.StrokeEraser: case InkTool.SegmentEraser: - this._batch = UndoManager.StartBatch('collectionErase'); - this._eraserPts.length = 0; - setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction, hit !== -1, false); - break; case InkTool.RadiusEraser: this._batch = UndoManager.StartBatch('collectionErase'); this._eraserPts.length = 0; - setupMoveUpEvents(this, e, this.onRadiusEraserMove, this.onEraserUp, emptyFunction, hit !== -1, false); + setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, this.onEraserClick, hit !== -1); + e.stopPropagation(); break; case InkTool.SmartDraw: - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, this.createDrawing, hit !== -1, false); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, this.createDrawing, hit !== -1); + e.stopPropagation(); case InkTool.None: if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1); + e.stopPropagation(); } break; default: @@ -608,50 +608,83 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection _eraserLock = 0; _eraserPts: number[][] = []; // keep track of the last few eraserPts to make the eraser circle 'stretch' - /** - * Erases strokes by intersecting them with an invisible "eraser stroke". - * By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments, - * and deletes the original stroke. - */ - @action - onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => { + erase = (e: PointerEvent, 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; // 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); - SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1'); - 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++; - 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[]); - const bounds = InkField.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', + if (Doc.ActiveTool === InkTool.RadiusEraser) { + const strokeMap: Map<DocumentView, number[]> = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint); + + strokeMap.forEach((intersects, stroke) => { + if (!this._deleteList.includes(stroke)) { + this._deleteList.push(stroke); + SetActiveInkWidth(StrCast(stroke.Document.stroke_width?.toString()) || '1'); + SetActiveInkColor(StrCast(stroke.Document.color?.toString()) || 'black'); + const segments = this.radiusErase(stroke, intersects.sort()); + segments?.forEach(segment => + this.forceStrokeGesture( + e, + 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[]) + ) + ); + } + stroke.layoutDoc.opacity = 0; + stroke.layoutDoc.dontIntersect = true; + }); + } else { + 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); + SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1'); + 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++; + 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[]); + const bounds = InkField.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--); + inkWidth + ); + }); + newStrokes && this.addDocument?.(newStrokes); + // setTimeout(() => this._eraserLock--); + } + // Lower ink opacity to give the user a visual indicator of deletion. + intersect.inkView.layoutDoc.opacity = 0; + intersect.inkView.layoutDoc.dontIntersect = true; } - // Lower ink opacity to give the user a visual indicator of deletion. - intersect.inkView.layoutDoc.opacity = 0; - intersect.inkView.layoutDoc.dontIntersect = true; - } - }); + }); + } + return false; + }; + + /** + * Erases strokes by intersecting them with an invisible "eraser stroke". + * By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments, + * and deletes the original stroke. + */ + @action + onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => { + this.erase(e, delta); + // if (this._eraserLock) return false; // leaving this commented out in case the idea is revisited in the future + return false; + }; + + @action + onEraserClick = (e: PointerEvent, doubleTap?: boolean) => { + this.erase(e, [0, 0]); return false; }; @@ -720,7 +753,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // increase radius slightly based on the erased stroke's width, added to make eraser look more realistic const 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 movement = { x: Math.max(endInkCoordsIn.X - startInkCoordsIn.X, 1), y: Math.max(endInkCoordsIn.Y - startInkCoordsIn.Y, 1) }; 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 @@ -1232,28 +1265,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action createDrawing = (e: PointerEvent, doubleTap?: boolean) => { - SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY); - if (SmartDrawHandler.Instance.strokes.length > 0) { - const strokeList: InkData[] = SmartDrawHandler.Instance.strokes; - strokeList.forEach(coords => { - // const stroke = new InkField(coords); - // const points = coords.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.X, Y: p.Y }) ?? { X: 0, Y: 0 }), [] as PointData[]); - const bounds = InkField.getBounds(coords); - const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height); - const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale; - const inkDoc = Docs.Create.InkDocument( - coords, - { title: 'stroke', + SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY, this.createInkStroke); + }; + + @action + createInkStroke = (strokeList: InkData[]) => { + strokeList.forEach(coords => { + // const stroke = new InkField(coords); + // const points = coords.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.X, Y: p.Y }) ?? { X: 0, Y: 0 }), [] as PointData[]); + const bounds = InkField.getBounds(coords); + const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height); + const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale; + const inkDoc = Docs.Create.InkDocument( + coords, + { 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 - ); - this.addDocument(inkDoc); - }); - } + inkWidth + ); + this.addDocument(inkDoc); + }); }; @action @@ -1849,8 +1883,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action onCursorMove = (e: React.PointerEvent) => { - this._eraserX = e.clientX; - this._eraserY = e.clientY; + const locPt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY); + this._eraserX = locPt[0]; + this._eraserY = locPt[1]; + // Doc.ActiveTool === InkTool.RadiusEraser ? this._childPointerEvents = 'none' : this._childPointerEvents = 'all' // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); }; @@ -2161,8 +2197,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onPointerMove={this.onCursorMove} style={{ position: 'fixed', - left: this._eraserX - 60, - top: this._eraserY - 100, + left: this._eraserX, + top: this._eraserY, width: (ActiveEraserWidth() + 5) * 2, height: (ActiveEraserWidth() + 5) * 2, borderRadius: '50%', diff --git a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx index 7e66a62d4..fc8f7a429 100644 --- a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx +++ b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx @@ -10,7 +10,6 @@ import { AiOutlineSend } from 'react-icons/ai'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import './ImageLabelHandler.scss'; import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT'; -import { InkingStroke } from '../../InkingStroke'; import { InkData } from '../../../../fields/InkField'; @observer @@ -23,7 +22,8 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { @observable private _yRelativeToTop: boolean = true; @observable private _isLoading: boolean = false; @observable private _userInput: string = ''; - @observable public strokes: InkData[] = []; + // @observable public strokes: InkData[] = []; + private _addToDocFunc: (strokeList: InkData[]) => void = () => {}; constructor(props: any) { super(props); @@ -42,10 +42,11 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { }; @action - displaySmartDrawHandler = (x: number, y: number) => { + displaySmartDrawHandler = (x: number, y: number, addToDoc: (strokeList: InkData[]) => void) => { this._pageX = x; this._pageY = y; this._display = true; + this._addToDocFunc = addToDoc; }; @action @@ -54,8 +55,11 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { }; @action - drawWithGPT = async (startPoint: {X: number, Y: number}, input: string) => { - console.log("start point is", startPoint); + waitForCoords = async () => {}; + + @action + drawWithGPT = async (startPoint: { X: number; Y: number }, input: string) => { + console.log('start point is', startPoint); this.setIsLoading(true); try { const res = await gptAPICall(input, GPTCallType.DRAW); @@ -63,8 +67,7 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { console.error('GPT call failed'); return; } - console.log("GPT response:", res); - try { + console.log('GPT response:', res); // const controlPts: [number, number][][] = JSON.parse(res) as [number, number][][]; // console.log("Control Points", controlPts); // const transformedPts: { X: number; Y: number }[][] = []; @@ -74,15 +77,17 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { // }); // transformedPts.push(stroke); // }); + const simplifiedRes: string = res.replace(/[^\d\[\],]/g, ''); + console.log(simplifiedRes) + try { + const controlPts: { X: number; Y: number }[][] = JSON.parse(simplifiedRes).map((stroke: [number, number][]) => stroke.map(([X, Y]) => ({ X: X + startPoint.X, Y: Y + startPoint.Y }))); + console.log('transformed points', controlPts); - const controlPts: { X: number; Y: number }[][] = JSON.parse(res).map((stroke: [number, number][]) => - stroke.map(([X, Y]) => ({ X: X + startPoint.X, Y: Y + startPoint.Y }))); - console.log("transformed points", controlPts); - this.strokes = controlPts; + // this.strokes = controlPts; + this._addToDocFunc(controlPts); } catch (err) { console.error('Incompatible GPT output type'); } - } catch (err) { console.error('GPT call failed', err); } @@ -124,7 +129,7 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { iconPlacement="right" color={MarqueeOptionsMenu.Instance.userColor} onClick={e => { - this.drawWithGPT({X: e.clientX, Y: e.clientY}, this._userInput); + this.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._userInput); }} /> {/* <IconButton |
