From f4da02dac8ea9d92443533b82bee2557cdd8957e Mon Sep 17 00:00:00 2001 From: vkalev Date: Wed, 11 Aug 2021 20:02:23 -0400 Subject: added circle shape generation using only 4 Bézier curves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/util/InteractionUtils.tsx | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index ba935e3bf..cb6a53157 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -299,8 +299,6 @@ export namespace InteractionUtils { return points; case "circle": - - const centerX = (Math.max(left, right) + Math.min(left, right)) / 2; const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2; const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom)); @@ -315,7 +313,6 @@ export namespace InteractionUtils { points.push({ X: newX, Y: y }); } points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(top, bottom) - centerY), 2))) + centerX, Y: Math.min(top, bottom) }); - } else { for (var x = Math.min(left, right); x < Math.max(left, right); x++) { const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY; @@ -327,7 +324,6 @@ export namespace InteractionUtils { points.push({ X: x, Y: newY }); } points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(left, right) - centerX), 2))) + centerY }); - } return points; // case "arrow": -- cgit v1.2.3-70-g09d2 From 8695343975decf566d8590d314a2f5c5cd89121b Mon Sep 17 00:00:00 2001 From: vkalev Date: Thu, 12 Aug 2021 12:48:24 -0400 Subject: fixed positioning of selection line --- src/client/util/InteractionUtils.tsx | 1 + src/client/views/GestureOverlay.tsx | 10 ++++++++-- src/client/views/InkHandles.tsx | 3 +++ src/client/views/InkingStroke.tsx | 7 ++++++- 4 files changed, 18 insertions(+), 3 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index cb6a53157..32b70c4b4 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -210,6 +210,7 @@ export namespace InteractionUtils { style={{ // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined, fill: fill ? fill : "none", + // opacity: 1.0, opacity: strokeWidth !== width ? 0.5 : undefined, pointerEvents: pevents as any, stroke: color ?? "rgb(0, 0, 0)", diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index f7b5cc030..cf57ac7a1 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -726,7 +726,7 @@ export class GestureOverlay extends Touchable { break; case "circle": - // Approximation of a circle using Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%, + // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%, // making the curves indistinguishable from a circle. // Source: https://spencermortensen.com/articles/bezier-circle/ const c = 0.551915024494; @@ -748,11 +748,17 @@ export class GestureOverlay extends Touchable { this._points.push({ X: centerX + radius, Y: centerY - (c * radius) }); this._points.push({ X: centerX + (c * radius), Y: centerY - radius }); this._points.push({ X: centerX, Y: centerY - radius }); - + this._points.push({ X: centerX, Y: centerY - radius }); this._points.push({ X: centerX - (c * radius), Y: centerY - radius }); this._points.push({ X: centerX - radius, Y: centerY - (c * radius) }); this._points.push({ X: centerX - radius, Y: centerY }); + + // this._points.push({ X: centerX - radius, Y: centerY }); + // this._points.push({ X: centerX - radius, Y: centerY - (c * radius) }); + // this._points.push({ X: centerX - (c * radius), Y: centerY - radius }); + // this._points.push({ X: centerX, Y: centerY - radius }); + break; case "line": diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx index f1eb4b9db..0b24c3c32 100644 --- a/src/client/views/InkHandles.tsx +++ b/src/client/views/InkHandles.tsx @@ -11,10 +11,12 @@ import { listSpec } from "../../fields/Schema"; import { List } from "../../fields/List"; import { Cast } from "../../fields/Types"; import { Colors } from "./global/globalEnums"; +import { GestureOverlay } from "./GestureOverlay"; export interface InkHandlesProps { inkDoc: Doc; data: InkData; + shape?: string; format: number[]; ScreenToLocalTransform: () => Transform; } @@ -68,6 +70,7 @@ export class InkHandles extends React.Component { // Accessing the current ink's data and extracting all handle points and handle lines. const data = this.props.data; + const shape = this.props.shape; const handlePoints: HandlePoint[] = []; const handleLines: HandleLine[] = []; if (data.length >= 4) { diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index d30244a8f..4beab6a62 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -20,6 +20,7 @@ import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { InkControls } from "./InkControls"; import { InkHandles } from "./InkHandles"; import { Colors } from "./global/globalEnums"; +import { GestureOverlay } from "./GestureOverlay"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -28,6 +29,7 @@ const InkDocument = makeInterface(documentSchema); export class InkingStroke extends ViewBoxBaseComponent(InkDocument) { static readonly MaskDim = 50000; @observable private _properties?: InkStrokeProperties; + // static InkShape = GestureOverlay.Instance.InkShape; constructor(props: FieldViewProps & InkDocument) { super(props); @@ -79,6 +81,8 @@ export class InkingStroke extends ViewBoxBaseComponent 1 && lineRight - lineLeft > 1, false); // Thin blue line indicating that the current ink stroke is selected. - const selectedLine = InteractionUtils.CreatePolyline(data, left - strokeWidth / 3, top - strokeWidth / 3, Colors.MEDIUM_BLUE, strokeWidth / 6, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), + 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); // Invisible polygonal line that enables the ink to be selected by the user. const clickableLine = InteractionUtils.CreatePolyline(data, left, top, this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier), @@ -143,6 +147,7 @@ export class InkingStroke extends ViewBoxBaseComponent : ""} -- cgit v1.2.3-70-g09d2 From 200585ebdc40b1c9710c8028e4e25965eaa73a9d Mon Sep 17 00:00:00 2001 From: vkalev Date: Thu, 12 Aug 2021 13:02:26 -0400 Subject: increasing opacity (selected lines are no longer slightly transparent) --- src/client/util/InteractionUtils.tsx | 4 ++-- src/client/views/InkingStroke.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 32b70c4b4..e009fb3a9 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -210,8 +210,8 @@ export namespace InteractionUtils { style={{ // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined, fill: fill ? fill : "none", - // opacity: 1.0, - opacity: strokeWidth !== width ? 0.5 : undefined, + opacity: 1.0, + // opacity: strokeWidth !== width ? 0.5 : undefined, pointerEvents: pevents as any, stroke: color ?? "rgb(0, 0, 0)", strokeWidth: strokeWidth, diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 4beab6a62..affea61e3 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -108,7 +108,7 @@ export class InkingStroke extends ViewBoxBaseComponent 1 && lineRight - lineLeft > 1, false); // Invisible polygonal line that enables the ink to be selected by the user. - const clickableLine = InteractionUtils.CreatePolyline(data, left, top, this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier), + 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); // 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), -- cgit v1.2.3-70-g09d2 From 7e439440a1d7fc3e3b29333b0502d6ed4d178309 Mon Sep 17 00:00:00 2001 From: vkalev Date: Wed, 18 Aug 2021 12:12:23 -0400 Subject: highlighter tool added & working --- src/client/util/InteractionUtils.tsx | 6 ++++-- src/client/views/GestureOverlay.tsx | 6 ++++-- src/client/views/InkHandles.tsx | 2 -- src/client/views/InkingStroke.tsx | 23 +++++++++++++++++------ src/client/views/collections/CollectionMenu.tsx | 21 ++++++++++++++------- 5 files changed, 39 insertions(+), 19 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index e009fb3a9..15a6709a2 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -3,6 +3,8 @@ import * as beziercurve from 'bezier-curve'; import * as fitCurve from 'fit-curve'; import "./InteractionUtils.scss"; import { Utils } from "../../Utils"; +import { CurrentUserUtils } from "./CurrentUserUtils"; +import { InkTool } from "../../fields/InkField"; export namespace InteractionUtils { export const MOUSETYPE = "mouse"; @@ -139,7 +141,7 @@ export namespace InteractionUtils { export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, - dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) { + dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) { let pts: { X: number; Y: number; }[] = []; if (shape) { //if any of the shape are true pts = makePolygon(shape, points); @@ -210,7 +212,7 @@ export namespace InteractionUtils { style={{ // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined, fill: fill ? fill : "none", - opacity: 1.0, + opacity: opacity, // opacity: strokeWidth !== width ? 0.5 : undefined, pointerEvents: pevents as any, stroke: color ?? "rgb(0, 0, 0)", diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index bbf21f22c..c77521572 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -494,6 +494,7 @@ export class GestureOverlay extends Touchable { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); + // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); } } @@ -733,6 +734,7 @@ export class GestureOverlay extends Touchable { const centerX = (Math.max(left, right) + Math.min(left, right)) / 2; const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2; const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom)); + // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve. this._points.push({ X: centerX - radius, Y: centerY }); this._points.push({ X: centerX - radius, Y: centerY + (c * radius) }); @@ -842,12 +844,12 @@ export class GestureOverlay extends Touchable { return {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), - ActiveDash(), 1, 1, this.InkShape, "none", false, false)} + ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} ; }), this._points.length <= 1 ? (null) : - {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false, false)} + {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} ] ]; } diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx index 0b24c3c32..b9f75f8d7 100644 --- a/src/client/views/InkHandles.tsx +++ b/src/client/views/InkHandles.tsx @@ -16,7 +16,6 @@ import { GestureOverlay } from "./GestureOverlay"; export interface InkHandlesProps { inkDoc: Doc; data: InkData; - shape?: string; format: number[]; ScreenToLocalTransform: () => Transform; } @@ -70,7 +69,6 @@ export class InkHandles extends React.Component { // Accessing the current ink's data and extracting all handle points and handle lines. const data = this.props.data; - const shape = this.props.shape; const handlePoints: HandlePoint[] = []; const handleLines: HandleLine[] = []; if (data.length >= 4) { diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 5fc159f14..7200d6da1 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -29,11 +29,13 @@ const InkDocument = makeInterface(documentSchema); 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(); } public static LayoutString(fieldStr: string) { @@ -75,12 +77,22 @@ export class InkingStroke extends ViewBoxBaseComponent { + if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) { + // this._previousColor = ActiveInkColor(); + SetActiveInkColor("rgba(245, 230, 95, 0.75)"); + } + // } else { + // SetActiveInkColor(this._previousColor); + // } + } + render() { TraceMobx(); this.toggleControlButton(); + // this.checkHighlighter(); // 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); @@ -101,13 +113,13 @@ export class InkingStroke extends ViewBoxBaseComponent 1 && lineRight - lineLeft > 1, false); + StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "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); + 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); + StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", 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); @@ -145,7 +157,6 @@ export class InkingStroke extends ViewBoxBaseComponent : ""} diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 8f4df4a92..835b3e575 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -614,12 +614,12 @@ export class CollectionFreeFormViewChrome extends React.Component Date: Sat, 21 Aug 2021 16:55:55 -0400 Subject: added unrounded endpoints for highlight ink strokes --- src/client/util/InteractionUtils.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 15a6709a2..a11c090a7 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -217,8 +217,8 @@ export namespace InteractionUtils { pointerEvents: pevents as any, stroke: color ?? "rgb(0, 0, 0)", strokeWidth: strokeWidth, - strokeLinejoin: "round", - strokeLinecap: "round", + strokeLinejoin: color === "rgba(245, 230, 95, 0.75)" ? "miter" : "round", + strokeLinecap: color === "rgba(245, 230, 95, 0.75)" ? "square" : "round", strokeDasharray: dashArray }} markerStart={`url(#${arrowStart + "Start" + defGuid})`} -- cgit v1.2.3-70-g09d2 From c9c9d00b581bc7f80edbf7848a8ffb3a8e64f005 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Sep 2021 10:14:04 -0400 Subject: event handling fixes for ink. double click on deselected ink takes shows handles. lightbox view works. transparent fills are not selectable. --- src/client/util/InteractionUtils.tsx | 2 +- src/client/views/InkingStroke.tsx | 48 ++++++++++++++++------ src/client/views/nodes/DocumentContentsView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 8 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 +- 5 files changed, 41 insertions(+), 21 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index e009fb3a9..8429a806a 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -209,7 +209,7 @@ export namespace InteractionUtils { points={strpts} style={{ // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined, - fill: fill ? fill : "none", + fill: fill && fill !== "transparent" ? fill : "none", opacity: 1.0, // opacity: strokeWidth !== width ? 0.5 : undefined, pointerEvents: pevents as any, diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index db09849fd..dc2a5b39b 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,26 +1,26 @@ 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, NumCast } 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 { GestureOverlay } from "./GestureOverlay"; +import { Colors } from "./global/globalEnums"; import { InkControls } from "./InkControls"; import { InkHandles } from "./InkHandles"; -import { Colors } from "./global/globalEnums"; -import { GestureOverlay } from "./GestureOverlay"; +import "./InkStroke.scss"; +import { InkStrokeProperties } from "./InkStrokeProperties"; +import { FieldView, FieldViewProps } from "./nodes/FieldView"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -29,6 +29,8 @@ const InkDocument = makeInterface(documentSchema); export class InkingStroke extends ViewBoxBaseComponent(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); @@ -36,6 +38,14 @@ export class InkingStroke extends ViewBoxBaseComponent 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) { return FieldView.LayoutString(InkingStroke, fieldStr); } @@ -53,14 +63,26 @@ export class InkingStroke extends ViewBoxBaseComponent { + 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 ); } } @@ -77,7 +99,6 @@ export class InkingStroke extends ViewBoxBaseComponent { const cm = ContextMenu.Instance; if (cm) { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index fad905d6d..dbab5e762 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -113,7 +113,6 @@ export class DocumentContentsView extends React.Component number, setHeight: (height: number) => void, layoutKey: string, - hideOnLeave?: boolean, }> { @computed get layout(): string { TraceMobx(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8b19fb204..a2d2f17b6 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -808,7 +808,7 @@ export class DocumentViewInternal extends DocComponent; return
+
field.trim()).map(field => targetDoc[field]?.toString()).join("\\")} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 78de1fd89..e7a44f113 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -69,7 +69,6 @@ const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { makeLink?: () => Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text - hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar) xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView yPadding?: number; noSidebar?: boolean; @@ -1558,7 +1557,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp TraceMobx(); const selected = this.props.isSelected(); const active = this.props.isContentActive(); - const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); -- cgit v1.2.3-70-g09d2 From 1561e37eb966607564938530a71aeb7e3ba27583 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Sep 2021 23:03:47 -0400 Subject: fixed ink stroke editing to work with closed curves (eg break / restore tangent & drag start/end point). changed ink rendering to use svg bezier curves. fixed bugs with keydown handler to break tangent. --- src/client/util/InteractionUtils.tsx | 51 +++++++++------------------------ src/client/views/GestureOverlay.tsx | 4 +-- src/client/views/InkControls.tsx | 10 +++++-- src/client/views/InkHandles.tsx | 38 +++++++++++++----------- src/client/views/InkStrokeProperties.ts | 11 ++++--- src/client/views/InkingStroke.tsx | 4 +-- 6 files changed, 53 insertions(+), 65 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 4a8011e3c..66afc849e 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -146,7 +146,7 @@ export namespace InteractionUtils { if (shape) { //if any of the shape are true pts = makePolygon(shape, points); } - else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) { + else if (((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) && !bezier) { for (var i = 0; i < points.length - 3; i += 4) { const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]]; for (var t = 0; t < 1; t += 0.01) { @@ -154,47 +154,22 @@ export namespace InteractionUtils { pts.push({ X: point[0], Y: point[1] }); } } - } - else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) { - //pointer is up (first and last points are the same) - const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); - newPoints.pop(); - - const bezierCurves = fitCurve(newPoints, parseInt(bezier)); - for (const curve of bezierCurves) { - for (var t = 0; t < 1; t += 0.01) { - const point = beziercurve(t, curve); - pts.push({ X: point[0], Y: point[1] }); - } - } } else { pts = points.slice(); - // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag. need to remove it here. - if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) { - pts.pop(); - } - } - if (isNaN(scalex)) { - scalex = 1; - } - if (isNaN(scaley)) { - scaley = 1; } - const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc + - `${(pt.X - left - width / 2) * scalex + width / 2}, - ${(pt.Y - top - width / 2) * scaley + width / 2} `, ""); + if (isNaN(scalex)) scalex = 1; + if (isNaN(scaley)) scaley = 1; + + const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `; + var strpts = bezier ? + pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pts[i]) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : + pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, ""); + const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; const defGuid = Utils.GenerateGuid(); const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth))); - const addables = pts.map((pts, i) => - - { console.log(i); }} pointerEvents="all" cursor="all-scroll" - /> - ); - - + const Tag = (bezier ? "path" : "polyline") as keyof JSX.IntrinsicElements; return ( {/* setting the svg fill sets the arrowStart fill */} {nodefs ? (null) : {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : @@ -207,8 +182,10 @@ export namespace InteractionUtils { } } - - {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} + {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "", ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} ] ]; } diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx index 4df7ee813..7e685288d 100644 --- a/src/client/views/InkControls.tsx +++ b/src/client/views/InkControls.tsx @@ -37,8 +37,8 @@ export class InkControls extends React.Component { const controlUndo = UndoManager.StartBatch("DocDecs set radius"); const screenScale = this.props.ScreenToLocalTransform().Scale; const order = controlIndex % 4; - const handleIndexA = order === 2 ? controlIndex - 1 : controlIndex - 2; - const handleIndexB = order === 2 ? controlIndex + 2 : controlIndex + 1; + const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; + const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { @@ -47,7 +47,11 @@ export class InkControls extends React.Component { }, () => controlUndo?.end(), action((e: PointerEvent, doubleTap: boolean | undefined) => { - if (doubleTap && brokenIndices && brokenIndices.includes(controlIndex)) { + const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; + if (doubleTap && brokenIndices?.includes(equivIndex)) { + InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); + } + if (doubleTap && brokenIndices?.includes(controlIndex)) { InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); } })); diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx index afe94cdfb..1a514bdce 100644 --- a/src/client/views/InkHandles.tsx +++ b/src/client/views/InkHandles.tsx @@ -31,10 +31,11 @@ export class InkHandles extends React.Component { const controlUndo = UndoManager.StartBatch("DocDecs set radius"); const screenScale = this.props.ScreenToLocalTransform().Scale; const order = handleIndex % 4; - const oppositeHandleIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; - const controlIndex = order === 1 ? handleIndex - 1 : handleIndex + 2; - document.addEventListener("keydown", (e: KeyboardEvent) => this.onBreakTangent(e, controlIndex), true); + const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; + const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.data.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.data.length; + const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.data.length; setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { + if (e.altKey) this.onBreakTangent(controlIndex); InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); return false; }, () => controlUndo?.end(), emptyFunction @@ -48,15 +49,14 @@ export class InkHandles extends React.Component { * @param handleNum The index of the currently selected handle point. */ @action - onBreakTangent = (e: KeyboardEvent, controlIndex: number) => { - const doc: Doc = this.props.inkDoc; - if (["Alt"].includes(e.key)) { - e.stopPropagation(); - if (doc) { - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; - if (brokenIndices && !brokenIndices.includes(controlIndex)) { - brokenIndices.push(controlIndex); - } + onBreakTangent = (controlIndex: number) => { + const doc = this.props.inkDoc; + if (doc) { + const closed = this.props.data.lastElement().X === this.props.data[0].X && this.props.data.lastElement().Y === this.props.data[0].Y; + const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; + if (!brokenIndices?.includes(controlIndex) && + ((controlIndex > 0 && controlIndex < this.props.data.length - 1) || closed)) { + brokenIndices.push(controlIndex); doc.brokenInkIndices = brokenIndices; } } @@ -70,14 +70,20 @@ export class InkHandles extends React.Component { const data = this.props.data; const handlePoints: HandlePoint[] = []; const handleLines: HandleLine[] = []; + const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; if (data.length >= 4) { for (let 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 }); + handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : 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 ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); } // Adding first and last (single) handle lines. - handleLines.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 }); - handleLines.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 }); + if (closed) { + handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); + } + else { + handleLines.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 }); + handleLines.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 }); + } for (let i = 2; i < data.length - 4; i += 4) { handleLines.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 }); } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 42190238e..2073497b9 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,14 +1,14 @@ import { action, computed, observable, reaction } from "mobx"; -import { Doc, DocListCast, Field, Opt } from "../../fields/Doc"; +import { Doc, Field, Opt } from "../../fields/Doc"; import { Document } from "../../fields/documentSchemas"; -import { InkField, InkData, PointData, ControlPoint, InkTool } from "../../fields/InkField"; +import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast, NumCast } from "../../fields/Types"; import { DocumentType } from "../documents/DocumentTypes"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; @@ -241,6 +241,7 @@ export class InkStrokeProperties { this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const newPoints: { X: number, Y: number }[] = []; const order = controlIndex % 4; + const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; for (var i = 0; i < ink.length; i++) { const leftHandlePoint = order === 0 && i === controlIndex + 1; const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; @@ -248,6 +249,7 @@ export class InkStrokeProperties { leftHandlePoint || rightHandlePoint || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || + ((order === 0 || order === 3) && (controlIndex === 0 || controlIndex === ink.length - 1) && (i === 1 || i === ink.length - 2) && closed) || (order === 3 && i === controlIndex - 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || @@ -335,6 +337,7 @@ export class InkStrokeProperties { @action moveHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { + const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; const oldHandlePoint = ink[handleIndex]; let oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; @@ -342,7 +345,7 @@ export class InkStrokeProperties { ink[handleIndex] = newHandlePoint; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). - if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && handleIndex !== 1 && handleIndex !== ink.length - 2) { + if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); ink[oppositeHandleIndex] = oppositeHandlePoint; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index ca39bdaa1..a518bf07b 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -11,14 +11,11 @@ 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 { InkControls } from "./InkControls"; import { InkHandles } from "./InkHandles"; -import { GestureOverlay } from "./GestureOverlay"; -import { isThisTypeNode } from "typescript"; import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; @@ -83,6 +80,7 @@ export class InkingStroke extends ViewBoxBaseComponent Date: Tue, 28 Sep 2021 13:59:28 -0400 Subject: converted ink addpoints to not use sampling. needs addPoints() to be filled in. --- package-lock.json | 10 +++ package.json | 2 + src/client/util/InteractionUtils.tsx | 65 +----------------- src/client/views/InkControls.tsx | 46 ++++++------- src/client/views/InkStrokeProperties.ts | 116 +++++++++++++++++--------------- src/client/views/InkingStroke.tsx | 50 +++++++++++--- 6 files changed, 138 insertions(+), 151 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/package-lock.json b/package-lock.json index 7810e3120..c1dd8506f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -616,6 +616,11 @@ "integrity": "sha1-TN2WtJKTs5MhIuS34pVD415rrlg=", "dev": true }, + "@types/bezier-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/bezier-js/-/bezier-js-4.1.0.tgz", + "integrity": "sha512-ElU16s8E6Pr6magp8ihwH1O8pbUJASbMND/qgUc9RsLmP3lMLHiDMRXdjtaObwW5GPtOVYOsXDUIhTIluT+yaw==" + }, "@types/bluebird": { "version": "3.5.32", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.32.tgz", @@ -2628,6 +2633,11 @@ "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz", "integrity": "sha1-o9+v6rEqlMRicw1QeYxSqEBdc3k=" }, + "bezier-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-4.1.1.tgz", + "integrity": "sha512-oVOS6SSFFFlfnZdzC+lsfvhs/RRcbxJ47U04M4s5QIBaJmr3YWmTIL3qmrOK9uW+nUUcl9Jccmo/xpTrG+bBoQ==" + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", diff --git a/package.json b/package.json index 6cbde9e1f..5d10c0d54 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "@hig/theme-data": "^2.16.1", "@material-ui/core": "^4.11.0", "@react-three/fiber": "^6.0.16", + "@types/bezier-js": "^4.1.0", "@types/cors": "^2.8.8", "@types/d3-axis": "^2.0.0", "@types/d3-color": "^2.0.1", @@ -145,6 +146,7 @@ "babel-runtime": "^6.26.0", "bcrypt-nodejs": "0.0.3", "bezier-curve": "^1.0.0", + "bezier-js": "^4.1.1", "bluebird": "^3.7.2", "body-parser": "^1.18.3", "bootstrap": "^4.5.0", diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 66afc849e..f748188d7 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,10 +1,8 @@ import React = require("react"); import * as beziercurve from 'bezier-curve'; import * as fitCurve from 'fit-curve'; -import "./InteractionUtils.scss"; import { Utils } from "../../Utils"; -import { CurrentUserUtils } from "./CurrentUserUtils"; -import { InkTool } from "../../fields/InkField"; +import "./InteractionUtils.scss"; export namespace InteractionUtils { export const MOUSETYPE = "mouse"; @@ -93,70 +91,13 @@ export namespace InteractionUtils { return myTouches; } - export function CreatePoints(points: { X: number, Y: number }[], left: number, top: number, - color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, - dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) { - let pts: { X: number; Y: number; }[] = []; - if (shape) { //if any of the shape are true - pts = makePolygon(shape, points); - } - else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) { - for (var i = 0; i < points.length - 3; i += 4) { - const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]]; - for (var t = 0; t < 1; t += 0.01) { - const point = beziercurve(t, array); - pts.push({ X: point[0], Y: point[1] }); - } - } - } - else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) { - //pointer is up (first and last points are the same) - const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); - newPoints.pop(); - - const bezierCurves = fitCurve(newPoints, parseInt(bezier)); - for (const curve of bezierCurves) { - for (var t = 0; t < 1; t += 0.01) { - const point = beziercurve(t, curve); - pts.push({ X: point[0], Y: point[1] }); - } - } - } else { - pts = points.slice(); - // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag. need to remove it here. - if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) { - pts.pop(); - } - } - if (isNaN(scalex)) { - scalex = 1; - } - if (isNaN(scaley)) { - scaley = 1; - } - return pts; - } - - - export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) { let pts: { X: number; Y: number; }[] = []; if (shape) { //if any of the shape are true - pts = makePolygon(shape, points); - } - else if (((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) && !bezier) { - for (var i = 0; i < points.length - 3; i += 4) { - const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]]; - for (var t = 0; t < 1; t += 0.01) { - const point = beziercurve(t, array); - pts.push({ X: point[0], Y: point[1] }); - } - } - } else { - pts = points.slice(); - } + const pts = shape ? makePolygon(shape, points) : points; + if (isNaN(scalex)) scalex = 1; if (isNaN(scaley)) scaley = 1; diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx index 7e685288d..5fe0c0f8a 100644 --- a/src/client/views/InkControls.tsx +++ b/src/client/views/InkControls.tsx @@ -5,7 +5,7 @@ import { Doc } from "../../fields/Doc"; import { ControlPoint, InkData, PointData } from "../../fields/InkField"; import { listSpec } from "../../fields/Schema"; import { Cast } from "../../fields/Types"; -import { setupMoveUpEvents } from "../../Utils"; +import { setupMoveUpEvents, Utils } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; @@ -15,17 +15,16 @@ export interface InkControlProps { inkDoc: Doc; inkCtrlPoints: InkData; screenCtrlPoints: InkData; - inkStrokeSamplePts: PointData[]; - screenStrokeSamplePoints: PointData[]; format: number[]; ScreenToLocalTransform: () => Transform; + nearestScreenPt: () => PointData | undefined; } @observer export class InkControls extends React.Component { + @observable private _overControl = -1; @observable private _overAddPoint = -1; - /** * Handles the movement of a selected control point when the user clicks and drags. * @param controlIndex The index of the currently selected control point. @@ -57,6 +56,14 @@ export class InkControls extends React.Component { })); } } + /** + * Updates whether a user has hovered over a particular control point or point that could be added + * on click. + */ + @action onEnterControl = (i: number) => { this._overControl = i; }; + @action onLeaveControl = () => { this._overControl = -1; }; + @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; + @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; /** * Deletes the currently selected point. @@ -79,19 +86,11 @@ export class InkControls extends React.Component { } } - /** - * Updates whether a user has hovered over a particular control point or point that could be added - * on click. - */ - @action onEnterControl = (i: number) => { this._overControl = i; }; - @action onLeaveControl = () => { this._overControl = -1; }; - @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; - @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; - render() { const formatInstance = InkStrokeProperties.Instance; if (!formatInstance) return (null); + // Accessing the current ink's data and extracting all control points. const scrData = this.props.screenCtrlPoints; const sreenCtrlPoints: ControlPoint[] = []; @@ -109,24 +108,23 @@ export class InkControls extends React.Component { const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format; const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; + + const nearestScreenPt = this.props.nearestScreenPt(); return ( {/* should really have just one circle here that represents the neqraest point on the stroke to the users hover point. This points should be passed as a prop from InkingStroke's UI which should set it in its onPointerOver method */} - {this.props.screenStrokeSamplePoints.map((pts, i) => - formatInstance?.addPoints(this.props.inkStrokeSamplePts[i].X, this.props.inkStrokeSamplePts[i].Y, this.props.inkStrokeSamplePts, i, inkCtrlPts)} - onMouseEnter={() => this.onEnterAddPoint(i)} - onMouseLeave={this.onLeaveAddPoint} - pointerEvents="all" + pointerEvents="none" cursor="all-scroll" /> - )} + } {sreenCtrlPoints.map((control, i) => { - this.applyFunction((doc: Doc, ink: InkData) => { - const newControl = { X: x, Y: y }; - const newPoints: InkData = []; - let [counter, start, end] = [0, 0, 0]; - for (let k = 0; k < points.length; k++) { - if (end === 0) { - controls.forEach((control) => { - if (control.X === points[k].X && control.Y === points[k].Y) { - if (k < index) { - counter++; - start = k; - } else if (k > index) { - end = k; - } - } - }); - } - } - if (end === 0) end = points.length - 1; - // Index of new control point with regards to the ink data. - const newIndex = Math.floor(counter / 2) * 4 + 2; - // Creating new ink data with the new control point and handle points inputted. - for (let i = 0; i < ink.length; i++) { - if (i === newIndex) { - const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index + 1), points.slice(index, end), newControl); - newPoints.push(handleA, newControl, newControl, handleB); - // Adjusting the magnitude of the left handle line of the right neighboring control point. - const [rightControl, rightHandle] = [points[end], ink[i]]; - const scaledVector = this.getScaledHandlePoint(false, start, end, index, rightControl, rightHandle); - rightHandle && newPoints.push({ X: rightControl.X - scaledVector.X, Y: rightControl.Y - scaledVector.Y }); - } else if (i === newIndex - 1) { - // Adjusting the magnitude of the right handle line of the left neighboring control point. - const [leftControl, leftHandle] = [points[start], ink[i]]; - const scaledVector = this.getScaledHandlePoint(true, start, end, index, leftControl, leftHandle); - leftHandle && newPoints.push({ X: leftControl.X - scaledVector.X, Y: leftControl.Y - scaledVector.Y }); - } else { - ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y }); - } + addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => { + const array = [{ x: controls[i].X, y: controls[i].Y }, { x: controls[i + 1].X, y: controls[i + 1].Y }, { x: controls[i + 2].X, y: controls[i + 2].Y }, { x: controls[i + 3].X, y: controls[i + 3].Y }]; + const newsegs = new Bezier(array).split(t); + // this.applyFunction((doc: Doc, ink: InkData) => { + // const newControl = { X: x, Y: y }; + // const newPoints: InkData = []; + // let [counter, start, end] = [0, 0, 0]; + // for (let k = 0; k < points.length; k++) { + // if (end === 0) { + // controls.forEach((control) => { + // if (control.X === points[k].X && control.Y === points[k].Y) { + // if (k < index) { + // counter++; + // start = k; + // } else if (k > index) { + // end = k; + // } + // } + // }); + // } + // } + // if (end === 0) end = points.length - 1; + // // Index of new control point with regards to the ink data. + // const newIndex = Math.floor(counter / 2) * 4 + 2; + // // Creating new ink data with the new control point and handle points inputted. + // for (let i = 0; i < ink.length; i++) { + // if (i === newIndex) { + // const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index + 1), points.slice(index, end), newControl); + // newPoints.push(handleA, newControl, newControl, handleB); + // // Adjusting the magnitude of the left handle line of the right neighboring control point. + // const [rightControl, rightHandle] = [points[end], ink[i]]; + // const scaledVector = this.getScaledHandlePoint(false, start, end, index, rightControl, rightHandle); + // rightHandle && newPoints.push({ X: rightControl.X - scaledVector.X, Y: rightControl.Y - scaledVector.Y }); + // } else if (i === newIndex - 1) { + // // Adjusting the magnitude of the right handle line of the left neighboring control point. + // const [leftControl, leftHandle] = [points[start], ink[i]]; + // const scaledVector = this.getScaledHandlePoint(true, start, end, index, leftControl, leftHandle); + // leftHandle && newPoints.push({ X: leftControl.X - scaledVector.X, Y: leftControl.Y - scaledVector.Y }); + // } else { + // ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y }); + // } - } - let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); - // Updating the indices of the control points whose handle tangency has been broken. - if (brokenIndices) { - brokenIndices = new List(brokenIndices.map((control) => { - if (control >= newIndex) { - return control + 4; - } else { - return control; - } - })); - } - doc.brokenInkIndices = brokenIndices; - this._currentPoint = -1; - return newPoints; - }); + // } + // let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); + // // Updating the indices of the control points whose handle tangency has been broken. + // if (brokenIndices) { + // brokenIndices = new List(brokenIndices.map((control) => { + // if (control >= newIndex) { + // return control + 4; + // } else { + // return control; + // } + // })); + // } + // doc.brokenInkIndices = brokenIndices; + // this._currentPoint = -1; + // return newPoints; + // }); } /** diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index a518bf07b..efe2e5f66 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -16,6 +16,7 @@ import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; import { InkControls } from "./InkControls"; import { InkHandles } from "./InkHandles"; +import { Bezier } from "bezier-js"; import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; @@ -82,6 +83,8 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt; + + @action + onPointerOver = (e: React.PointerEvent) => { + const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); + const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth)[0]; + 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 / 2; + const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth / 2; + var nearest = Number.MAX_SAFE_INTEGER; + this._nearestT = -1; + this._nearestSeg = -1; + this._nearestScrPt = { X: 0, Y: 0 }; + for (var i = 0; i < screenPts.length - 3; i += 4) { + const array = [{ x: screenPts[i].X, y: screenPts[i].Y }, { x: screenPts[i + 1].X, y: screenPts[i + 1].Y }, { x: screenPts[i + 2].X, y: screenPts[i + 2].Y }, { x: screenPts[i + 3].X, y: screenPts[i + 3].Y }]; + const point = new Bezier(array).project({ x: e.clientX + screenLeft - screenOrigin[0], y: e.clientY + screenTop - screenOrigin[1] }); + if (point.t) { + const dist = (point.x - screenLeft - e.clientX + screenOrigin[0]) * (point.x - screenLeft - e.clientX + screenOrigin[0]) + + (point.y - screenTop - e.clientY + screenOrigin[1]) * (point.y - screenTop - e.clientY + screenOrigin[1]); + if (dist < nearest) { + nearest = dist; + this._nearestT = point.t; + this._nearestSeg = i; + this._nearestScrPt = { X: point.x, Y: point.y }; + } + } + } + } componentUI = (boundsLeft: number, boundsTop: number) => { const inkDoc = this.props.Document; @@ -141,13 +177,6 @@ export class InkingStroke extends ViewBoxBaseComponent 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
+ this._nearestScrPt = undefined + )} + onPointerMove={this.onPointerOver} onPointerDown={this.onPointerDown} onClick={this.onClick} onContextMenu={() => { -- cgit v1.2.3-70-g09d2 From 2a6385ad330ac4d7caf6b23e64cb13b7546ea44a Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 13:59:37 -0400 Subject: from last --- src/client/util/InteractionUtils.tsx | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index f748188d7..9f3159d83 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -94,8 +94,6 @@ export namespace InteractionUtils { export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) { - let pts: { X: number; Y: number; }[] = []; - if (shape) { //if any of the shape are true const pts = shape ? makePolygon(shape, points) : points; if (isNaN(scalex)) scalex = 1; -- cgit v1.2.3-70-g09d2 From bc654229325e8bbd30c0b3e464c7e66fa0fbc609 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 18:03:07 -0400 Subject: a bunch of code cleanup for inkingstrokes --- src/client/util/InteractionUtils.tsx | 2 - src/client/views/InkControls.tsx | 24 +++---- src/client/views/InkHandles.tsx | 44 ++++++------- src/client/views/InkStrokeProperties.ts | 1 - src/client/views/InkingStroke.tsx | 109 ++++++++++---------------------- 5 files changed, 68 insertions(+), 112 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 9f3159d83..3e7a4924f 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,6 +1,4 @@ import React = require("react"); -import * as beziercurve from 'bezier-curve'; -import * as fitCurve from 'fit-curve'; import { Utils } from "../../Utils"; import "./InteractionUtils.scss"; diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx index ee09273e3..eb0eebcdf 100644 --- a/src/client/views/InkControls.tsx +++ b/src/client/views/InkControls.tsx @@ -5,7 +5,7 @@ import { Doc } from "../../fields/Doc"; import { ControlPoint, InkData, PointData } from "../../fields/InkField"; import { listSpec } from "../../fields/Schema"; import { Cast } from "../../fields/Types"; -import { setupMoveUpEvents, Utils } from "../../Utils"; +import { setupMoveUpEvents } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; @@ -15,13 +15,13 @@ export interface InkControlProps { inkDoc: Doc; inkCtrlPoints: InkData; screenCtrlPoints: InkData; - format: number[]; + screenSpaceLineWidth: number; ScreenToLocalTransform: () => Transform; nearestScreenPt: () => PointData | undefined; } @observer -export class InkControls extends React.Component { +export class InkControlPtHandles extends React.Component { @observable private _overControl = -1; @observable private _overAddPoint = -1; @@ -94,18 +94,18 @@ export class InkControls extends React.Component { const scrData = this.props.screenCtrlPoints; const sreenCtrlPoints: ControlPoint[] = []; for (let i = 0; i <= scrData.length - 4; i += 4) { - sreenCtrlPoints.push({ X: scrData[i].X, Y: scrData[i].Y, I: i }); - sreenCtrlPoints.push({ X: scrData[i + 3].X, Y: scrData[i + 3].Y, I: i + 3 }); + sreenCtrlPoints.push({ ...scrData[i], I: i }); + sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 }); } const inkData = this.props.inkCtrlPoints; const inkCtrlPts: ControlPoint[] = []; for (let i = 0; i <= inkData.length - 4; i += 4) { - inkCtrlPts.push({ X: inkData[i].X, Y: inkData[i].Y, I: i }); - inkCtrlPts.push({ X: inkData[i + 3].X, Y: inkData[i + 3].Y, I: i + 3 }); + inkCtrlPts.push({ ...inkData[i], I: i }); + inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 }); } - const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format; + const screenSpaceLineWidth = this.props.screenSpaceLineWidth; const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; const nearestScreenPt = this.props.nearestScreenPt(); @@ -124,18 +124,18 @@ export class InkControls extends React.Component { } {sreenCtrlPoints.map((control, i) => { + onPointerDown={e => { this.changeCurrPoint(control.I); this.onControlDown(e, control.I); }} - onMouseEnter={() => this.onEnterControl(i)} + onMouseEnter={e => this.onEnterControl(i)} onMouseLeave={this.onLeaveControl} pointerEvents="all" cursor="default" diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx index 1a514bdce..dbe9ca027 100644 --- a/src/client/views/InkHandles.tsx +++ b/src/client/views/InkHandles.tsx @@ -14,13 +14,13 @@ import { InkStrokeProperties } from "./InkStrokeProperties"; export interface InkHandlesProps { inkDoc: Doc; - data: InkData; - format: number[]; + screenCtrlPoints: InkData; + screenSpaceLineWidth: number; ScreenToLocalTransform: () => Transform; } @observer -export class InkHandles extends React.Component { +export class InkTangentHandles extends React.Component { /** * Handles the movement of a selected handle point when the user clicks and drags. * @param handleNum The index of the currently selected handle point. @@ -32,8 +32,8 @@ export class InkHandles extends React.Component { const screenScale = this.props.ScreenToLocalTransform().Scale; const order = handleIndex % 4; const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; - const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.data.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.data.length; - const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.data.length; + const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; + const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { if (e.altKey) this.onBreakTangent(controlIndex); InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); @@ -52,10 +52,10 @@ export class InkHandles extends React.Component { onBreakTangent = (controlIndex: number) => { const doc = this.props.inkDoc; if (doc) { - const closed = this.props.data.lastElement().X === this.props.data[0].X && this.props.data.lastElement().Y === this.props.data[0].Y; + const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; if (!brokenIndices?.includes(controlIndex) && - ((controlIndex > 0 && controlIndex < this.props.data.length - 1) || closed)) { + ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { brokenIndices.push(controlIndex); doc.brokenInkIndices = brokenIndices; } @@ -67,14 +67,14 @@ export class InkHandles extends React.Component { if (!formatInstance) return (null); // Accessing the current ink's data and extracting all handle points and handle lines. - const data = this.props.data; + const data = this.props.screenCtrlPoints; const handlePoints: HandlePoint[] = []; const handleLines: HandleLine[] = []; const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; if (data.length >= 4) { for (let 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 ? (closed ? data.length - 1 : 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 ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); + handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); + handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); } // Adding first and last (single) handle lines. if (closed) { @@ -88,19 +88,19 @@ export class InkHandles extends React.Component { handleLines.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 }); } } - const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format; + const screenSpaceLineWidth = this.props.screenSpaceLineWidth; return ( <> {handlePoints.map((pts, i) => this.onHandleDown(e, pts.I)} + onPointerDown={e => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> @@ -108,18 +108,18 @@ export class InkHandles extends React.Component { {handleLines.map((pts, i) => diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 0b7fe5cd1..cfe6ec523 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -10,7 +10,6 @@ import { DocumentType } from "../documents/DocumentTypes"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; -import { last } from "lodash"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 40bed0eca..c9e401ab4 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,4 +1,5 @@ import React = require("react"); +import { Bezier } from "bezier-js"; import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; @@ -14,9 +15,8 @@ import { InteractionUtils } from "../util/InteractionUtils"; import { ContextMenu } from "./ContextMenu"; import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; -import { InkControls } from "./InkControls"; -import { InkHandles } from "./InkHandles"; -import { Bezier } from "bezier-js"; +import { InkControlPtHandles } from "./InkControls"; +import { InkTangentHandles } from "./InkHandles"; import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; @@ -26,16 +26,21 @@ const InkDocument = makeInterface(documentSchema); @observer export class InkingStroke extends ViewBoxBaseComponent(InkDocument) { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } 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; + @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); this._properties = InkStrokeProperties.Instance; - // this._previousColor = ActiveInkColor(); } componentDidMount() { @@ -47,10 +52,6 @@ export class InkingStroke extends ViewBoxBaseComponent { - 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. */ @@ -107,13 +101,10 @@ export class InkingStroke extends ViewBoxBaseComponent { - const inkData: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; + 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; @@ -132,20 +123,13 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt; @action onPointerOver = (e: React.PointerEvent) => { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth)[0]; - const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, + 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 screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth / 2; - const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth / 2; var nearest = Number.MAX_SAFE_INTEGER; this._nearestT = -1; this._nearestSeg = -1; @@ -159,59 +143,54 @@ export class InkingStroke extends ViewBoxBaseComponent 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, 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 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 screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + const screenHdlPts = screenPts; return
- {InteractionUtils.CreatePolyline(screenPts, screenLeft, screenTop, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, + {InteractionUtils.CreatePolyline(screenPts, 0, 0, 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 ? + StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} + {!this._properties?._controlButton ? (null) : <> - - - : ""} + }
; } render() { TraceMobx(); - // 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; + const strokeColor = StrCast(this.layoutDoc.color); // 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), @@ -232,39 +211,19 @@ export class InkingStroke extends ViewBoxBaseComponent - console.log("") //this._nearestScrPt = undefined - )} + onPointerLeave={action(e => this._nearestScrPt = undefined)} onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined} onPointerDown={this.onPointerDown} - onClick={this.onClick} + 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 ? - <> - - - : ""} */} ); } -- cgit v1.2.3-70-g09d2 From 244e06ec9873888dcef3cd08322880d73848fe69 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 18:10:26 -0400 Subject: renamed some ink files --- src/client/util/InteractionUtils.tsx | 4 +- src/client/views/InkControlPtHandles.tsx | 147 +++++++++++++++++++++++++++++++ src/client/views/InkControls.tsx | 147 ------------------------------- src/client/views/InkHandles.tsx | 130 --------------------------- src/client/views/InkTangentHandles.tsx | 130 +++++++++++++++++++++++++++ src/client/views/InkingStroke.tsx | 4 +- 6 files changed, 281 insertions(+), 281 deletions(-) create mode 100644 src/client/views/InkControlPtHandles.tsx delete mode 100644 src/client/views/InkControls.tsx delete mode 100644 src/client/views/InkHandles.tsx create mode 100644 src/client/views/InkTangentHandles.tsx (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 3e7a4924f..71d68262f 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -98,8 +98,8 @@ export namespace InteractionUtils { if (isNaN(scaley)) scaley = 1; const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `; - var strpts = bezier ? - pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pts[i]) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : + const strpts = bezier ? + pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, ""); const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx new file mode 100644 index 000000000..eb0eebcdf --- /dev/null +++ b/src/client/views/InkControlPtHandles.tsx @@ -0,0 +1,147 @@ +import React = require("react"); +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Doc } from "../../fields/Doc"; +import { ControlPoint, InkData, PointData } from "../../fields/InkField"; +import { listSpec } from "../../fields/Schema"; +import { Cast } from "../../fields/Types"; +import { setupMoveUpEvents } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { UndoManager } from "../util/UndoManager"; +import { Colors } from "./global/globalEnums"; +import { InkStrokeProperties } from "./InkStrokeProperties"; + +export interface InkControlProps { + inkDoc: Doc; + inkCtrlPoints: InkData; + screenCtrlPoints: InkData; + screenSpaceLineWidth: number; + ScreenToLocalTransform: () => Transform; + nearestScreenPt: () => PointData | undefined; +} + +@observer +export class InkControlPtHandles extends React.Component { + + @observable private _overControl = -1; + @observable private _overAddPoint = -1; + /** + * Handles the movement of a selected control point when the user clicks and drags. + * @param controlIndex The index of the currently selected control point. + */ + @action + onControlDown = (e: React.PointerEvent, controlIndex: number): void => { + if (InkStrokeProperties.Instance) { + InkStrokeProperties.Instance.moveControl(0, 0, 1); + const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + const screenScale = this.props.ScreenToLocalTransform().Scale; + const order = controlIndex % 4; + const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; + const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; + const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); + setupMoveUpEvents(this, e, + (e: PointerEvent, down: number[], delta: number[]) => { + InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); + return false; + }, + () => controlUndo?.end(), + action((e: PointerEvent, doubleTap: boolean | undefined) => { + const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; + if (doubleTap && brokenIndices?.includes(equivIndex)) { + InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); + } + if (doubleTap && brokenIndices?.includes(controlIndex)) { + InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + } + })); + } + } + /** + * Updates whether a user has hovered over a particular control point or point that could be added + * on click. + */ + @action onEnterControl = (i: number) => { this._overControl = i; }; + @action onLeaveControl = () => { this._overControl = -1; }; + @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; + @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; + + /** + * Deletes the currently selected point. + */ + @action + onDelete = (e: KeyboardEvent) => { + if (["-", "Backspace", "Delete"].includes(e.key)) { + if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation(); + } + } + + /** + * Changes the current selected control point. + */ + @action + changeCurrPoint = (i: number) => { + if (InkStrokeProperties.Instance) { + InkStrokeProperties.Instance._currentPoint = i; + document.addEventListener("keydown", this.onDelete, true); + } + } + + render() { + const formatInstance = InkStrokeProperties.Instance; + if (!formatInstance) return (null); + + // Accessing the current ink's data and extracting all control points. + const scrData = this.props.screenCtrlPoints; + const sreenCtrlPoints: ControlPoint[] = []; + for (let i = 0; i <= scrData.length - 4; i += 4) { + sreenCtrlPoints.push({ ...scrData[i], I: i }); + sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 }); + } + + const inkData = this.props.inkCtrlPoints; + const inkCtrlPts: ControlPoint[] = []; + for (let i = 0; i <= inkData.length - 4; i += 4) { + inkCtrlPts.push({ ...inkData[i], I: i }); + inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 }); + } + + const screenSpaceLineWidth = this.props.screenSpaceLineWidth; + const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; + + const nearestScreenPt = this.props.nearestScreenPt(); + return ( + {!nearestScreenPt ? (null) : + + } + {sreenCtrlPoints.map((control, i) => + { + this.changeCurrPoint(control.I); + this.onControlDown(e, control.I); + }} + onMouseEnter={e => this.onEnterControl(i)} + onMouseLeave={this.onLeaveControl} + pointerEvents="all" + cursor="default" + /> + )} + + ); + } +} \ No newline at end of file diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx deleted file mode 100644 index eb0eebcdf..000000000 --- a/src/client/views/InkControls.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import React = require("react"); -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../fields/Doc"; -import { ControlPoint, InkData, PointData } from "../../fields/InkField"; -import { listSpec } from "../../fields/Schema"; -import { Cast } from "../../fields/Types"; -import { setupMoveUpEvents } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { UndoManager } from "../util/UndoManager"; -import { Colors } from "./global/globalEnums"; -import { InkStrokeProperties } from "./InkStrokeProperties"; - -export interface InkControlProps { - inkDoc: Doc; - inkCtrlPoints: InkData; - screenCtrlPoints: InkData; - screenSpaceLineWidth: number; - ScreenToLocalTransform: () => Transform; - nearestScreenPt: () => PointData | undefined; -} - -@observer -export class InkControlPtHandles extends React.Component { - - @observable private _overControl = -1; - @observable private _overAddPoint = -1; - /** - * Handles the movement of a selected control point when the user clicks and drags. - * @param controlIndex The index of the currently selected control point. - */ - @action - onControlDown = (e: React.PointerEvent, controlIndex: number): void => { - if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance.moveControl(0, 0, 1); - const controlUndo = UndoManager.StartBatch("DocDecs set radius"); - const screenScale = this.props.ScreenToLocalTransform().Scale; - const order = controlIndex % 4; - const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; - const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; - const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); - setupMoveUpEvents(this, e, - (e: PointerEvent, down: number[], delta: number[]) => { - InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); - return false; - }, - () => controlUndo?.end(), - action((e: PointerEvent, doubleTap: boolean | undefined) => { - const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; - if (doubleTap && brokenIndices?.includes(equivIndex)) { - InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); - } - if (doubleTap && brokenIndices?.includes(controlIndex)) { - InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); - } - })); - } - } - /** - * Updates whether a user has hovered over a particular control point or point that could be added - * on click. - */ - @action onEnterControl = (i: number) => { this._overControl = i; }; - @action onLeaveControl = () => { this._overControl = -1; }; - @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; - @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; - - /** - * Deletes the currently selected point. - */ - @action - onDelete = (e: KeyboardEvent) => { - if (["-", "Backspace", "Delete"].includes(e.key)) { - if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation(); - } - } - - /** - * Changes the current selected control point. - */ - @action - changeCurrPoint = (i: number) => { - if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance._currentPoint = i; - document.addEventListener("keydown", this.onDelete, true); - } - } - - render() { - const formatInstance = InkStrokeProperties.Instance; - if (!formatInstance) return (null); - - // Accessing the current ink's data and extracting all control points. - const scrData = this.props.screenCtrlPoints; - const sreenCtrlPoints: ControlPoint[] = []; - for (let i = 0; i <= scrData.length - 4; i += 4) { - sreenCtrlPoints.push({ ...scrData[i], I: i }); - sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 }); - } - - const inkData = this.props.inkCtrlPoints; - const inkCtrlPts: ControlPoint[] = []; - for (let i = 0; i <= inkData.length - 4; i += 4) { - inkCtrlPts.push({ ...inkData[i], I: i }); - inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 }); - } - - const screenSpaceLineWidth = this.props.screenSpaceLineWidth; - const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; - - const nearestScreenPt = this.props.nearestScreenPt(); - return ( - {!nearestScreenPt ? (null) : - - } - {sreenCtrlPoints.map((control, i) => - { - this.changeCurrPoint(control.I); - this.onControlDown(e, control.I); - }} - onMouseEnter={e => this.onEnterControl(i)} - onMouseLeave={this.onLeaveControl} - pointerEvents="all" - cursor="default" - /> - )} - - ); - } -} \ No newline at end of file diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx deleted file mode 100644 index dbe9ca027..000000000 --- a/src/client/views/InkHandles.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React = require("react"); -import { action } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../fields/Doc"; -import { HandleLine, HandlePoint, InkData } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { listSpec } from "../../fields/Schema"; -import { Cast } from "../../fields/Types"; -import { emptyFunction, setupMoveUpEvents } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { UndoManager } from "../util/UndoManager"; -import { Colors } from "./global/globalEnums"; -import { InkStrokeProperties } from "./InkStrokeProperties"; - -export interface InkHandlesProps { - inkDoc: Doc; - screenCtrlPoints: InkData; - screenSpaceLineWidth: number; - ScreenToLocalTransform: () => Transform; -} - -@observer -export class InkTangentHandles extends React.Component { - /** - * Handles the movement of a selected handle point when the user clicks and drags. - * @param handleNum The index of the currently selected handle point. - */ - onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { - if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance.moveControl(0, 0, 1); - const controlUndo = UndoManager.StartBatch("DocDecs set radius"); - const screenScale = this.props.ScreenToLocalTransform().Scale; - const order = handleIndex % 4; - const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; - const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; - const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; - setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { - if (e.altKey) this.onBreakTangent(controlIndex); - InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); - return false; - }, () => controlUndo?.end(), emptyFunction - ); - } - } - - /** - * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and - * its matching (opposite) handle to a list of broken handle indices. - * @param handleNum The index of the currently selected handle point. - */ - @action - onBreakTangent = (controlIndex: number) => { - const doc = this.props.inkDoc; - if (doc) { - const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; - if (!brokenIndices?.includes(controlIndex) && - ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { - brokenIndices.push(controlIndex); - doc.brokenInkIndices = brokenIndices; - } - } - } - - render() { - const formatInstance = InkStrokeProperties.Instance; - if (!formatInstance) return (null); - - // Accessing the current ink's data and extracting all handle points and handle lines. - const data = this.props.screenCtrlPoints; - const handlePoints: HandlePoint[] = []; - const handleLines: HandleLine[] = []; - const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; - if (data.length >= 4) { - for (let i = 0; i <= data.length - 4; i += 4) { - handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); - handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); - } - // Adding first and last (single) handle lines. - if (closed) { - handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); - } - else { - handleLines.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 }); - handleLines.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 }); - } - for (let i = 2; i < data.length - 4; i += 4) { - handleLines.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 }); - } - } - const screenSpaceLineWidth = this.props.screenSpaceLineWidth; - - return ( - <> - {handlePoints.map((pts, i) => - - this.onHandleDown(e, pts.I)} - pointerEvents="all" - cursor="default" - display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> - )} - {handleLines.map((pts, i) => - - - - )} - - ); - } -} \ No newline at end of file diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx new file mode 100644 index 000000000..dbe9ca027 --- /dev/null +++ b/src/client/views/InkTangentHandles.tsx @@ -0,0 +1,130 @@ +import React = require("react"); +import { action } from "mobx"; +import { observer } from "mobx-react"; +import { Doc } from "../../fields/Doc"; +import { HandleLine, HandlePoint, InkData } from "../../fields/InkField"; +import { List } from "../../fields/List"; +import { listSpec } from "../../fields/Schema"; +import { Cast } from "../../fields/Types"; +import { emptyFunction, setupMoveUpEvents } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { UndoManager } from "../util/UndoManager"; +import { Colors } from "./global/globalEnums"; +import { InkStrokeProperties } from "./InkStrokeProperties"; + +export interface InkHandlesProps { + inkDoc: Doc; + screenCtrlPoints: InkData; + screenSpaceLineWidth: number; + ScreenToLocalTransform: () => Transform; +} + +@observer +export class InkTangentHandles extends React.Component { + /** + * Handles the movement of a selected handle point when the user clicks and drags. + * @param handleNum The index of the currently selected handle point. + */ + onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { + if (InkStrokeProperties.Instance) { + InkStrokeProperties.Instance.moveControl(0, 0, 1); + const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + const screenScale = this.props.ScreenToLocalTransform().Scale; + const order = handleIndex % 4; + const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; + const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; + const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; + setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { + if (e.altKey) this.onBreakTangent(controlIndex); + InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); + return false; + }, () => controlUndo?.end(), emptyFunction + ); + } + } + + /** + * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and + * its matching (opposite) handle to a list of broken handle indices. + * @param handleNum The index of the currently selected handle point. + */ + @action + onBreakTangent = (controlIndex: number) => { + const doc = this.props.inkDoc; + if (doc) { + const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; + const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; + if (!brokenIndices?.includes(controlIndex) && + ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { + brokenIndices.push(controlIndex); + doc.brokenInkIndices = brokenIndices; + } + } + } + + render() { + const formatInstance = InkStrokeProperties.Instance; + if (!formatInstance) return (null); + + // Accessing the current ink's data and extracting all handle points and handle lines. + const data = this.props.screenCtrlPoints; + const handlePoints: HandlePoint[] = []; + const handleLines: HandleLine[] = []; + const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; + if (data.length >= 4) { + for (let i = 0; i <= data.length - 4; i += 4) { + handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); + handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); + } + // Adding first and last (single) handle lines. + if (closed) { + handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); + } + else { + handleLines.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 }); + handleLines.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 }); + } + for (let i = 2; i < data.length - 4; i += 4) { + handleLines.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 }); + } + } + const screenSpaceLineWidth = this.props.screenSpaceLineWidth; + + return ( + <> + {handlePoints.map((pts, i) => + + this.onHandleDown(e, pts.I)} + pointerEvents="all" + cursor="default" + display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> + )} + {handleLines.map((pts, i) => + + + + )} + + ); + } +} \ No newline at end of file diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index c9e401ab4..b921014a3 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -15,10 +15,10 @@ import { InteractionUtils } from "../util/InteractionUtils"; import { ContextMenu } from "./ContextMenu"; import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; -import { InkControlPtHandles } from "./InkControls"; -import { InkTangentHandles } from "./InkHandles"; +import { InkControlPtHandles } from "./InkControlPtHandles"; import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; +import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; type InkDocument = makeInterface<[typeof documentSchema]>; -- cgit v1.2.3-70-g09d2 From ca018693224f5f7737f0546c4fa5f4d4210a3020 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 18:33:45 -0400 Subject: prevent crashes when ink points are not multiple of 4. deleting ink ctrl point that leaves one bezier segment no longer converts to a line. --- src/client/util/InteractionUtils.tsx | 2 +- src/client/views/InkStrokeProperties.ts | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 71d68262f..633876683 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -97,7 +97,7 @@ export namespace InteractionUtils { if (isNaN(scalex)) scalex = 1; if (isNaN(scaley)) scaley = 1; - const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `; + const toScr = (p: { X: number, Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `; const strpts = bezier ? pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, ""); diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index cfe6ec523..510c5f2dd 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -160,14 +160,6 @@ export class InkStrokeProperties { if (last) newPoints.splice(newPoints.length - 3, 2); this._currentPoint = -1; if (newPoints.length < 4) return undefined; - if (newPoints.length === 4) { - const newerPoints: { X: number, Y: number }[] = []; - newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y }); - newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y }); - newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y }); - newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y }); - return newerPoints; - } return newPoints; }, true) -- cgit v1.2.3-70-g09d2 From e5905220a84a62fff36965a3bf74a55b793ae31b Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 11:26:58 -0400 Subject: fixed filling of curves. added toggling of brokenindex with right-click. changed look of ink handles to be lighter weight and to reflect brokenindex sttate --- src/client/util/InteractionUtils.tsx | 2 +- src/client/views/InkControlPtHandles.tsx | 95 ++++++++++++++++++-------------- src/client/views/InkTangentHandles.tsx | 55 +++++++++--------- src/client/views/InkingStroke.tsx | 1 + 4 files changed, 82 insertions(+), 71 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 633876683..d43ebd692 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -99,7 +99,7 @@ export namespace InteractionUtils { const toScr = (p: { X: number, Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `; const strpts = bezier ? - pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : + pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : (i === 0 ? "M" + toScr(pt) : "") + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, ""); const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 898c3bf26..f80aca268 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -10,6 +10,7 @@ import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; import { InkStrokeProperties } from "./InkStrokeProperties"; +import { List } from "../../fields/List"; export interface InkControlProps { inkDoc: Doc; @@ -24,6 +25,8 @@ export interface InkControlProps { export class InkControlPtHandles extends React.Component { @observable private _overControl = -1; + + @observable controlUndo: UndoManager.Batch | undefined; /** * Handles the movement of a selected control point when the user clicks and drags. * @param controlIndex The index of the currently selected control point. @@ -31,34 +34,40 @@ export class InkControlPtHandles extends React.Component { @action onControlDown = (e: React.PointerEvent, controlIndex: number): void => { if (InkStrokeProperties.Instance) { - var controlUndo: UndoManager.Batch | undefined; const screenScale = this.props.ScreenToLocalTransform().Scale; const order = controlIndex % 4; const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); setupMoveUpEvents(this, e, - (e: PointerEvent, down: number[], delta: number[]) => { - if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); + action((e: PointerEvent, down: number[], delta: number[]) => { + if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); return false; - }, - () => { - controlUndo?.end(); + }), + action(() => { + this.controlUndo?.end(); + this.controlUndo = undefined; UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); - }, + }), action((e: PointerEvent, doubleTap: boolean | undefined) => { const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; - if (doubleTap) { - if (brokenIndices?.includes(equivIndex)) { - if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); - } - if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { - if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + if (doubleTap || e.button === 2) { + if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) { + if (brokenIndices) brokenIndices.push(controlIndex); + else this.props.inkDoc.brokenInkIndices = new List([controlIndex]); + } else { + if (brokenIndices?.includes(equivIndex)) { + if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); + InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); + } + if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { + if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); + InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + } } - controlUndo?.end(); + this.controlUndo?.end(); + this.controlUndo = undefined; } })); } @@ -92,9 +101,6 @@ export class InkControlPtHandles extends React.Component { } render() { - const formatInstance = InkStrokeProperties.Instance; - if (!formatInstance) return (null); - // Accessing the current ink's data and extracting all control points. const scrData = this.props.screenCtrlPoints; const sreenCtrlPoints: ControlPoint[] = []; @@ -111,41 +117,46 @@ export class InkControlPtHandles extends React.Component { } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; - const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; - const nearestScreenPt = this.props.nearestScreenPt(); + const TagType = (broken?: boolean) => broken ? "rect" : "circle"; + const hdl = (control: { X: number, Y: number, I: number }, scale: number, color: string) => { + const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"))?.includes(control.I); + const Tag = TagType(broken) as keyof JSX.IntrinsicElements; + return { + this.changeCurrPoint(control.I); + this.onControlDown(e, control.I); + }} + onMouseEnter={() => this.onEnterControl(control.I)} + onMouseLeave={this.onLeaveControl} + pointerEvents="all" + cursor="default" + />; + } return ( {!nearestScreenPt ? (null) : } - {sreenCtrlPoints.map((control, i) => - { - this.changeCurrPoint(control.I); - this.onControlDown(e, control.I); - }} - onMouseEnter={e => this.onEnterControl(i)} - onMouseLeave={this.onLeaveControl} - pointerEvents="all" - cursor="default" - /> - )} + {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))} ); } diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 759e48134..4f1a406c2 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -68,62 +68,61 @@ export class InkTangentHandles extends React.Component { // Accessing the current ink's data and extracting all handle points and handle lines. const data = this.props.screenCtrlPoints; - const handlePoints: HandlePoint[] = []; - const handleLines: HandleLine[] = []; + const tangentHandles: HandlePoint[] = []; + const tangentLines: HandleLine[] = []; const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; if (data.length >= 4) { for (let i = 0; i <= data.length - 4; i += 4) { - handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); - handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); + tangentHandles.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); + tangentHandles.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); } // Adding first and last (single) handle lines. if (closed) { - handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); + tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); } else { - handleLines.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 }); - handleLines.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 }); + tangentLines.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 }); + tangentLines.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 }); } for (let i = 2; i < data.length - 4; i += 4) { - handleLines.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 }); + tangentLines.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 }); } } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; return ( <> - {handlePoints.map((pts, i) => + {tangentHandles.map((pts, i) => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> )} - {handleLines.map((pts, i) => - - + {tangentLines.map((pts, i) => { + const tangentLine = (x1: number, y1: number, x2: number, y2: number) => - )} + strokeDasharray={"1 1"} + strokeWidth={1} + display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />; + return + {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)} + {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)} + ; + })} ); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 867677005..4cd7f7698 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -210,6 +210,7 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt = undefined)} onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined} -- cgit v1.2.3-70-g09d2 From c7121eb1756e04eafd6a74e665ef46ada655df5a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 22:13:29 -0400 Subject: cleaned up/fixed line arrowheads & dots. added linecap options for lines, added outline of lines using fillColor. turned off border in comic mode for ink. --- src/client/util/InteractionUtils.tsx | 33 +++++++++++++++++---------------- src/client/views/GestureOverlay.tsx | 9 ++++++--- src/client/views/InkingStroke.tsx | 32 +++++++++++++++++++++++--------- src/client/views/StyleProvider.tsx | 2 +- 4 files changed, 47 insertions(+), 29 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index d43ebd692..48fb968cf 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -90,7 +90,7 @@ export namespace InteractionUtils { } export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, - color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, + color: string, width: number, strokeWidth: number, lineJoin: string, lineCap: string, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) { const pts = shape ? makePolygon(shape, points) : points; @@ -104,20 +104,23 @@ export namespace InteractionUtils { const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; const defGuid = Utils.GenerateGuid(); - const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth))); const Tag = (bezier ? "path" : "polyline") as keyof JSX.IntrinsicElements; + const makerStrokeWidth = strokeWidth / 2; return ( {/* setting the svg fill sets the arrowStart fill */} {nodefs ? (null) : - {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : - - } - {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : - - } - {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : - - } + {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : + + + } + {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : + + + } + {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : + + + } } - {/* {addables} */} ); } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index a95660409..f28485e43 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -841,20 +841,23 @@ export class GestureOverlay extends Touchable { B.bottom = B.bottom + width / 2; B.width += width; B.height += width; + const fillColor = ActiveFillColor(); + const strokeColor = fillColor && fillColor !== "transparent" ? fillColor : ActiveInkColor(); return [ this.props.children, this._palette, [this._strokes.map((l, i) => { const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 };//this.getBounds(l, true); return - {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), width, width, - ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), + {InteractionUtils.CreatePolyline(l, b.left, b.top, strokeColor, width, width, "miter", "round", + ActiveInkBezierApprox(), "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} ; }), this._points.length <= 1 ? (null) : - {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "", ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} + {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "miter", "round", "", + "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} ] ]; } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 8b1b3ea32..952af3019 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -20,6 +20,7 @@ import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; +import { SnappingManager } from "../util/SnappingManager"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -143,6 +144,7 @@ export class InkingStroke extends ViewBoxBaseComponent this.props.ScreenToLocalTransform().inverse().transformPoint( @@ -150,13 +152,15 @@ export class InkingStroke extends ViewBoxBaseComponent ({ X: p[0], Y: p[1] })); const screenHdlPts = screenPts; - return
{InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, - StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"), - StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker), - StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} + StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier), + !closed ? "none" : StrCast(this.layoutDoc.fillColor, "none"), + startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} {!this._properties?._controlButton ? (null) : <> , props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; -- cgit v1.2.3-70-g09d2 From 4ccaecc185bed251ee74d1d9168cf3602c969680 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 10:20:02 -0400 Subject: fixed dragging lines with arrows to display arrowhead --- src/client/util/DragManager.ts | 16 ++++++++++++++++ src/client/util/InteractionUtils.tsx | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'src/client/util/InteractionUtils.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 421e4c6bb..6fde4b06e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -365,6 +365,22 @@ export namespace DragManager { const dragElements = eles.map(ele => { if (!ele.parentNode) dragDiv.appendChild(ele); const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement; + let children = Array.from(dragElement.children); + while (children.length) { // need to replace all the maker node reference ids with new unique ids. otherwise, the clone nodes will reference the original nodes which are all hidden during the drag + const next = children.pop(); + next && children.push(...Array.from(next.children)); + if (next) { + if (next.localName.startsWith("path") && (next.attributes as any)["marker-start"]) { + next.setAttribute("marker-start", (next.attributes as any)["marker-start"].value.replace("#", "#X")); + } + if (next.localName.startsWith("path") && (next.attributes as any)["marker-end"]) { + next.setAttribute("marker-end", (next.attributes as any)["marker-end"].value.replace("#", "#X")); + } + if (next.localName.startsWith("marker")) { + next.id = "X" + next.id; + } + } + } const rect = ele.getBoundingClientRect(); const scaleX = rect.width / ele.offsetWidth; const scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX; diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 48fb968cf..a32a8eecc 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -113,11 +113,11 @@ export namespace InteractionUtils { } - {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : + {arrowStart !== "arrow" ? (null) : } - {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : + {arrowEnd !== "arrow" ? (null) : } -- cgit v1.2.3-70-g09d2