aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkStrokeProperties.ts
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-08-29 13:07:53 -0400
committerbobzel <zzzman@gmail.com>2024-08-29 13:07:53 -0400
commit0ac79ba6a7ab19b4aafbc11dac9bab4781d4bd40 (patch)
tree4d5774dc4247782ccb6c99b018ca8e0c91187378 /src/client/views/InkStrokeProperties.ts
parentf7cdcb654e83d7fdbfd0b1cfc80c485bb9554f08 (diff)
merge cleanup cleanup
Diffstat (limited to 'src/client/views/InkStrokeProperties.ts')
-rw-r--r--src/client/views/InkStrokeProperties.ts268
1 files changed, 135 insertions, 133 deletions
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index ffda126f4..b5cd72fa7 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -9,7 +9,7 @@ import { Cast, NumCast, toList } from '../../fields/Types';
import { Gestures, PointData } from '../../pen-gestures/GestureTypes';
import { Point } from '../../pen-gestures/ndollar';
import { DocumentType } from '../documents/DocumentTypes';
-import { undoBatch } from '../util/UndoManager';
+import { undoBatch, undoable } from '../util/UndoManager';
import { FitOneCurve } from '../util/bezierFit';
import { InkingStroke } from './InkingStroke';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
@@ -92,8 +92,7 @@ export class InkStrokeProperties {
* @param i index of first control point of segment being split
* @param control The list of all control points of the ink.
*/
- @undoBatch
- addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => {
+ addPoints = undoable((inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => {
this.applyFunction(inkView, (view: DocumentView /* , ink: InkData */) => {
const doc = view.Document;
const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]];
@@ -109,7 +108,7 @@ export class InkStrokeProperties {
return controls;
});
- };
+ }, 'add ink points');
/**
* Scales a handle point of a control point that is adjacent to a newly added one.
@@ -164,46 +163,48 @@ export class InkStrokeProperties {
/**
* Deletes the current control point of the selected ink instance.
*/
- @undoBatch
- deletePoints = (inkView: DocumentView, preserve: boolean) =>
- this.applyFunction(
- inkView,
- (view: DocumentView, ink: InkData) => {
- const doc = view.Document;
- const newPoints = ink.slice();
- const brokenIndices = NumListCast(doc.brokenInkIndices);
- if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) {
- newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4);
- } else {
- const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
- const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
- const samples: Point[] = [];
- let startDir = { x: 0, y: 0 };
- let endDir = { x: 0, y: 0 };
- for (let i = 0; i < splicedPoints.length / 4; i++) {
- const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
- if (i === 0) startDir = bez.derivative(0);
- if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
- for (let t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
- const pt = bez.compute(t);
- samples.push(new Point(pt.x, pt.y));
- }
- }
- const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- if (error < 100) {
- newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls);
+ deletePoints = undoable(
+ (inkView: DocumentView, preserve: boolean) =>
+ this.applyFunction(
+ inkView,
+ (view: DocumentView, ink: InkData) => {
+ const doc = view.Document;
+ const newPoints = ink.slice();
+ const brokenIndices = NumListCast(doc.brokenInkIndices);
+ if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) {
+ newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4);
} else {
- newPoints.splice(this._currentPoint - 2, 4);
+ const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
+ const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
+ const samples: Point[] = [];
+ let startDir = { x: 0, y: 0 };
+ let endDir = { x: 0, y: 0 };
+ for (let i = 0; i < splicedPoints.length / 4; i++) {
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ if (i === 0) startDir = bez.derivative(0);
+ if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
+ for (let t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
+ const pt = bez.compute(t);
+ samples.push(new Point(pt.x, pt.y));
+ }
+ }
+ const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ if (error < 100) {
+ newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls);
+ } else {
+ newPoints.splice(this._currentPoint - 2, 4);
+ }
}
- }
- doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control)));
- runInAction(() => {
- this._currentPoint = -1;
- });
- return newPoints.length < 4 ? undefined : newPoints;
- },
- true
- );
+ doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control)));
+ runInAction(() => {
+ this._currentPoint = -1;
+ });
+ return newPoints.length < 4 ? undefined : newPoints;
+ },
+ true
+ ),
+ 'delete ink points'
+ );
/**
* Rotates ink stroke(s) about a point
@@ -211,8 +212,7 @@ export class InkStrokeProperties {
* @param angle The angle at which to rotate the ink in radians.
* @param scrpt The center point of the rotation in screen coordinates
*/
- @undoBatch
- rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: PointData) => {
+ rotateInk = undoable((inkStrokes: DocumentView[], angle: number, scrpt: PointData) => {
this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number /* , inkStrokeWidth: number */) => {
const inkCenterPt = view.ComponentView?.ptFromScreen?.(scrpt);
return !inkCenterPt
@@ -224,7 +224,7 @@ export class InkStrokeProperties {
return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y };
});
});
- };
+ }, 'rotate ink');
/**
* Rotates ink stroke(s) about a point
@@ -232,8 +232,7 @@ export class InkStrokeProperties {
* @param angle The angle at which to rotate the ink in radians.
* @param scrpt The center point of the rotation in screen coordinates
*/
- @undoBatch
- stretchInk = (inkStrokes: DocumentView[], scaling: number, scrpt: PointData, scrVec: PointData, scaleUniformly: boolean) => {
+ stretchInk = undoable((inkStrokes: DocumentView[], scaling: number, scrpt: PointData, scrVec: PointData, scaleUniformly: boolean) => {
this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData) => {
const ptFromScreen = view.ComponentView?.ptFromScreen;
const ptToScreen = view.ComponentView?.ptToScreen;
@@ -247,77 +246,79 @@ export class InkStrokeProperties {
return ptFromScreen(newscrpt);
});
});
- };
+ }, 'stretch ink');
/**
* Handles the movement/scaling of a control point.
*/
- @undoBatch
- moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) =>
- inkView &&
- this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
- const order = controlIndex % 4;
- const closed = InkingStroke.IsClosed(ink);
- const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []);
- if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
- const cptBefore = ink[controlIndex];
- const cpt = { X: cptBefore.X + deltaX, Y: cptBefore.Y + deltaY };
- const newink = origInk.slice();
- const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
- const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
- const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
- if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1) || nearestSeg < 0) return ink.slice();
- const samplesLeft: Point[] = [];
- const samplesRight: Point[] = [];
- let startDir = { x: 0, y: 0 };
- let endDir = { x: 0, y: 0 };
- for (let i = 0; i < nearestSeg / 4 + 1; i++) {
- const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
- if (i === 0) startDir = bez.derivative(_.isEqual(bez.derivative(0), { x: 0, y: 0, t: 0 }) ? 1e-8 : 0);
- if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
- for (let t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) {
- const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
- samplesLeft.push(new Point(pt.x, pt.y));
+ moveControlPtHandle = undoable(
+ (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) =>
+ inkView &&
+ this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+ const order = controlIndex % 4;
+ const closed = InkingStroke.IsClosed(ink);
+ const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []);
+ if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
+ const cptBefore = ink[controlIndex];
+ const cpt = { X: cptBefore.X + deltaX, Y: cptBefore.Y + deltaY };
+ const newink = origInk.slice();
+ const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
+ const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
+ const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
+ if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1) || nearestSeg < 0) return ink.slice();
+ const samplesLeft: Point[] = [];
+ const samplesRight: Point[] = [];
+ let startDir = { x: 0, y: 0 };
+ let endDir = { x: 0, y: 0 };
+ for (let i = 0; i < nearestSeg / 4 + 1; i++) {
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ if (i === 0) startDir = bez.derivative(_.isEqual(bez.derivative(0), { x: 0, y: 0, t: 0 }) ? 1e-8 : 0);
+ if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
+ for (let t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) {
+ const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
+ samplesLeft.push(new Point(pt.x, pt.y));
+ }
}
- }
- let { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- for (let i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
- const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
- if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
- if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(_.isEqual(bez.derivative(1), { x: 0, y: 0, t: 1 }) ? 1 - 1e-8 : 1);
- for (let t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) {
- const pt = bez.compute(Math.min(1, t));
- samplesRight.push(new Point(pt.x, pt.y));
+ let { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ for (let i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
+ if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(_.isEqual(bez.derivative(1), { x: 0, y: 0, t: 1 }) ? 1 - 1e-8 : 1);
+ for (let t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) {
+ const pt = bez.compute(Math.min(1, t));
+ samplesRight.push(new Point(pt.x, pt.y));
+ }
}
+ const { finalCtrls: rightCtrls /* , error: errorRight */ } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ finalCtrls = finalCtrls.concat(rightCtrls);
+ newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
+ return newink;
}
- const { finalCtrls: rightCtrls /* , error: errorRight */ } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- finalCtrls = finalCtrls.concat(rightCtrls);
- newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
- return newink;
- }
- return ink.map((pt, i) => {
- const leftHandlePoint = order === 0 && i === controlIndex + 1;
- const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2;
- if (controlIndex === i || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || (order === 3 && i === controlIndex - 1)) {
- return { X: pt.X + deltaX, Y: pt.Y + deltaY };
- }
- if (
- controlIndex === i ||
- leftHandlePoint ||
- rightHandlePoint ||
- (order === 0 && controlIndex !== 0 && i === controlIndex - 1) ||
- ((order === 0 || order === 3) && (controlIndex === 0 || controlIndex === ink.length - 1) && (i === 1 || i === ink.length - 2) && closed) ||
- (order === 3 && i === controlIndex - 1) ||
- (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) ||
- (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) ||
- (ink[0].X === ink[ink.length - 1].X && ink[0].Y === ink[ink.length - 1].Y && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))
- ) {
- return { X: pt.X + deltaX, Y: pt.Y + deltaY };
- }
- return pt;
- });
- });
+ return ink.map((pt, i) => {
+ const leftHandlePoint = order === 0 && i === controlIndex + 1;
+ const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2;
+ if (controlIndex === i || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || (order === 3 && i === controlIndex - 1)) {
+ return { X: pt.X + deltaX, Y: pt.Y + deltaY };
+ }
+ if (
+ controlIndex === i ||
+ leftHandlePoint ||
+ rightHandlePoint ||
+ (order === 0 && controlIndex !== 0 && i === controlIndex - 1) ||
+ ((order === 0 || order === 3) && (controlIndex === 0 || controlIndex === ink.length - 1) && (i === 1 || i === ink.length - 2) && closed) ||
+ (order === 3 && i === controlIndex - 1) ||
+ (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) ||
+ (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) ||
+ (ink[0].X === ink[ink.length - 1].X && ink[0].Y === ink[ink.length - 1].Y && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))
+ ) {
+ return { X: pt.X + deltaX, Y: pt.Y + deltaY };
+ }
+ return pt;
+ });
+ }),
+ 'move ink ctrl pt'
+ );
public static nearestPtToStroke(ctrlPoints: { X: number; Y: number }[], refInkSpacePt: { X: number; Y: number }, excludeSegs?: number[]) {
let distance = Number.MAX_SAFE_INTEGER;
@@ -470,26 +471,28 @@ export class InkStrokeProperties {
/**
* Handles the movement/scaling of a handle point.
*/
- @undoBatch
- moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
- this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
- const doc = view.Document;
- const closed = InkingStroke.IsClosed(ink);
- const oldHandlePoint = ink[handleIndex];
- const oppositeHandlePoint = ink[oppositeHandleIndex];
- const controlPoint = ink[controlIndex];
- const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY };
- const inkCopy = ink.slice();
- inkCopy[handleIndex] = newHandlePoint;
- const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'));
- const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1;
- // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle).
- if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) {
- const angle = InkStrokeProperties.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
- inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
- }
- return inkCopy;
- });
+ moveTangentHandle = undoable(
+ (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
+ this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+ const doc = view.Document;
+ const closed = InkingStroke.IsClosed(ink);
+ const oldHandlePoint = ink[handleIndex];
+ const oppositeHandlePoint = ink[oppositeHandleIndex];
+ const controlPoint = ink[controlIndex];
+ const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY };
+ const inkCopy = ink.slice();
+ inkCopy[handleIndex] = newHandlePoint;
+ const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'));
+ const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1;
+ // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle).
+ if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) {
+ const angle = InkStrokeProperties.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
+ inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
+ }
+ return inkCopy;
+ }),
+ 'move ink tangent'
+ );
/**
* Function that "smooths" ink strokes by using the gesture recognizer to detect shapes and
@@ -497,8 +500,7 @@ export class InkStrokeProperties {
* @param inkDocs
* @param tolerance Determines how strong the smooth effect will be
*/
- @undoBatch
- smoothInkStrokes = (inkDocs: Doc[], tolerance: number = 5) => {
+ smoothInkStrokes = undoable((inkDocs: Doc[], tolerance: number = 5) => {
inkDocs.forEach(inkDoc => {
const inkView = DocumentView.getDocumentView(inkDoc);
const inkStroke = inkView?.ComponentView as InkingStroke;
@@ -530,5 +532,5 @@ export class InkStrokeProperties {
}
}
});
- };
+ }, 'smooth ink stroke');
}