aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkingStroke.tsx
diff options
context:
space:
mode:
authorgeireann <geireann.lindfield@gmail.com>2021-08-27 14:19:25 -0400
committergeireann <geireann.lindfield@gmail.com>2021-08-27 14:19:25 -0400
commitbe4fd2492ad706f30af28f33133a4df0e8049e12 (patch)
treee33b32f54be50122ed16c07d2b6f6b2e79239cb4 /src/client/views/InkingStroke.tsx
parentc5e96c72fcf149b9bcfe5f7f7a9c714de1d5fd9a (diff)
parent7c83bc30b3a6ed6061ef68bcef6a0e8941668b3c (diff)
Merge branch 'master' into schema-view-En-Hua
Diffstat (limited to 'src/client/views/InkingStroke.tsx')
-rw-r--r--src/client/views/InkingStroke.tsx220
1 files changed, 80 insertions, 140 deletions
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 449019ca8..5fc159f14 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,4 +1,5 @@
-import { action } from "mobx";
+import React = require("react");
+import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../fields/Doc";
import { documentSchema } from "../../fields/documentSchemas";
@@ -10,25 +11,36 @@ import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { InteractionUtils } from "../util/InteractionUtils";
import { Scripting } from "../util/Scripting";
-import { UndoManager } from "../util/UndoManager";
import { ContextMenu } from "./ContextMenu";
import { ViewBoxBaseComponent } from "./DocComponent";
-import "./InkingStroke.scss";
+import "./InkStroke.scss";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import React = require("react");
import { InkStrokeProperties } from "./InkStrokeProperties";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { InkControls } from "./InkControls";
+import { InkHandles } from "./InkHandles";
+import { Colors } from "./global/globalEnums";
+import { GestureOverlay } from "./GestureOverlay";
type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
- private _controlUndo?: UndoManager.Batch;
+ static readonly MaskDim = 50000;
+ @observable private _properties?: InkStrokeProperties;
+
+ constructor(props: FieldViewProps & InkDocument) {
+ super(props);
+
+ this._properties = InkStrokeProperties.Instance;
+ }
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(InkingStroke, fieldStr);
+ }
- private analyzeStrokes = () => {
+ analyzeStrokes() {
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
}
@@ -41,149 +53,67 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
});
- @action
- onControlDown = (e: React.PointerEvent, controlNum: number): void => {
- if (InkStrokeProperties.Instance) {
- InkStrokeProperties.Instance.control(0, 0, 1);
- const controlUndo = UndoManager.StartBatch("DocDecs set radius");
- const screenScale = this.props.ScreenToLocalTransform().Scale;
- setupMoveUpEvents(this, e,
- (e: PointerEvent, down: number[], delta: number[]) => {
- InkStrokeProperties.Instance?.control(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
- return false;
- },
- () => controlUndo?.end(), emptyFunction);
- }
- }
-
- @action
- changeCurrPoint = (i: number) => {
- if (InkStrokeProperties.Instance) {
- InkStrokeProperties.Instance._currPoint = i;
- document.addEventListener("keydown", this.delPts, true);
+ /**
+ * Handles the movement of the entire ink object when the user clicks and drags.
+ */
+ onPointerDown = (e: React.PointerEvent) => {
+ if (this.props.isSelected(true)) {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction,
+ action((e: PointerEvent, doubleTap: boolean | undefined) =>
+ doubleTap && this._properties && (this._properties._controlButton = true))
+ );
}
}
+ /**
+ * Ensures the ink controls and handles aren't rendered when the current ink stroke is reselected.
+ */
@action
- delPts = (e: KeyboardEvent) => {
- if (["-", "Backspace", "Delete"].includes(e.key)) {
- if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation();
+ toggleControlButton = () => {
+ if (!this.props.isSelected() && this._properties) {
+ this._properties._controlButton = false;
}
}
- onPointerDown = (e: React.PointerEvent) => {
- if (this.props.isSelected(true)) {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e: PointerEvent, doubleTap: boolean | undefined) =>
- doubleTap && InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = true)));
- }
- }
-
- public static MaskDim = 50000;
render() {
TraceMobx();
- const formatInstance = InkStrokeProperties.Instance;
- if (!formatInstance) return (null);
+ this.toggleControlButton();
+ // Extracting the ink data and formatting information of the current ink stroke.
+ // console.log(InkingStroke.InkShape);
+ const InkShape = GestureOverlay.Instance.InkShape;
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
- // const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
+ const inkDoc: Doc = this.layoutDoc;
const strokeWidth = Number(this.layoutDoc.strokeWidth);
- const xs = data.map(p => p.X);
- const ys = data.map(p => p.Y);
- const lineTop = Math.min(...ys);
- const lineBot = Math.max(...ys);
- const lineLft = Math.min(...xs);
- const lineRgt = Math.max(...xs);
- const left = lineLft - strokeWidth / 2;
+ const lineTop = Math.min(...data.map(p => p.Y));
+ const lineBottom = Math.max(...data.map(p => p.Y));
+ const lineLeft = Math.min(...data.map(p => p.X));
+ const lineRight = Math.max(...data.map(p => p.X));
+ const left = lineLeft - strokeWidth / 2;
const top = lineTop - strokeWidth / 2;
- const right = lineRgt + strokeWidth / 2;
- const bottom = lineBot + strokeWidth / 2;
+ const right = lineRight + strokeWidth / 2;
+ const bottom = lineBottom + strokeWidth / 2;
const width = Math.max(1, right - left);
const height = Math.max(1, bottom - top);
const scaleX = width === strokeWidth ? 1 : (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
const strokeColor = StrCast(this.layoutDoc.color, "");
-
- const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
- StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
- StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
- StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
-
- const hpoints = InteractionUtils.CreatePolyline(data, left, top,
- this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15),
- StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
- "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
-
- //points for adding
- const apoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
- StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
- StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
- StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
-
- const controlPoints: { X: number, Y: number, I: number }[] = [];
- const handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = [];
- const handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = [];
- if (data.length >= 4) {
- for (var i = 0; i <= data.length - 4; i += 4) {
- controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
- controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
- 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 });
- }
-
- handleLine.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 });
- for (var i = 2; i < data.length - 4; i += 4) {
-
- handleLine.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 });
-
- }
- handleLine.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 (var 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 });
- }
- }
- // if (data.length <= 4) {
- // handlePoints = [];
- // handleLine = [];
- // controlPoints = [];
- // for (var i = 0; i < data.length; i++) {
- // controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
- // }
-
- // }
const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
- const addpoints = apoints.map((pts, i) =>
- <svg height="10" width="10" key={`add${i}`}>
- <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} stroke="invisible" strokeWidth={dotsize / 2} fill="invisible"
- onPointerDown={(e) => { formatInstance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} pointerEvents="all" cursor="all-scroll"
- />
- </svg>);
- const handles = handlePoints.map((pts, i) =>
- <svg height="10" width="10" key={`hdl${i}`}>
- <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth} strokeWidth={0} fill="green"
- onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
- </svg>);
-
- const controls = controlPoints.map((pts, i) =>
- <svg height="10" width="10" key={`ctrl${i}`}>
- <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} strokeWidth={0} fill="red"
- onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="default"
- />
- </svg>);
- const handleLines = handleLine.map((pts, i) =>
- <svg height="100" width="100" key={`line${i}`} >
- <line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
- x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" strokeWidth={dotsize / 6}
- display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "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="green" strokeWidth={dotsize / 6}
- display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
- </svg>);
-
+ // Visually renders the polygonal line made by the user.
+ const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
+ StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
+ // Thin blue line indicating that the current ink stroke is selected.
+ const selectedLine = InteractionUtils.CreatePolyline(data, left, top, Colors.MEDIUM_BLUE, strokeWidth, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+ StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
+ // Invisible polygonal line that enables the ink to be selected by the user.
+ const clickableLine = InteractionUtils.CreatePolyline(data, left, top, "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
+ StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
+ // Set of points rendered upon the ink that can be added if a user clicks on one.
+ const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
+ StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
return (
- <svg className="inkingStroke"
+ <svg className="inkStroke"
style={{
pointerEvents: this.props.Document.isInkMask && this.props.layerProvider?.(this.props.Document) !== false ? "all" : "none",
transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
@@ -196,19 +126,29 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
if (cm) {
!Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
cm.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
- cm.addItem({ description: "Edit Points", event: action(() => formatInstance._controlBtn = !formatInstance._controlBtn), icon: "paint-brush" });
- //cm.addItem({ description: "Format Shape...", event: this.formatShape, icon: "paint-brush" });
+ cm.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" });
}
}}
- ><defs>
- </defs>
- {hpoints}
- {points}
- {formatInstance._controlBtn && this.props.isSelected() ? addpoints : ""}
- {formatInstance._controlBtn && this.props.isSelected() ? handleLines : ""}
- {formatInstance._controlBtn && this.props.isSelected() ? handles : ""}
- {formatInstance._controlBtn && this.props.isSelected() ? controls : ""}
-
+ >
+
+ {clickableLine}
+ {inkLine}
+ {this.props.isSelected() ? selectedLine : ""}
+ {this.props.isSelected() && this._properties?._controlButton ?
+ <>
+ <InkControls
+ inkDoc={inkDoc}
+ data={data}
+ addedPoints={addedPoints}
+ format={[left, top, scaleX, scaleY, strokeWidth]}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
+ <InkHandles
+ inkDoc={inkDoc}
+ data={data}
+ shape={InkShape}
+ format={[left, top, scaleX, scaleY, strokeWidth]}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
+ </> : ""}
</svg>
);
}