diff options
-rw-r--r-- | src/client/views/InkHandles.tsx | 32 | ||||
-rw-r--r-- | src/client/views/InkStrokeProperties.ts | 6 | ||||
-rw-r--r-- | src/fields/InkField.ts | 2 |
3 files changed, 32 insertions, 8 deletions
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx index c2163c124..993e427b3 100644 --- a/src/client/views/InkHandles.tsx +++ b/src/client/views/InkHandles.tsx @@ -15,6 +15,8 @@ export interface InkControlProps { @observer export class InkHandles extends React.Component<InkControlProps> { + @observable private _brokenIndices: number[] = []; + /** * Handles the movement of a selected handle point when the user clicks and drags. * @param handleNum The index of the currently selected handle point. @@ -24,13 +26,25 @@ export class InkHandles extends React.Component<InkControlProps> { InkStrokeProperties.Instance.moveControl(0, 0, 1); const controlUndo = UndoManager.StartBatch("DocDecs set radius"); const screenScale = this.props.ScreenToLocalTransform().Scale; + document.addEventListener("keydown", (e: KeyboardEvent) => this.onBreakTangent(e, handleNum), true); setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { - InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleNum); + InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleNum, this._brokenIndices); return false; }, () => controlUndo?.end(), emptyFunction ); } } + + @action + onBreakTangent = (e: KeyboardEvent, handleIndex: number) => { + if (["Alt"].includes(e.key)) { + const order = handleIndex % 4; + const oppositeHandleIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; + if (!this._brokenIndices.includes(handleIndex) && !this._brokenIndices.includes(oppositeHandleIndex)) { + this._brokenIndices.push(handleIndex, oppositeHandleIndex); + } + } + } render() { const formatInstance = InkStrokeProperties.Instance; @@ -39,15 +53,15 @@ export class InkHandles extends React.Component<InkControlProps> { const handlePoints: HandlePoint[] = []; const handleLines: HandleLine[] = []; if (data.length >= 4) { - // adding first and last (single) handle lines - handleLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[1].X, Y2: data[1].Y, dot1: 0, dot2: 0 }); - handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); for (let i = 0; i <= data.length - 4; i += 4) { handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 }); handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 }); } + // adding first and last (single) handle lines + handleLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); + handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); for (let i = 2; i < data.length - 4; i += 4) { - handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 3].X, Y2: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); + handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); } } const [left, top, scaleX, scaleY, strokeWidth, dotsize] = this.props.format; @@ -77,6 +91,14 @@ export class InkHandles extends React.Component<InkControlProps> { stroke="#1F85DE" strokeWidth={dotsize / 8} display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> + <line + x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} + y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} + x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} + y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} + stroke="#1F85DE" + strokeWidth={dotsize / 8} + display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> </svg>)} </> ); diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index a5c028730..812e8ff6e 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -220,15 +220,15 @@ export class InkStrokeProperties { */ @undoBatch @action - moveHandle = (deltaX: number, deltaY: number, handleIndex: number) => + moveHandle = (deltaX: number, deltaY: number, handleIndex: number, brokenIndices: number[]) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const order = handleIndex % 4; const oldHandlePoint = ink[handleIndex]; const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; ink[handleIndex] = newHandlePoint; - // Rotating opposite handle (first and final control point only have one handle). - if (handleIndex !== 1 && handleIndex !== ink.length - 2) { + // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). + if (!brokenIndices.includes(handleIndex) && handleIndex !== 1 && handleIndex !== ink.length - 2) { let oppositeHandlePoint = order === 1 ? ink[handleIndex - 3] : ink[handleIndex + 3]; const controlPoint = order === 1 ? ink[handleIndex - 1] : ink[handleIndex + 1]; const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index c158dac42..485376a34 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -42,6 +42,8 @@ export interface HandleLine { Y1: number; X2: number; Y2: number; + X3: number; + Y3: number; dot1: number; dot2: number; } |