aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkingStroke.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/InkingStroke.tsx')
-rw-r--r--src/client/views/InkingStroke.tsx239
1 files changed, 153 insertions, 86 deletions
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 5fc159f14..752db1413 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,34 +1,46 @@
import React = require("react");
-import { action, observable } from "mobx";
+import { Bezier } from "bezier-js";
+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, BoolCast } 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 { Colors } from "./global/globalEnums";
+import { InkControlPtHandles } from "./InkControlPtHandles";
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 { InkTangentHandles } from "./InkTangentHandles";
+import { FieldView, FieldViewProps } from "./nodes/FieldView";
+import { SnappingManager } from "../util/SnappingManager";
+import Color = require("color");
type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
+ public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
static readonly MaskDim = 50000;
+ public static IsClosed(inkData: InkData) {
+ return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
+ }
@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;
+
+ @observable _nearestT: number | undefined;
+ @observable _nearestSeg: number | undefined;
+ @observable _nearestScrPt: { X: number, Y: number } | undefined;
+ @observable _inkSamplePts: { X: number, Y: number }[] | undefined;
constructor(props: FieldViewProps & InkDocument) {
super(props);
@@ -36,8 +48,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
this._properties = InkStrokeProperties.Instance;
}
- public static LayoutString(fieldStr: string) {
- return FieldView.LayoutString(InkingStroke, fieldStr);
+ 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?.();
}
analyzeStrokes() {
@@ -52,15 +69,23 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
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) => {
+ 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;
+ 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
+ } else if (this._properties?._controlButton) {
+ this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+ }
+ }), this._properties?._controlButton, this._properties?._controlButton
);
}
}
@@ -75,80 +100,140 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
}
}
+ @action
+ checkHighlighter = () => {
+ if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) {
+ // this._previousColor = ActiveInkColor();
+ SetActiveInkColor("rgba(245, 230, 95, 0.75)");
+ }
+ }
+
+ inkScaledData = () => {
+ const 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: ((this.props.PanelWidth() - inkStrokeWidth) / ((inkWidth - inkStrokeWidth) || 1) || 1),
+ inkScaleY: ((this.props.PanelHeight() - inkStrokeWidth) / ((inkHeight - inkStrokeWidth) || 1) || 1)
+ };
+ }
+
+ @action
+ onPointerMove = (e: React.PointerEvent) => {
+ const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+ const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
+ (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
+ (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
+
+ this._nearestT = nearestT;
+ this._nearestSeg = nearestSeg;
+ this._nearestScrPt = nearestPt;
+ }
+
+
+ nearestScreenPt = () => this._nearestScrPt;
+ 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 - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
+ (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const screenHdlPts = screenPts;
+
+ const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
+ const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
+ return SnappingManager.GetIsDragging() ? (null) : <div className="inkstroke-UI" style={{
+ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)`
+ }} >
+ {!this._properties?._controlButton ? (null) :
+ <>
+ {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
+ StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier),
+ "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
+ <InkControlPtHandles
+ inkDoc={inkDoc}
+ inkCtrlPoints={inkData}
+ screenCtrlPoints={screenHdlPts}
+ nearestScreenPt={this.nearestScreenPt}
+ screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
+ <InkTangentHandles
+ inkDoc={inkDoc}
+ screenCtrlPoints={screenHdlPts}
+ screenSpaceLineWidth={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 strokeColor = StrCast(this.layoutDoc.color, "");
- const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
+ const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
+
+ const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
+ const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
+ const closed = InkingStroke.IsClosed(inkData);
+ const fillColor = StrCast(this.layoutDoc.fillColor, "transparent");
+ const strokeColor = !closed && fillColor && fillColor !== "transparent" ? fillColor : StrCast(this.layoutDoc.color);
// 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.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
+ StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
+ 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 highlightIndex = BoolCast(this.props.Document.isLinkButton) && Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
+ const highlightColor = !highlightIndex ?
+ StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") :
+ ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
+
+ const clickableLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
+ inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
+ StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
+ StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
+ undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0, false);
// 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",
+ cursor: this.props.isSelected() ? "default" : undefined
}}
+ onPointerLeave={action(e => this._nearestScrPt = undefined)}
+ onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined}
onPointerDown={this.onPointerDown}
+ onClick={e => this._handledClick && e.stopPropagation()}
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" });
- }
+ !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
- 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>
);
}
@@ -168,23 +253,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); });