From b0efa4a390415072eaeb06c8719ea57d73e10466 Mon Sep 17 00:00:00 2001
From: vkalev <50213748+vkalev@users.noreply.github.com>
Date: Wed, 30 Jun 2021 12:52:52 -0500
Subject: ink Bézier handle movement fixed + small visual changes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/client/views/InkingStroke.tsx | 93 +++++++++++++++++++++++----------------
 1 file changed, 54 insertions(+), 39 deletions(-)

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

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 859e53b97..163eb05b0 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -2,7 +2,7 @@ import { action } 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 { InkData, InkField, InkTool, ControlPoint, HandlePoint, HandleLine } from "../../fields/InkField";
 import { makeInterface } from "../../fields/Schema";
 import { Cast, StrCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
@@ -28,33 +28,51 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
 
-    private analyzeStrokes = () => {
+    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) => {
+    @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 a selected control point when the user clicks and drags.
-     * @param e React Pointer Event.
-     * @param controlNum The number of the currently selected control point.
+     * @param controlNum The index of the currently selected control point.
      */
     @action
     onControlDown = (e: React.PointerEvent, controlNum: number): void => {
         if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance.control(0, 0, 1);
+            InkStrokeProperties.Instance.moveControl(0, 0, 1);
+            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
+            const screenScale = this.props.ScreenToLocalTransform().Scale;
+            setupMoveUpEvents(this, e,
+                (e: PointerEvent, down: number[], delta: number[]) => {
+                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
+                    return false;
+                },
+                () => controlUndo?.end(), emptyFunction);
+        }
+    }
+
+    /**
+     * Handles the movement of a selected handle point when the user clicks and drags.
+     * @param controlNum The index of the currently selected handle point.
+     */
+    onHandleDown = (e: React.PointerEvent, handleNum: number): void => {
+        if (InkStrokeProperties.Instance) {
+            InkStrokeProperties.Instance.moveControl(0, 0, 1);
             const controlUndo = UndoManager.StartBatch("DocDecs set radius");
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             setupMoveUpEvents(this, e,
                 (e: PointerEvent, down: number[], delta: number[]) => {
-                    InkStrokeProperties.Instance?.control(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
+                    InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleNum);
                     return false;
                 },
                 () => controlUndo?.end(), emptyFunction);
@@ -69,7 +87,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
     changeCurrPoint = (i: number) => {
         if (InkStrokeProperties.Instance) {
             InkStrokeProperties.Instance._currPoint = i;
-            document.addEventListener("keydown", this.delPts, true);
+            document.addEventListener("keydown", this.onDelete, true);
         }
     }
 
@@ -78,7 +96,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
      * @param e Keyboard Event.
      */
     @action
-    delPts = (e: KeyboardEvent) => {
+    onDelete = (e: KeyboardEvent) => {
         if (["-", "Backspace", "Delete"].includes(e.key)) {
             if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation();
         }
@@ -101,7 +119,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const formatInstance = InkStrokeProperties.Instance;
         if (!formatInstance) return (null);
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
-        // const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
         const strokeWidth = Number(this.layoutDoc.strokeWidth);
         const xs = data.map(p => p.X);
         const ys = data.map(p => p.Y);
@@ -118,12 +135,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         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 points = 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 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
+        
+        const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - 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 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
 
         // Invisible polygonal line that enables the ink to be selected by the user.
         const hpoints = InteractionUtils.CreatePolyline(data, left, top,
@@ -136,10 +159,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
             StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
 
-        const controlPoints: { X: number, Y: number, I: number }[] = [];
-        const handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = [];
-        const handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = [];
+        const controlPoints: ControlPoint[] = [];
+        const handlePoints: HandlePoint[] = [];
+        const handleLine: HandleLine[] = [];
+        // check the purpose of this conditional
         if (data.length >= 4) {
+
+            // create separate functions for these            
             for (var i = 0; i <= data.length - 4; i += 4) {
                 controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
                 controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
@@ -147,29 +173,21 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
             }
 
-            handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 });
+            // first (single) handle line 
+            handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[1].X, Y2: data[1].Y, dot1: 0, dot2: 0 });
             for (var i = 2; i < data.length - 4; i += 4) {
 
-                handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
+                handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 3].X, Y2: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
 
             }
-            handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+            // last (single) handle line
+            handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
 
             for (var i = 0; i <= data.length - 4; i += 4) {
                 handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
                 handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
             }
         }
-        // if (data.length <= 4) {
-        //     handlePoints = [];
-        //     handleLine = [];
-        //     controlPoints = [];
-        //     for (var i = 0; i < data.length; i++) {
-        //         controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
-        //     }
-
-        // }
-        const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
 
         // Additional points (controls) added by the user via click when editing the ink's format.
         const addpoints = apoints.map((pts, i) =>
@@ -178,27 +196,24 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     onPointerDown={(e) => { formatInstance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} pointerEvents="all" cursor="all-scroll"
                 />
             </svg>);
-        // Green circles that allow the user to edit the curvature of the line using the selected point as the anchor.
+        // Blue circles that allow the user to edit the curvature of the line using the selected control point as the anchor.
         const handles = handlePoints.map((pts, i) =>
             <svg height="10" width="10" key={`hdl${i}`}>
-                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth} strokeWidth={0} fill="green"
-                    onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
+                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} strokeWidth={0} fill="#1F85DE"
+                    onPointerDown={(e) => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
             </svg>);
-        // Points (red circles) of the ink that are made visible to user when editing its format.
+        // Control points of the ink (blue outlined squares) that are made visible to user when editing its format.
         const controls = controlPoints.map((pts, i) =>
             <svg height="10" width="10" key={`ctrl${i}`}>
-                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} strokeWidth={0} fill="red"
+                <rect x={(pts.X - left) * scaleX} y={(pts.Y - top) * scaleY} height={strokeWidth} width={strokeWidth} strokeWidth={strokeWidth / 6} stroke="#1F85DE" fill={formatInstance && formatInstance._currPoint === pts.I ? "#1F85DE" : "white"}
                     onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="default"
                 />
             </svg>);
-        // Set of two green lines (each with a handle at the end) that are rendered perpendicular to the current selected point while editing.
+        // Set of two blue lines (each with a handle at the end) that are rendered perpendicular to the current selected point while editing.
         const handleLines = handleLine.map((pts, i) =>
-            <svg height="100" width="100" key={`line${i}`}  >
+            <svg height="100" width="100" key={`line${i}`}>
                 <line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                    x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" strokeWidth={dotsize / 6}
-                    display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
-                <line x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                    x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" strokeWidth={dotsize / 6}
+                    x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="#1F85DE" strokeWidth={dotsize / 10}
                     display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
             </svg>);
 
@@ -227,8 +242,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 {formatInstance._controlBtn && this.props.isSelected() ? addpoints : ""}
                 {formatInstance._controlBtn && this.props.isSelected() ? handleLines : ""}
                 {formatInstance._controlBtn && this.props.isSelected() ? handles : ""}
+                {this.props.isSelected() ? selectedLine : ""}
                 {formatInstance._controlBtn && this.props.isSelected() ? controls : ""}
-
             </svg>
         );
     }
-- 
cgit v1.2.3-70-g09d2

