aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkingStroke.tsx
diff options
context:
space:
mode:
authorvkalev <vjk1883@gmail.com>2021-07-10 11:13:42 -0500
committervkalev <vjk1883@gmail.com>2021-07-10 11:13:42 -0500
commit8bc17cecdfce184e5a426dc2332d3c9ad0406f58 (patch)
treeb93de76fb5cf384b973fa9747ea466114b571bf7 /src/client/views/InkingStroke.tsx
parent5ab81f49a11bd8a74725228a887a90c88a3848ff (diff)
fixed adding point bug with breaking handle tangency
Diffstat (limited to 'src/client/views/InkingStroke.tsx')
-rw-r--r--src/client/views/InkingStroke.tsx187
1 files changed, 187 insertions, 0 deletions
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
new file mode 100644
index 000000000..b630c20cc
--- /dev/null
+++ b/src/client/views/InkingStroke.tsx
@@ -0,0 +1,187 @@
+import React = require("react");
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc } from "../../fields/Doc";
+import { documentSchema } from "../../fields/documentSchemas";
+import { InkData, InkField, InkTool } from "../../fields/InkField";
+import { makeInterface } from "../../fields/Schema";
+import { Cast, StrCast } from "../../fields/Types";
+import { TraceMobx } from "../../fields/util";
+import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { InteractionUtils } from "../util/InteractionUtils";
+import { Scripting } from "../util/Scripting";
+import { ContextMenu } from "./ContextMenu";
+import { ViewBoxBaseComponent } from "./DocComponent";
+import "./InkStroke.scss";
+import { FieldView, FieldViewProps } from "./nodes/FieldView";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { InkControls } from "./InkControls";
+import { InkHandles } from "./InkHandles";
+
+type InkDocument = makeInterface<[typeof documentSchema]>;
+const InkDocument = makeInterface(documentSchema);
+
+@observer
+export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
+ 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);
+ }
+
+ analyzeStrokes() {
+ const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+ CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
+ }
+
+ @action
+ public static toggleMask = (inkDoc: Doc) => {
+ inkDoc.isInkMask = !inkDoc.isInkMask;
+ inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
+ inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
+ inkDoc.color = "#9b9b9bff";
+ inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
+ }
+
+ /**
+ * 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))
+ );
+ }
+ }
+
+ render() {
+ TraceMobx();
+ // Extracting the ink data and formatting information of the current ink stroke.
+ const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+ const strokeWidth = Number(this.layoutDoc.strokeWidth);
+ 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 = 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 dotsize = Math.max(width * scaleX, height * scaleY) / 40;
+
+ // 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, lineLeft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6,
+ 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,
+ 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);
+ // 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="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,
+ mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
+ overflow: "visible",
+ }}
+ onPointerDown={this.onPointerDown}
+ onContextMenu={() => {
+ const cm = ContextMenu.Instance;
+ 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(() => {if (this._properties) { this._properties._controlButton = !this._properties._controlButton; }} ), icon: "paint-brush" });
+ }
+ }}
+ >
+
+ {clickableLine}
+ {inkLine}
+ {this.props.isSelected() ? selectedLine : ""}
+ {this.props.isSelected() && this._properties?._controlButton ?
+ <>
+ <InkControls
+ data={data}
+ addedPoints={addedPoints}
+ format={[left, top, scaleX, scaleY, strokeWidth, dotsize]}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
+ <InkHandles
+ data={data}
+ format={[left, top, scaleX, scaleY, strokeWidth, dotsize]}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
+ </> : ""}
+ </svg>
+ );
+ }
+}
+
+
+export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
+export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
+export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
+export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
+export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
+export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
+export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
+export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
+export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
+export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
+export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
+export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
+export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
+export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
+export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
+Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
+ CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None;
+ SetActiveInkWidth(width);
+ SetActiveInkColor(color);
+ SetActiveFillColor(fill);
+ SetActiveArrowStart(arrowStart);
+ SetActiveArrowEnd(arrowEnd);
+ SetActiveDash(dash);
+});
+Scripting.addGlobal(function activateEraser(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Eraser : InkTool.None; });
+Scripting.addGlobal(function activateStamp(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Stamp : InkTool.None; });
+Scripting.addGlobal(function deactivateInk() { return CurrentUserUtils.SelectedTool = InkTool.None; });
+Scripting.addGlobal(function setInkWidth(width: any) { return SetActiveInkWidth(width); });
+Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
+Scripting.addGlobal(function setFillColor(fill: any) { return SetActiveFillColor(fill); });
+Scripting.addGlobal(function setActiveArrowStart(arrowStart: any) { return SetActiveArrowStart(arrowStart); });
+Scripting.addGlobal(function setActiveArrowEnd(arrowEnd: any) { return SetActiveArrowStart(arrowEnd); });
+Scripting.addGlobal(function setActiveDash(dash: any) { return SetActiveDash(dash); });