From 061dab5285d3a334a258d8097a6e95b065b30de3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 29 Nov 2021 14:09:17 -0500
Subject: added moving ink stroke segments.   added stretching and rotating ink
 strokes about opposite end point.

---
 src/client/views/InkingStroke.tsx | 79 +++++++++++++++++++++++++++------------
 1 file changed, 56 insertions(+), 23 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 59efb36dd..ecc82a580 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,28 +1,29 @@
 import React = require("react");
 import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym } from "../../fields/Doc";
+import { Doc, WidthSym } from "../../fields/Doc";
 import { documentSchema } from "../../fields/documentSchemas";
 import { InkData, InkField, InkTool } from "../../fields/InkField";
 import { makeInterface } from "../../fields/Schema";
 import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
-import { emptyFunction, returnFalse, setupMoveUpEvents, OmitKeys } from "../../Utils";
+import { OmitKeys, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
 import { SnappingManager } from "../util/SnappingManager";
+import { Transform } from "../util/Transform";
+import { UndoManager } from "../util/UndoManager";
 import { ContextMenu } from "./ContextMenu";
 import { ViewBoxBaseComponent } from "./DocComponent";
 import { Colors } from "./global/globalEnums";
-import { InkControlPtHandles } from "./InkControlPtHandles";
+import { InkControlPtHandles, InkEndPtHandles } from "./InkControlPtHandles";
 import "./InkStroke.scss";
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { InkTangentHandles } from "./InkTangentHandles";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import Color = require("color");
-import { Transform } from "../util/Transform";
 import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
+import Color = require("color");
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -89,21 +90,49 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
     /**
      * Handles the movement of the entire ink object when the user clicks and drags.
      */
+    @action
     onPointerDown = (e: React.PointerEvent) => {
+        const ptFromScreen = this.ptFromScreen;
         this._handledClick = false;
-        if (this.props.isSelected(true)) {
-            setupMoveUpEvents(this, e, returnFalse, emptyFunction,
+        if (InkStrokeProperties.Instance && ptFromScreen) {
+            const inkView = this.props.docViewPath().lastElement();
+            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 { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
+            const controlIndex = nearestSeg;
+            const wasSelected = InkStrokeProperties.Instance?._currentPoint === controlIndex;
+            var controlUndo: UndoManager.Batch | undefined;
+            const isEditing = this._properties?._controlButton && this.props.isSelected();
+            setupMoveUpEvents(this, e,
+                !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => {
+                    if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
+                    const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
+                    const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
+                    InkStrokeProperties.Instance?.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
+                    InkStrokeProperties.Instance?.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
+                    return false;
+                }),
+                !isEditing ? returnFalse : action(() => {
+                    if (controlUndo) {
+                        InkStrokeProperties.Instance?.snapControl(inkView, controlIndex);
+                        InkStrokeProperties.Instance?.snapControl(inkView, controlIndex + 3);
+                    }
+                    controlUndo?.end();
+                    controlUndo = undefined;
+                    UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+                }),
                 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) {
+                    } else if (isEditing) {
                         this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
-                }), this._properties?._controlButton, this._properties?._controlButton
-            );
+                }), isEditing, isEditing, () => wasSelected && InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1));
         }
     }
 
@@ -185,7 +214,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         this._nearestScrPt = nearestPt;
     }
 
-
     nearestScreenPt = () => this._nearestScrPt;
     componentUI = (boundsLeft: number, boundsTop: number) => {
         const inkDoc = this.props.Document;
@@ -200,11 +228,17 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 
         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) :
-                <>
+        return SnappingManager.GetIsDragging() ? (null) :
+            !this._properties?._controlButton ?
+                (!this.props.isSelected() || InkingStroke.IsClosed(inkData) ? (null) :
+                    <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
+                        <InkEndPtHandles
+                            inkView={this.props.docViewPath().lastElement()}
+                            inkDoc={inkDoc}
+                            startPt={this.ptToScreen(inkData[0])}
+                            endPt={this.ptToScreen(inkData.lastElement())}
+                            screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /></div>) :
+                <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
                     {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)}
@@ -222,8 +256,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         screenCtrlPoints={screenHdlPts}
                         screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
                         ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
-                </>}
-        </div>;
+                </div>;
     }
 
     render() {
@@ -246,11 +279,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             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];
         // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
-            inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && fillColor && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
+        const clickableLine = (downHdlr?: (e: React.PointerEvent) => void) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
+            inkStrokeWidth, inkStrokeWidth + (CurrentUserUtils.SelectedTool === InkTool.Eraser ? 0 : highlightIndex && closed && fillColor && (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);
+            undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0,
+            false, downHdlr);
         // Set of points rendered upon the ink that can be added if a user clicks on one.
 
         return <div className="inkStroke-wrapper" style={{ display: "flex", alignItems: "center", height: "100%" }}>
@@ -266,7 +300,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 }}
                 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;
@@ -275,7 +308,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     cm?.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" });
                 }}
             >
-                {clickableLine}
+                {clickableLine(this.onPointerDown)}
                 {inkLine}
             </svg>
             {!closed ? (null) :
-- 
cgit v1.2.3-70-g09d2

