diff options
Diffstat (limited to 'src/client/views/InkingStroke.tsx')
-rw-r--r-- | src/client/views/InkingStroke.tsx | 197 |
1 files changed, 133 insertions, 64 deletions
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 5fc159f14..ca39bdaa1 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,26 +1,27 @@ import React = require("react"); -import { action, observable } from "mobx"; +import { action, IReactionDisposer, observable, reaction } 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 { Cast, NumCast, StrCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; -import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils"; +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 "./InkStroke.scss"; -import { FieldView, FieldViewProps } from "./nodes/FieldView"; -import { InkStrokeProperties } from "./InkStrokeProperties"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; +import { Colors } from "./global/globalEnums"; import { InkControls } from "./InkControls"; import { InkHandles } from "./InkHandles"; -import { Colors } from "./global/globalEnums"; import { GestureOverlay } from "./GestureOverlay"; +import { isThisTypeNode } from "typescript"; +import "./InkStroke.scss"; +import { InkStrokeProperties } from "./InkStrokeProperties"; +import { FieldView, FieldViewProps } from "./nodes/FieldView"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -29,11 +30,23 @@ const InkDocument = makeInterface(documentSchema); export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) { static readonly MaskDim = 50000; @observable private _properties?: InkStrokeProperties; + _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated + _selDisposer: IReactionDisposer | undefined; constructor(props: FieldViewProps & InkDocument) { super(props); this._properties = InkStrokeProperties.Instance; + // this._previousColor = ActiveInkColor(); + } + + componentDidMount() { + this.props.setContentView?.(this); + this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles + selected => !selected && this.toggleControlButton()); + } + componentWillUnmount() { + this._selDisposer?.(); } public static LayoutString(fieldStr: string) { @@ -53,14 +66,26 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined; }); + onClick = (e: React.MouseEvent) => { + if (this._handledClick) { + e.stopPropagation(); //stop the event so that docView won't open the lightbox + } + } + /** * Handles the movement of the entire ink object when the user clicks and drags. */ onPointerDown = (e: React.PointerEvent) => { + this._handledClick = false; if (this.props.isSelected(true)) { setupMoveUpEvents(this, e, returnFalse, emptyFunction, - action((e: PointerEvent, doubleTap: boolean | undefined) => - doubleTap && this._properties && (this._properties._controlButton = true)) + action((e: PointerEvent, doubleTap: boolean | undefined) => { + doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick; + if (doubleTap && this._properties) { + this._properties._controlButton = true; + 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 ); } } @@ -75,52 +100,115 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume } } + @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 inkDoc = this.props.Document; + const screenSpaceCenterlineStrokeWidth = 3; // the width of the blue line widget that shows the centerline of the ink stroke + const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = 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 screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + + const screenSpaceSamplePoints = InteractionUtils.CreatePoints(screenPts, screenLeft, screenTop, StrCast(inkDoc.strokeColor, "none"), screenInkWidth[0], screenSpaceCenterlineStrokeWidth, + 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); + const inkSpaceSamplePoints = InteractionUtils.CreatePoints(inkData, inkLeft, inkTop, StrCast(inkDoc.strokeColor, "none"), inkStrokeWidth, screenSpaceCenterlineStrokeWidth, + StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker), + StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), 1, 1, "", "none", this.props.isSelected() && inkStrokeWidth <= 5, false); + + return <div className="inkstroke-UI" style={{ + left: screenOrigin[0], + top: screenOrigin[1], + clip: `rect(${boundsTop - screenOrigin[1]}px, 10000px, 10000px, ${boundsLeft - screenOrigin[0]}px)` + }} > + {InteractionUtils.CreatePolyline(screenPts, screenLeft, screenTop, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, + 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 ? + <> + <InkControls + inkDoc={inkDoc} + inkCtrlPoints={inkData} + screenCtrlPoints={screenPts} + inkStrokeSamplePts={inkSpaceSamplePoints} + screenStrokeSamplePoints={screenSpaceSamplePoints} + format={[screenLeft, screenTop, inkScaleX, inkScaleY, screenInkWidth[0], screenSpaceCenterlineStrokeWidth]} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> + <InkHandles + inkDoc={inkDoc} + data={screenPts} + format={[screenLeft, screenTop, inkScaleX, inkScaleY, screenInkWidth[0], screenSpaceCenterlineStrokeWidth]} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> + </> : ""} + </div>; + } + render() { TraceMobx(); - 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 inkDoc: Doc = this.layoutDoc; - 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 { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData(); + const strokeColor = StrCast(this.layoutDoc.color, ""); - const dotsize = Math.max(width * scaleX, height * scaleY) / 40; + const dotsize = Math.max(inkWidth * inkScaleX, inkHeight * inkScaleY) / 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); + 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", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false); + // 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(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); + 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. - 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", + pointerEvents: "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} + onClick={this.onClick} onContextMenu={() => { const cm = ContextMenu.Instance; if (cm) { @@ -133,22 +221,21 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume {clickableLine} {inkLine} - {this.props.isSelected() ? selectedLine : ""} - {this.props.isSelected() && this._properties?._controlButton ? + {/* {this.props.isSelected() ? selectedLine : ""} */} + {/* {this.props.isSelected() && this._properties?._controlButton ? <> <InkControls inkDoc={inkDoc} - data={data} + data={inkData} addedPoints={addedPoints} - format={[left, top, scaleX, scaleY, strokeWidth]} + format={[inkLeft, inkTop, inkScaleX, inkScaleY, inkStrokeWidth]} ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> <InkHandles inkDoc={inkDoc} - data={data} - shape={InkShape} - format={[left, top, scaleX, scaleY, strokeWidth]} + data={inkData} + format={[inkLeft, inkTop, inkScaleX, inkScaleY, inkStrokeWidth]} ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> - </> : ""} + </> : ""} */} </svg> ); } @@ -168,23 +255,5 @@ export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activ 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 ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } 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); }); |