aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2021-09-27 23:03:47 -0400
committerbobzel <zzzman@gmail.com>2021-09-27 23:03:47 -0400
commit1561e37eb966607564938530a71aeb7e3ba27583 (patch)
treedd48aa670f8fce0291d25dfe4c5dc48de7f7486e /src
parenteb529611c97c9936577697b829c50b4ca0736c6e (diff)
fixed ink stroke editing to work with closed curves (eg break / restore tangent & drag start/end point). changed ink rendering to use svg bezier curves. fixed bugs with keydown handler to break tangent.
Diffstat (limited to 'src')
-rw-r--r--src/client/util/InteractionUtils.tsx51
-rw-r--r--src/client/views/GestureOverlay.tsx4
-rw-r--r--src/client/views/InkControls.tsx10
-rw-r--r--src/client/views/InkHandles.tsx38
-rw-r--r--src/client/views/InkStrokeProperties.ts11
-rw-r--r--src/client/views/InkingStroke.tsx4
6 files changed, 53 insertions, 65 deletions
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 4a8011e3c..66afc849e 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -146,7 +146,7 @@ export namespace InteractionUtils {
if (shape) { //if any of the shape are true
pts = makePolygon(shape, points);
}
- else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) {
+ else if (((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) && !bezier) {
for (var i = 0; i < points.length - 3; i += 4) {
const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
for (var t = 0; t < 1; t += 0.01) {
@@ -154,47 +154,22 @@ export namespace InteractionUtils {
pts.push({ X: point[0], Y: point[1] });
}
}
- }
- else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
- //pointer is up (first and last points are the same)
- const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
- newPoints.pop();
-
- const bezierCurves = fitCurve(newPoints, parseInt(bezier));
- for (const curve of bezierCurves) {
- for (var t = 0; t < 1; t += 0.01) {
- const point = beziercurve(t, curve);
- pts.push({ X: point[0], Y: point[1] });
- }
- }
} else {
pts = points.slice();
- // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag. need to remove it here.
- if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) {
- pts.pop();
- }
- }
- if (isNaN(scalex)) {
- scalex = 1;
- }
- if (isNaN(scaley)) {
- scaley = 1;
}
- const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc +
- `${(pt.X - left - width / 2) * scalex + width / 2},
- ${(pt.Y - top - width / 2) * scaley + width / 2} `, "");
+ if (isNaN(scalex)) scalex = 1;
+ if (isNaN(scaley)) scaley = 1;
+
+ const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `;
+ var strpts = bezier ?
+ pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pts[i]) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") :
+ pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, "");
+
const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined;
const defGuid = Utils.GenerateGuid();
const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth)));
- const addables = pts.map((pts, i) =>
- <svg height="10" width="10">
- <circle cx={(pts.X - left - width / 2) * scalex + width / 2} cy={(pts.Y - top - width / 2) * scaley + width / 2} r={strokeWidth / 2} stroke="black" strokeWidth={1} fill="blue"
- onDoubleClick={(e) => { console.log(i); }} pointerEvents="all" cursor="all-scroll"
- />
- </svg>);
-
-
+ const Tag = (bezier ? "path" : "polyline") as keyof JSX.IntrinsicElements;
return (<svg fill={color}> {/* setting the svg fill sets the arrowStart fill */}
{nodefs ? (null) : <defs>
{arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : <marker id={`dot${defGuid}`} orient="auto" overflow="visible">
@@ -207,8 +182,10 @@ export namespace InteractionUtils {
<polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} />
</marker>}
</defs>}
- <polyline
- points={strpts}
+
+ <Tag
+ d={bezier ? strpts : undefined}
+ points={bezier ? undefined : strpts}
style={{
// filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
fill: fill && fill !== "transparent" ? fill : "none",
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 6ccbd3fd7..465e6b2df 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -708,7 +708,7 @@ export class GestureOverlay extends Touchable {
this._points.push({ X: left, Y: top });
break;
-
+
case "triangle":
this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
@@ -851,7 +851,7 @@ export class GestureOverlay extends Touchable {
}),
this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height}
style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
+ {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "", ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
</svg>]
];
}
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index 4df7ee813..7e685288d 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -37,8 +37,8 @@ export class InkControls extends React.Component<InkControlProps> {
const controlUndo = UndoManager.StartBatch("DocDecs set radius");
const screenScale = this.props.ScreenToLocalTransform().Scale;
const order = controlIndex % 4;
- const handleIndexA = order === 2 ? controlIndex - 1 : controlIndex - 2;
- const handleIndexB = order === 2 ? controlIndex + 2 : controlIndex + 1;
+ 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"));
setupMoveUpEvents(this, e,
(e: PointerEvent, down: number[], delta: number[]) => {
@@ -47,7 +47,11 @@ export class InkControls extends React.Component<InkControlProps> {
},
() => controlUndo?.end(),
action((e: PointerEvent, doubleTap: boolean | undefined) => {
- if (doubleTap && brokenIndices && brokenIndices.includes(controlIndex)) {
+ const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
+ if (doubleTap && brokenIndices?.includes(equivIndex)) {
+ InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
+ }
+ if (doubleTap && brokenIndices?.includes(controlIndex)) {
InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
}
}));
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index afe94cdfb..1a514bdce 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -31,10 +31,11 @@ export class InkHandles extends React.Component<InkHandlesProps> {
const controlUndo = UndoManager.StartBatch("DocDecs set radius");
const screenScale = this.props.ScreenToLocalTransform().Scale;
const order = handleIndex % 4;
- const oppositeHandleIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
- const controlIndex = order === 1 ? handleIndex - 1 : handleIndex + 2;
- document.addEventListener("keydown", (e: KeyboardEvent) => this.onBreakTangent(e, controlIndex), true);
+ const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
+ const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.data.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.data.length;
+ const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.data.length;
setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+ if (e.altKey) this.onBreakTangent(controlIndex);
InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
return false;
}, () => controlUndo?.end(), emptyFunction
@@ -48,15 +49,14 @@ export class InkHandles extends React.Component<InkHandlesProps> {
* @param handleNum The index of the currently selected handle point.
*/
@action
- onBreakTangent = (e: KeyboardEvent, controlIndex: number) => {
- const doc: Doc = this.props.inkDoc;
- if (["Alt"].includes(e.key)) {
- e.stopPropagation();
- if (doc) {
- const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
- if (brokenIndices && !brokenIndices.includes(controlIndex)) {
- brokenIndices.push(controlIndex);
- }
+ onBreakTangent = (controlIndex: number) => {
+ const doc = this.props.inkDoc;
+ if (doc) {
+ const closed = this.props.data.lastElement().X === this.props.data[0].X && this.props.data.lastElement().Y === this.props.data[0].Y;
+ const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
+ if (!brokenIndices?.includes(controlIndex) &&
+ ((controlIndex > 0 && controlIndex < this.props.data.length - 1) || closed)) {
+ brokenIndices.push(controlIndex);
doc.brokenInkIndices = brokenIndices;
}
}
@@ -70,14 +70,20 @@ export class InkHandles extends React.Component<InkHandlesProps> {
const data = this.props.data;
const handlePoints: HandlePoint[] = [];
const handleLines: HandleLine[] = [];
+ const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y;
if (data.length >= 4) {
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 });
+ handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : 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 ? (closed ? (i + 4) % 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 });
+ if (closed) {
+ handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 });
+ }
+ else {
+ 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 + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 42190238e..2073497b9 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,14 +1,14 @@
import { action, computed, observable, reaction } from "mobx";
-import { Doc, DocListCast, Field, Opt } from "../../fields/Doc";
+import { Doc, Field, Opt } from "../../fields/Doc";
import { Document } from "../../fields/documentSchemas";
-import { InkField, InkData, PointData, ControlPoint, InkTool } from "../../fields/InkField";
+import { InkData, InkField, InkTool, PointData } from "../../fields/InkField";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast, NumCast } from "../../fields/Types";
import { DocumentType } from "../documents/DocumentTypes";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { SelectionManager } from "../util/SelectionManager";
import { undoBatch } from "../util/UndoManager";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
export class InkStrokeProperties {
static Instance: InkStrokeProperties | undefined;
@@ -241,6 +241,7 @@ export class InkStrokeProperties {
this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
const newPoints: { X: number, Y: number }[] = [];
const order = controlIndex % 4;
+ const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y;
for (var i = 0; i < ink.length; i++) {
const leftHandlePoint = order === 0 && i === controlIndex + 1;
const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2;
@@ -248,6 +249,7 @@ export class InkStrokeProperties {
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) ||
@@ -335,6 +337,7 @@ export class InkStrokeProperties {
@action
moveHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
+ const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y;
const oldHandlePoint = ink[handleIndex];
let oppositeHandlePoint = ink[oppositeHandleIndex];
const controlPoint = ink[controlIndex];
@@ -342,7 +345,7 @@ export class InkStrokeProperties {
ink[handleIndex] = newHandlePoint;
const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
// 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)) && handleIndex !== 1 && handleIndex !== ink.length - 2) {
+ if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) {
const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
ink[oppositeHandleIndex] = oppositeHandlePoint;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index ca39bdaa1..a518bf07b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -11,14 +11,11 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
-import { Scripting } from "../util/Scripting";
import { ContextMenu } from "./ContextMenu";
import { ViewBoxBaseComponent } from "./DocComponent";
import { Colors } from "./global/globalEnums";
import { InkControls } from "./InkControls";
import { InkHandles } from "./InkHandles";
-import { GestureOverlay } from "./GestureOverlay";
-import { isThisTypeNode } from "typescript";
import "./InkStroke.scss";
import { InkStrokeProperties } from "./InkStrokeProperties";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
@@ -83,6 +80,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
if (doubleTap && this._properties) {
this._properties._controlButton = true;
+ InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1);
this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
}
}), this._properties?._controlButton, this._properties?._controlButton