diff options
Diffstat (limited to 'src/client/views/InkControlPtHandles.tsx')
-rw-r--r-- | src/client/views/InkControlPtHandles.tsx | 99 |
1 files changed, 77 insertions, 22 deletions
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index f24dab949..24f796105 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -6,13 +6,14 @@ import { ControlPoint, InkData, PointData, InkField } from "../../fields/InkFiel import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast, NumCast } from "../../fields/Types"; -import { setupMoveUpEvents } from "../../Utils"; +import { setupMoveUpEvents, returnFalse } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; import { InkingStroke } from "./InkingStroke"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { DocumentView } from "./nodes/DocumentView"; +import { SelectionManager } from "../util/SelectionManager"; export interface InkControlProps { inkDoc: Doc; @@ -44,23 +45,24 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { @action onControlDown = (e: React.PointerEvent, controlIndex: number): void => { const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen; - if (InkStrokeProperties.Instance && ptFromScreen) { + if (ptFromScreen) { const order = controlIndex % 4; const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); - const wasSelected = InkStrokeProperties.Instance?._currentPoint === controlIndex; + const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex; + const origInk = this.props.inkCtrlPoints.slice(); setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] }); const inkMoveStart = ptFromScreen({ X: 0, Y: 0 }); - InkStrokeProperties.Instance?.moveControlPtHandle(this.props.inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex); + InkStrokeProperties.Instance.moveControlPtHandle(this.props.inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk); return false; }), action(() => { if (this.controlUndo) { - InkStrokeProperties.Instance?.snapControl(this.props.inkView, controlIndex); + InkStrokeProperties.Instance.snapControl(this.props.inkView, controlIndex); } this.controlUndo?.end(); this.controlUndo = undefined; @@ -75,11 +77,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { } else { if (brokenIndices?.includes(equivIndex)) { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB); + InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB); } if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB); + InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB); } } this.controlUndo?.end(); @@ -102,7 +104,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { @action onDelete = (e: KeyboardEvent) => { if (["-", "Backspace", "Delete"].includes(e.key)) { - InkStrokeProperties.Instance?.deletePoints(this.props.inkView); + InkStrokeProperties.Instance.deletePoints(this.props.inkView, e.shiftKey); e.stopPropagation(); } } @@ -111,11 +113,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { * Changes the current selected control point. */ @action - changeCurrPoint = (i: number) => { - if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance._currentPoint = i; - } - } + changeCurrPoint = (i: number) => InkStrokeProperties.Instance._currentPoint = i render() { // Accessing the current ink's data and extracting all control points. @@ -133,7 +131,6 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 }); } - const screenSpaceLineWidth = this.props.screenSpaceLineWidth; const closed = InkingStroke.IsClosed(inkData); const nearestScreenPt = this.props.nearestScreenPt(); const TagType = (broken?: boolean) => broken ? "rect" : "circle"; @@ -141,18 +138,18 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"))?.includes(control.I); const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements; return <Tag key={control.I.toString() + scale} - x={control.X - screenSpaceLineWidth * 2 * scale} - y={control.Y - screenSpaceLineWidth * 2 * scale} + x={control.X - this.props.screenSpaceLineWidth * 2 * scale} + y={control.Y - this.props.screenSpaceLineWidth * 2 * scale} cx={control.X} cy={control.Y} - r={screenSpaceLineWidth * 2 * scale} + r={this.props.screenSpaceLineWidth * 2 * scale} opacity={this.controlUndo ? 0.15 : 1} - height={screenSpaceLineWidth * 4 * scale} - width={screenSpaceLineWidth * 4 * scale} - strokeWidth={screenSpaceLineWidth / 2} + height={this.props.screenSpaceLineWidth * 4 * scale} + width={this.props.screenSpaceLineWidth * 4 * scale} + strokeWidth={this.props.screenSpaceLineWidth / 2} stroke={Colors.MEDIUM_BLUE} fill={broken ? Colors.MEDIUM_BLUE : color} - onPointerDown={(e: any) => this.onControlDown(e, control.I)} + onPointerDown={(e: React.PointerEvent) => this.onControlDown(e, control.I)} onMouseEnter={() => this.onEnterControl(control.I)} onMouseLeave={this.onLeaveControl} pointerEvents="all" @@ -164,7 +161,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { <circle key={"npt"} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} - r={screenSpaceLineWidth * 2} + r={this.props.screenSpaceLineWidth * 2} fill={"#00007777"} stroke={"#00007777"} strokeWidth={0} @@ -175,4 +172,62 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { </svg> ); } +} + + +export interface InkEndProps { + inkDoc: Doc; + inkView: DocumentView; + screenSpaceLineWidth: number; + startPt: PointData; + endPt: PointData; +} +@observer +export class InkEndPtHandles extends React.Component<InkEndProps> { + @observable controlUndo: UndoManager.Batch | undefined; + @observable _overStart: boolean = false; + @observable _overEnd: boolean = false; + + @action + dragRotate = (e: React.PointerEvent, p1: () => { X: number, Y: number }, p2: () => { X: number, Y: number }) => { + setupMoveUpEvents(this, e, (e) => { + if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("stretch ink"); + // compute stretch factor by finding scaling along axis between start and end points + const v1 = { X: p1().X - p2().X, Y: p1().Y - p2().Y }; + const v2 = { X: e.clientX - p2().X, Y: e.clientY - p2().Y }; + const v1len = Math.sqrt(v1.X * v1.X + v1.Y * v1.Y); + const v2len = Math.sqrt(v2.X * v2.X + v2.Y * v2.Y); + const scaling = v2len / v1len; + const v1n = { X: v1.X / v1len, Y: v1.Y / v1len }; + const v2n = { X: v2.X / v2len, Y: v2.Y / v2len }; + const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y); + InkStrokeProperties.Instance.stretchInk(SelectionManager.Views(), scaling, p2(), v1n, e.shiftKey); + InkStrokeProperties.Instance.rotateInk(SelectionManager.Views(), angle, p2()); + return false; + }, action(() => { + this.controlUndo?.end(); + this.controlUndo = undefined; + UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); + }), returnFalse); + } + + render() { + const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => <circle key={key} + cx={pt.X} + cy={pt.Y} + r={this.props.screenSpaceLineWidth * 2} + fill={this._overStart ? "#aaaaaa" : "#99999977"} + stroke={"#00007777"} + strokeWidth={0} + onPointerLeave={action(() => this._overStart = false)} + onPointerEnter={action(() => this._overStart = true)} + onPointerDown={dragFunc} + pointerEvents="all" + />; + return (<svg> + {hdl("start", this.props.startPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.startPt, () => this.props.endPt))} + {hdl("end", this.props.endPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.endPt, () => this.props.startPt))} + </svg> + ); + } }
\ No newline at end of file |