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, NumCast } 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"; import { Colors } from "./global/globalEnums"; import { GestureOverlay } from "./GestureOverlay"; import { isThisTypeNode } from "typescript"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @observer export class InkingStroke extends ViewBoxBaseComponent(InkDocument) { static readonly MaskDim = 50000; @observable private _properties?: InkStrokeProperties; // @observable private _previousColor: string; constructor(props: FieldViewProps & InkDocument) { super(props); this._properties = InkStrokeProperties.Instance; // this._previousColor = ActiveInkColor(); } componentDidMount() { this.props.setContentView?.(this); } 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]); } public static toggleMask = action((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)) ); } } /** * Ensures the ink controls and handles aren't rendered when the current ink stroke is reselected. */ @action toggleControlButton = () => { if (!this.props.isSelected() && this._properties) { this._properties._controlButton = false; } } @action checkHighlighter = () => { if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) { // this._previousColor = ActiveInkColor(); SetActiveInkColor("rgba(245, 230, 95, 0.75)"); } // } else { // SetActiveInkColor(this._previousColor); // } } inkScaledData = () => { const inkData: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1); const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2; const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2; const inkLeft = Math.min(...inkData.map(p => p.X)) - inkStrokeWidth / 2; const inkRight = Math.max(...inkData.map(p => p.X)) + inkStrokeWidth / 2; const inkWidth = Math.max(1, inkRight - inkLeft); const inkHeight = Math.max(1, inkBottom - inkTop); return { inkData, inkStrokeWidth, inkTop, inkLeft, inkWidth, inkHeight, inkScaleX: inkHeight === inkStrokeWidth ? 1 : (this.props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth), inkScaleY: inkWidth === inkStrokeWidth ? 1 : (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth) }; } componentUI = (boundsLeft: number, boundsTop: number) => { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth } = this.inkScaledData(); const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth); const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(point.X, point.Y)).map(p => ({ X: p[0], Y: p[1] })); const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth[0] / 2; const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth[0] / 2; const inkDoc = this.props.Document; const overlayWidth = 3; const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); const addedPoints = InteractionUtils.CreatePoints(inkData, screenLeft, screenTop, StrCast(inkDoc.strokeColor, "none"), screenInkWidth[0], overlayWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", this.props.isSelected() && inkStrokeWidth <= 5, false); return
{InteractionUtils.CreatePolyline(screenPts, screenLeft, screenTop, Colors.MEDIUM_BLUE, screenInkWidth[0], overlayWidth, StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"), StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker), StrCast(inkDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false)} {this._properties?._controlButton ? <> : ""}
} render() { TraceMobx(); this.toggleControlButton(); // this.checkHighlighter(); // Extracting the ink data and formatting information of the current ink stroke. const inkDoc: Doc = this.layoutDoc; const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData(); const strokeColor = StrCast(this.layoutDoc.color, ""); const dotsize = Math.max(inkWidth * inkScaleX, inkHeight * inkScaleY) / 40; // Visually renders the polygonal line made by the user. const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, 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", 1.0, false); // Invisible polygonal line that enables the ink to be selected by the user. const clickableLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, "transparent", inkStrokeWidth, inkStrokeWidth + 15, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, inkScaleX, inkScaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", 0.0, true); // Set of points rendered upon the ink that can be added if a user clicks on one. return ( { 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 ? <> : ""} */} ); } } 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); });