From 4d3960bfff57d6e0840f4d09d5a5150a055ac788 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Sep 2021 10:21:46 -0400 Subject: turned pen on/off and ink handles on/off whenever the other is activated. --- src/client/views/InkStrokeProperties.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index d527b2a05..42190238e 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,13 +1,14 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, reaction } from "mobx"; import { Doc, DocListCast, Field, Opt } from "../../fields/Doc"; import { Document } from "../../fields/documentSchemas"; -import { InkField, InkData, PointData, ControlPoint } from "../../fields/InkField"; +import { InkField, InkData, PointData, ControlPoint, InkTool } 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 { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; @@ -18,6 +19,8 @@ export class InkStrokeProperties { constructor() { InkStrokeProperties.Instance = this; + reaction(() => this._controlButton, button => button && (CurrentUserUtils.SelectedTool = InkTool.None)); + reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false)); } @computed get selectedInk() { -- 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/views/InkStrokeProperties.ts') 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/views/InkStrokeProperties.ts') 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 e4a0d2c5752f541926d1fa6b36a284b9b3d69af9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 15:06:25 -0400 Subject: added the splice --- src/client/views/InkStrokeProperties.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 03946bb60..6b4cf9e50 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -78,8 +78,12 @@ export class InkStrokeProperties { @undoBatch @action 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 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); + controls.splice(i, 4, ...[...newsegs.left.points.map(p => ({ X: p.x, Y: p.y })), ...newsegs.right.points.map(p => ({ X: p.x, Y: p.y }))]); + return controls; + }); // this.applyFunction((doc: Doc, ink: InkData) => { // const newControl = { X: x, Y: y }; // const newPoints: InkData = []; -- cgit v1.2.3-70-g09d2 From 2de1f44d00bcaa5e95045d8b6b7a2c713a72e7b3 Mon Sep 17 00:00:00 2001 From: vkalev Date: Tue, 28 Sep 2021 15:24:42 -0400 Subject: updating broken indices --- src/client/views/InkStrokeProperties.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 6b4cf9e50..adb9a7aa1 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -82,6 +82,21 @@ export class InkStrokeProperties { 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); controls.splice(i, 4, ...[...newsegs.left.points.map(p => ({ X: p.x, Y: p.y })), ...newsegs.right.points.map(p => ({ X: p.x, Y: p.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 >= i) { + return control + 4; + } else { + return control; + } + })); + } + doc.brokenInkIndices = brokenIndices; + this._currentPoint = -1; + return controls; }); // this.applyFunction((doc: Doc, ink: InkData) => { @@ -124,19 +139,7 @@ export class InkStrokeProperties { // } // } - // 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; // }); } -- cgit v1.2.3-70-g09d2 From fd64e2e902fa28f31f81661c863d9f4e574d5d6b Mon Sep 17 00:00:00 2001 From: vkalev Date: Tue, 28 Sep 2021 15:26:38 -0400 Subject: removing previous addpoint code --- src/client/views/InkStrokeProperties.ts | 43 --------------------------------- 1 file changed, 43 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index adb9a7aa1..8821ef5e4 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -99,49 +99,6 @@ export class InkStrokeProperties { return controls; }); - // 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 }); - // } - - // } - - // return newPoints; - // }); } /** -- cgit v1.2.3-70-g09d2 From caf4e2e6d14617a4105bd220e8441461c66a2e75 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 15:34:13 -0400 Subject: fixed broken indices for ink curve splitting --- src/client/views/InkStrokeProperties.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 8821ef5e4..115e75c2a 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -87,7 +87,7 @@ export class InkStrokeProperties { // Updating the indices of the control points whose handle tangency has been broken. if (brokenIndices) { brokenIndices = new List(brokenIndices.map((control) => { - if (control >= i) { + if (control > i) { return control + 4; } else { return control; -- cgit v1.2.3-70-g09d2 From cad1445ea3fa81363c00908647ed2825c0f34c65 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 17:21:24 -0400 Subject: fixed adding point drag location. fixed broken indices on deleting. --- src/client/views/InkControls.tsx | 7 ++----- src/client/views/InkStrokeProperties.ts | 16 +++++----------- src/client/views/InkingStroke.tsx | 15 +++++++-------- 3 files changed, 14 insertions(+), 24 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx index 5fe0c0f8a..ee09273e3 100644 --- a/src/client/views/InkControls.tsx +++ b/src/client/views/InkControls.tsx @@ -90,7 +90,6 @@ export class InkControls extends React.Component { 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,12 +110,10 @@ export class InkControls extends React.Component { 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 */} {!nearestScreenPt ? (null) : ({ X: p.x, Y: p.y })), ...newsegs.right.points.map(p => ({ X: p.x, Y: p.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 > i) { - return control + 4; - } else { - return control; - } - })); - } - doc.brokenInkIndices = brokenIndices; + doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control > i ? control + 4 : control)); this._currentPoint = -1; return controls; @@ -160,11 +151,14 @@ export class InkStrokeProperties { deletePoints = () => this.applyFunction((doc: Doc, ink: InkData) => { const newPoints: { X: number, Y: number }[] = []; const toRemove = Math.floor(((this._currentPoint + 2) / 4)); + const last = this._currentPoint === ink.length - 1; for (let i = 0; i < ink.length; i++) { if (Math.floor((i + 2) / 4) !== toRemove && (toRemove !== 0 || i > 3)) { newPoints.push({ X: ink[i].X, Y: ink[i].Y }); } } + doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control >= toRemove * 4 ? control - 4 : control)); + if (last) newPoints.splice(newPoints.length - 3, 2); this._currentPoint = -1; if (newPoints.length < 4) return undefined; if (newPoints.length === 4) { diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 83fe04893..40bed0eca 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -141,9 +141,9 @@ export class InkingStroke extends ViewBoxBaseComponent { 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 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; @@ -151,16 +151,15 @@ export class InkingStroke extends ViewBoxBaseComponent ({ x: p.X, y: p.Y }))).project({ x: e.clientX, y: e.clientY }); 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]); + const dist = (point.x - e.clientX) * (point.x - e.clientX) + (point.y - e.clientY) * (point.y - e.clientY); if (dist < nearest) { nearest = dist; this._nearestT = point.t; this._nearestSeg = i; - this._nearestScrPt = { X: point.x, Y: point.y }; + this._nearestScrPt = { X: point.x - screenLeft, Y: point.y - screenTop }; } } } @@ -234,7 +233,7 @@ export class InkingStroke extends ViewBoxBaseComponent - this._nearestScrPt = undefined + console.log("") //this._nearestScrPt = undefined )} onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined} onPointerDown={this.onPointerDown} -- 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/views/InkStrokeProperties.ts') 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 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/views/InkStrokeProperties.ts') 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 faaa10bfeaf9c4a33a75884c3cf93eb3138464d1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 19:17:27 -0400 Subject: cleaned up inkstrokeproperties code a bit. --- src/client/views/InkStrokeProperties.ts | 65 ++++++++++++--------------------- 1 file changed, 23 insertions(+), 42 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 510c5f2dd..6a503cc91 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,6 +1,6 @@ import { Bezier } from "bezier-js"; import { action, computed, observable, reaction } from "mobx"; -import { Doc, Field, Opt } from "../../fields/Doc"; +import { Doc } from "../../fields/Doc"; import { Document } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -29,11 +29,6 @@ export class InkStrokeProperties { return inks.length ? inks : undefined; } - getField(key: string) { - return this.selectedInk?.reduce((p, i) => - (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt); - } - /** * Helper function that enables other functions to be applied to a particular ink instance. * @param func The inputted function. @@ -79,9 +74,10 @@ export class InkStrokeProperties { @action addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => { this.applyFunction((doc: Doc, ink: InkData) => { - 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); - controls.splice(i, 4, ...[...newsegs.left.points.map(p => ({ X: p.x, Y: p.y })), ...newsegs.right.points.map(p => ({ X: p.x, Y: p.y }))]); + const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]]; + const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t); + const splicepts = [...newsegs.left.points, ...newsegs.right.points]; + controls.splice(i, 4, ...splicepts.map(p => ({ X: p.x, Y: p.y }))); // Updating the indices of the control points whose handle tangency has been broken. doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control > i ? control + 4 : control)); @@ -101,8 +97,7 @@ export class InkStrokeProperties { const prevSize = end - start; const newSize = isLeft ? index - start : end - index; const handleVector = { X: control.X - handle.X, Y: control.Y - handle.Y }; - const scaledVector = { X: handleVector.X * (newSize / prevSize), Y: handleVector.Y * (newSize / prevSize) }; - return scaledVector; + return { X: handleVector.X * (newSize / prevSize), Y: handleVector.Y * (newSize / prevSize) }; } /** @@ -159,8 +154,7 @@ export class InkStrokeProperties { doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control >= toRemove * 4 ? control - 4 : control)); if (last) newPoints.splice(newPoints.length - 3, 2); this._currentPoint = -1; - if (newPoints.length < 4) return undefined; - return newPoints; + return newPoints.length < 4 ? undefined : newPoints; }, true) /** @@ -174,11 +168,11 @@ export class InkStrokeProperties { const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 }; - const newPoints: { X: number, Y: number }[] = []; - ink.map(i => ({ X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y })).forEach(i => { - const newX = Math.cos(angle) * i.X - Math.sin(angle) * i.Y; - const newY = Math.sin(angle) * i.X + Math.cos(angle) * i.Y; - newPoints.push({ X: newX + centerPoint.X, Y: newY + centerPoint.Y }); + const newPoints = ink.map(i => { + const pt = { X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y }; + const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y; + const newY = Math.sin(angle) * pt.X + Math.cos(angle) * pt.Y; + return { X: newX + centerPoint.X, Y: newY + centerPoint.Y }; }); doc.rotation = NumCast(doc.rotation) + angle; return newPoints; @@ -192,10 +186,10 @@ export class InkStrokeProperties { @action moveControl = (deltaX: number, deltaY: number, controlIndex: number) => 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++) { + + return ink.map((pt, i) => { const leftHandlePoint = order === 0 && i === controlIndex + 1; const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; if (controlIndex === i || @@ -207,12 +201,10 @@ export class InkStrokeProperties { (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) { - newPoints.push({ X: ink[i].X - deltaX / xScale, Y: ink[i].Y - deltaY / yScale }); - } else { - newPoints.push({ X: ink[i].X, Y: ink[i].Y }); + return ({ X: pt.X - deltaX / xScale, Y: pt.Y - deltaY / yScale }); } - } - return newPoints; + return pt; + }); }) /** @@ -224,18 +216,11 @@ export class InkStrokeProperties { this.applyFunction((doc: Doc, ink: InkData) => { const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); if (brokenIndices) { - const newBrokenIndices = new List; - brokenIndices.forEach(brokenIndex => { - if (brokenIndex !== controlIndex) { - newBrokenIndices.push(brokenIndex); - } - }); - doc.brokenInkIndices = newBrokenIndices; + doc.brokenInkIndices = new List(brokenIndices.filter(brokenIndex => brokenIndex !== controlIndex)); const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]]; const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI); const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint); - const newHandleB = this.rotatePoint(handleB, controlPoint, angleDifference); - ink[handleIndexB] = newHandleB; + ink[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference); return ink; } }); @@ -249,9 +234,7 @@ export class InkStrokeProperties { const rotatedTarget = { X: target.X - origin.X, Y: target.Y - origin.Y }; const newX = Math.cos(angle) * rotatedTarget.X - Math.sin(angle) * rotatedTarget.Y; const newY = Math.sin(angle) * rotatedTarget.X + Math.cos(angle) * rotatedTarget.Y; - rotatedTarget.X = newX + origin.X; - rotatedTarget.Y = newY + origin.Y; - return rotatedTarget; + return { X: newX + origin.X, Y: newY + origin.Y }; } /** @@ -265,8 +248,7 @@ export class InkStrokeProperties { // Normalizing the vectors. vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA }; vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB }; - const dotProduct = vectorB.X * vectorA.X + vectorB.Y * vectorA.Y; - return Math.acos(dotProduct); + return Math.acos(vectorB.X * vectorA.X + vectorB.Y * vectorA.Y); } /** @@ -292,7 +274,7 @@ export class InkStrokeProperties { 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 oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; ink[handleIndex] = newHandlePoint; @@ -300,8 +282,7 @@ export class InkStrokeProperties { // 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)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); - oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); - ink[oppositeHandleIndex] = oppositeHandlePoint; + ink[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); } return ink; }) -- cgit v1.2.3-70-g09d2 From aed57a2d6435007676409aeba562fc11d0c4a44d Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 01:38:15 -0400 Subject: a number of undo/redo fixes for ink (snapping to tangent, add points, dragging tangents). also tried to make storage of undo events more efficient when dragging ink controls (avoid saving hundreds of copies of the InkField) --- src/client/util/UndoManager.ts | 19 +++++++++++++++++++ src/client/views/DocumentDecorations.tsx | 5 ++++- src/client/views/InkControlPtHandles.tsx | 27 ++++++++++++++++----------- src/client/views/InkStrokeProperties.ts | 19 +++++++++++-------- src/client/views/InkTangentHandles.tsx | 24 ++++++++++++------------ src/client/views/InkingStroke.tsx | 8 ++++---- src/fields/util.ts | 13 ++++++------- 7 files changed, 72 insertions(+), 43 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 05fb9f378..536d90371 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -70,6 +70,7 @@ export namespace UndoManager { export interface UndoEvent { undo: () => void; redo: () => void; + prop: string; } type UndoBatch = UndoEvent[]; @@ -104,6 +105,23 @@ export namespace UndoManager { export function GetOpenBatches(): Without[] { return openBatches; } + export function FilterBatches(fieldTypes: string[]) { + var fieldCounts: { [key: string]: number } = {}; + const lastStack = UndoManager.undoStack.lastElement(); + if (lastStack) { + lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1)); + var fieldCount2: { [key: string]: number } = {}; + runInAction(() => + UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => { + if (fieldTypes.includes(ev.prop)) { + fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1; + if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true; + return false; + } + return true; + })); + } + } export function TraceOpenBatches() { console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join("\n\t")}\n`); } @@ -140,6 +158,7 @@ export namespace UndoManager { batchCounter--; // console.log("End " + batchCounter); if (batchCounter === 0 && currentBatch?.length) { + // console.log("------ended----") if (!cancel) { undoStack.push(currentBatch); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index bd61c9f60..bd9c3509b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -202,7 +202,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180)); return false; }, - () => this._rotateUndo?.end(), + () => { + this._rotateUndo?.end(); + UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); + }, emptyFunction); this._prevY = e.clientY; this._inkCenterPts = SelectionManager.Views() diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index eb0eebcdf..898c3bf26 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -24,7 +24,6 @@ export interface InkControlProps { 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. @@ -32,8 +31,7 @@ export class InkControlPtHandles extends React.Component { @action onControlDown = (e: React.PointerEvent, controlIndex: number): void => { if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance.moveControl(0, 0, 1); - const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + 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; @@ -41,17 +39,26 @@ export class InkControlPtHandles extends React.Component { 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"); InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); return false; }, - () => controlUndo?.end(), + () => { + controlUndo?.end(); + 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 && brokenIndices?.includes(equivIndex)) { - InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); - } - if (doubleTap && brokenIndices?.includes(controlIndex)) { - InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + 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); + } + controlUndo?.end(); } })); } @@ -62,8 +69,6 @@ export class InkControlPtHandles extends React.Component { */ @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. diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 6a503cc91..3770eb7c1 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -214,14 +214,16 @@ export class InkStrokeProperties { */ snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => { this.applyFunction((doc: Doc, ink: InkData) => { - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); - if (brokenIndices) { - doc.brokenInkIndices = new List(brokenIndices.filter(brokenIndex => brokenIndex !== controlIndex)); + const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []); + const ind = brokenIndices.findIndex(value => value === controlIndex); + if (ind !== -1) { + brokenIndices.splice(ind, 1); const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]]; const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI); const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint); - ink[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference); - return ink; + const inkCopy = ink.slice(); + inkCopy[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference); + return inkCopy; } }); } @@ -277,13 +279,14 @@ export class InkStrokeProperties { const oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; - ink[handleIndex] = newHandlePoint; + const inkCopy = ink.slice(); + inkCopy[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)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); - ink[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); + inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); } - return ink; + return inkCopy; }) } \ No newline at end of file diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index dbe9ca027..759e48134 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -27,18 +27,21 @@ export class InkTangentHandles extends React.Component { */ onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance.moveControl(0, 0, 1); - const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + var controlUndo: UndoManager.Batch | undefined; 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 (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent"); if (e.altKey) this.onBreakTangent(controlIndex); InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); return false; - }, () => controlUndo?.end(), emptyFunction + }, () => { + controlUndo?.end(); + UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); + }, emptyFunction ); } } @@ -50,15 +53,12 @@ export class InkTangentHandles extends React.Component { */ @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; - } + const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; + var brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); + if (!brokenIndices?.includes(controlIndex) && + ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { + if (brokenIndices) brokenIndices.push(controlIndex); + else this.props.inkDoc.brokenInkIndices = new List([controlIndex]); } } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index b921014a3..867677005 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -77,8 +77,8 @@ export class InkingStroke extends ViewBoxBaseComponent { + onPointerMove = (e: React.PointerEvent) => { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint( (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, @@ -212,7 +212,7 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt = undefined)} - onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined} + onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined} onPointerDown={this.onPointerDown} onClick={e => this._handledClick && e.stopPropagation()} onContextMenu={() => { diff --git a/src/fields/util.ts b/src/fields/util.ts index 3590c2dea..99dfc04d9 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -98,13 +98,12 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number } else { DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); } - !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({ - redo: () => receiver[prop] = value, - undo: () => { - // console.log("Undo: " + prop + " = " + curValue); // bcz: uncomment to log undo - receiver[prop] = curValue; - } - }); + !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && + UndoManager.AddEvent({ + redo: () => receiver[prop] = value, + undo: () => receiver[prop] = curValue, + prop: prop?.toString() + }); return true; } return false; -- cgit v1.2.3-70-g09d2 From 5f95911a504a47c867198fccc32a75bf22d26056 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 15:15:21 -0400 Subject: added snapping to close curve or to self-snap a vertex to its curve. fixed ink decorations from being clipped when zoomed. fixed crash with zero-length tangent --- src/client/views/InkControlPtHandles.tsx | 18 ++++++---- src/client/views/InkStrokeProperties.ts | 61 ++++++++++++++++++++++++++++++-- src/client/views/InkingStroke.tsx | 26 ++++---------- src/client/views/MainView.tsx | 2 +- 4 files changed, 78 insertions(+), 29 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index f80aca268..249a0fa64 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -42,10 +42,13 @@ export class InkControlPtHandles extends React.Component { setupMoveUpEvents(this, e, 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); + InkStrokeProperties.Instance?.moveControl(delta[0] * screenScale, delta[1] * screenScale, controlIndex); return false; }), action(() => { + if (this.controlUndo) { + InkStrokeProperties.Instance?.snapControl(this.props.inkDoc, controlIndex); + } this.controlUndo?.end(); this.controlUndo = undefined; UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); @@ -117,20 +120,21 @@ export class InkControlPtHandles extends React.Component { } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; + const closed = inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; 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 { + const newpts = ink.map((pt, i) => { const leftHandlePoint = order === 0 && i === controlIndex + 1; const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; if (controlIndex === i || @@ -201,12 +201,68 @@ export class InkStrokeProperties { (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) { - return ({ X: pt.X - deltaX / xScale, Y: pt.Y - deltaY / yScale }); + return ({ X: pt.X + deltaX / xScale, Y: pt.Y + deltaY / yScale }); } return pt; }); + return newpts; }) + + public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refPt: { X: number, Y: number }, excludeSegs?: number[]) { + var distance = Number.MAX_SAFE_INTEGER; + var nearestT = -1; + var nearestSeg = -1; + var nearestPt = { X: 0, Y: 0 }; + for (var i = 0; i < ctrlPoints.length - 3; i += 4) { + if (excludeSegs?.includes(i)) continue; + const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]]; + const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refPt.X, y: refPt.Y }); + if (point.t !== undefined) { + const dist = Math.sqrt((point.x - refPt.X) * (point.x - refPt.X) + (point.y - refPt.Y) * (point.y - refPt.Y)); + if (dist < distance) { + distance = dist; + nearestT = point.t; + nearestSeg = i; + nearestPt = { X: point.x, Y: point.y }; + } + } + } + return { distance, nearestT, nearestSeg, nearestPt }; + } + + /** + * Handles the movement/scaling of a control point. + */ + snapControl = (inkDoc: Doc, controlIndex: number) => { + const ink = Cast(inkDoc.data, InkField)?.inkData; + if (ink) { + const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; + + // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve) + const thisseg = Math.floor(controlIndex / 4) * 4; + const which = controlIndex % 4; + const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1; + const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1; + const refPt = ink[controlIndex]; + const { nearestPt } = InkStrokeProperties.nearestPtToStroke(ink, refPt, [thisseg, prevseg, nextseg]); + + // nearestPt is in inkDoc coordinates -- we need to compute the distance in screen coordinates. + // so we scale the X & Y distances by the internal ink scale factor and then transform the final distance by the ScreenToLocal.Scale of the inkDoc itself. + const oldXrange = (xs => ({ coord: NumCast(inkDoc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); + const oldYrange = (ys => ({ coord: NumCast(inkDoc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); + const ptsXscale = NumCast(inkDoc._width) / (oldXrange.max - oldXrange.min); + const ptsYscale = NumCast(inkDoc._height) / (oldYrange.max - oldYrange.min); + const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale + + (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); + + if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) { + return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex) + } + } + return false; + } + /** * Snaps a control point with broken tangency back to synced rotation. * @param handleIndexA The handle point that retains its current position. @@ -247,6 +303,7 @@ export class InkStrokeProperties { angleBetweenTwoVectors = (vectorA: PointData, vectorB: PointData) => { const magnitudeA = Math.sqrt(vectorA.X * vectorA.X + vectorA.Y * vectorA.Y); const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y); + if (magnitudeA === 0 || magnitudeB === 0) return 0; // Normalizing the vectors. vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA }; vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB }; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 4cd7f7698..8b1b3ea32 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -130,25 +130,14 @@ export class InkingStroke extends ViewBoxBaseComponent 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] })); - 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 = [screenPts[i], screenPts[i + 1], screenPts[i + 2], screenPts[i + 3]]; - const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: e.clientX, y: e.clientY }); - if (point.t) { - const dist = (point.x - e.clientX) * (point.x - e.clientX) + (point.y - e.clientY) * (point.y - e.clientY); - if (dist < nearest) { - nearest = dist; - this._nearestT = point.t; - this._nearestSeg = i; - this._nearestScrPt = { X: point.x, Y: point.y }; - } - } - } + const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); + + this._nearestT = nearestT; + this._nearestSeg = nearestSeg; + this._nearestScrPt = nearestPt; } + nearestScreenPt = () => this._nearestScrPt; componentUI = (boundsLeft: number, boundsTop: number) => { const inkDoc = this.props.Document; @@ -159,11 +148,10 @@ export class InkingStroke extends ViewBoxBaseComponent 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, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"), diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 3f7df705f..6d0d5eb39 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -618,7 +618,7 @@ export class MainView extends React.Component { - + {this.topbar} {LinkDescriptionPopup.descriptionPopup ? : null} {DocumentLinksButton.LinkEditorDocView ? : (null)} -- cgit v1.2.3-70-g09d2 From b9adc2fec10d4509cd96558bb832f7c02bc70b25 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 22:26:24 -0400 Subject: fixed breaking smoothness of closed curve at 0/last index. fixed highlighting closed curves. --- src/client/views/InkStrokeProperties.ts | 4 +++- src/client/views/InkingStroke.tsx | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index ac5cdfee2..d5033f44b 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -339,8 +339,10 @@ export class InkStrokeProperties { const inkCopy = ink.slice(); inkCopy[handleIndex] = newHandlePoint; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); + const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; // 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)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { + if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && + (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 952af3019..552318e23 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -159,8 +159,7 @@ export class InkingStroke extends ViewBoxBaseComponent {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, 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)} + "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} {!this._properties?._controlButton ? (null) : <> Date: Wed, 29 Sep 2021 22:51:21 -0400 Subject: made IsClosed a static function. fixed warnings and errors. --- src/client/util/UndoManager.ts | 4 ++-- src/client/views/InkControlPtHandles.tsx | 5 +++-- src/client/views/InkStrokeProperties.ts | 9 +++++---- src/client/views/InkTangentHandles.tsx | 7 ++++--- src/client/views/InkingStroke.tsx | 6 ++++-- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/fields/util.ts | 9 ++++++--- 8 files changed, 26 insertions(+), 18 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 536d90371..44e44d335 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -106,11 +106,11 @@ export namespace UndoManager { return openBatches; } export function FilterBatches(fieldTypes: string[]) { - var fieldCounts: { [key: string]: number } = {}; + const fieldCounts: { [key: string]: number } = {}; const lastStack = UndoManager.undoStack.lastElement(); if (lastStack) { lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1)); - var fieldCount2: { [key: string]: number } = {}; + const fieldCount2: { [key: string]: number } = {}; runInAction(() => UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => { if (fieldTypes.includes(ev.prop)) { diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 249a0fa64..8162f3fdc 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -11,6 +11,7 @@ import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { List } from "../../fields/List"; +import { InkingStroke } from "./InkingStroke"; export interface InkControlProps { inkDoc: Doc; @@ -120,7 +121,7 @@ export class InkControlPtHandles extends React.Component { } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; - const closed = inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; + const closed = InkingStroke.IsClosed(inkData); 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) => { @@ -147,7 +148,7 @@ export class InkControlPtHandles extends React.Component { pointerEvents="all" cursor="default" />; - } + }; return ( {!nearestScreenPt ? (null) : this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const order = controlIndex % 4; - const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; + const closed = InkingStroke.IsClosed(ink); const newpts = ink.map((pt, i) => { const leftHandlePoint = order === 0 && i === controlIndex + 1; @@ -237,7 +238,7 @@ export class InkStrokeProperties { snapControl = (inkDoc: Doc, controlIndex: number) => { const ink = Cast(inkDoc.data, InkField)?.inkData; if (ink) { - const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; + const closed = InkingStroke.IsClosed(ink); // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve) const thisseg = Math.floor(controlIndex / 4) * 4; @@ -257,7 +258,7 @@ export class InkStrokeProperties { (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) { - return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex) + return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); } } return false; @@ -331,7 +332,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 closed = InkingStroke.IsClosed(ink); const oldHandlePoint = ink[handleIndex]; const oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 4f1a406c2..8f7bef936 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -11,6 +11,7 @@ import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; import { InkStrokeProperties } from "./InkStrokeProperties"; +import { InkingStroke } from "./InkingStroke"; export interface InkHandlesProps { inkDoc: Doc; @@ -53,8 +54,8 @@ export class InkTangentHandles extends React.Component { */ @action onBreakTangent = (controlIndex: number) => { - const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; - var brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); + const closed = InkingStroke.IsClosed(this.props.screenCtrlPoints); + const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); if (!brokenIndices?.includes(controlIndex) && ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { if (brokenIndices) brokenIndices.push(controlIndex); @@ -70,7 +71,7 @@ export class InkTangentHandles extends React.Component { const data = this.props.screenCtrlPoints; const tangentHandles: HandlePoint[] = []; const tangentLines: HandleLine[] = []; - const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; + const closed = InkingStroke.IsClosed(data); if (data.length >= 4) { for (let i = 0; i <= data.length - 4; i += 4) { tangentHandles.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 552318e23..42a09fa52 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -29,6 +29,9 @@ const InkDocument = makeInterface(documentSchema); export class InkingStroke extends ViewBoxBaseComponent(InkDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } static readonly MaskDim = 50000; + public static IsClosed(inkData: InkData) { + return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; + } @observable private _properties?: InkStrokeProperties; _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated _selDisposer: IReactionDisposer | undefined; @@ -144,7 +147,6 @@ export class InkingStroke extends ViewBoxBaseComponent this.props.ScreenToLocalTransform().inverse().transformPoint( @@ -184,7 +186,7 @@ export class InkingStroke extends ViewBoxBaseComponent void; Pause?: () => void; setFocus?: () => void; - componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element; + componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; fieldKey?: string; annotationKey?: string; getTitle?: () => string; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 7afa54d5f..aa53f751d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -960,7 +960,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp GoogleApiClientUtils.Docs.write({ reference, content, mode }); } }; - UndoManager.AddEvent({ undo, redo }); + UndoManager.AddEvent({ undo, redo, prop: "" }); redo(); }); } diff --git a/src/fields/util.ts b/src/fields/util.ts index 99dfc04d9..c708affe3 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -405,7 +405,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any ind !== -1 && receiver[prop].splice(ind, 1); }); lastValue = ObjectField.MakeCopy(receiver[prop]); - }) + }), + prop: "" } : diff?.op === "$remFromSet" ? { @@ -423,7 +424,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item); }); lastValue = ObjectField.MakeCopy(receiver[prop]); - } + }, + prop: "" } : { redo: () => { @@ -434,7 +436,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo receiver[prop] = ObjectField.MakeCopy(prevValue as List); lastValue = ObjectField.MakeCopy(receiver[prop]); - } + }, + prop: "" }); } target[Update](op); -- cgit v1.2.3-70-g09d2 From 4e4a1ec7bb6c479e8fd1b0a7bfe73e2447bafc74 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 13:11:26 -0400 Subject: fixed creating/drawing straight horizontal/vertical lines. fixed showing proper context menu for ink. --- src/client/views/DocumentButtonBar.tsx | 3 ++- src/client/views/InkControlPtHandles.tsx | 2 +- src/client/views/InkStrokeProperties.ts | 12 ++++++------ src/client/views/InkTangentHandles.tsx | 2 +- src/client/views/InkingStroke.tsx | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 8edd7e5bd..aa9318310 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -28,6 +28,7 @@ import { PresBox } from './nodes/trails/PresBox'; import { undoBatch } from '../util/UndoManager'; import { CollectionViewType } from './collections/CollectionView'; import { Colors } from './global/globalEnums'; +import { DashFieldView } from './nodes/formattedText/DashFieldView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -338,7 +339,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV while (child.children.length) { const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string"); if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; - if (next?.className?.toString().includes("dashFieldView")) break; + if (next?.className?.toString().includes(DashFieldView.name)) break; if (next) child = next; else break; } diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 8162f3fdc..8eb74381a 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -43,7 +43,7 @@ export class InkControlPtHandles extends React.Component { setupMoveUpEvents(this, e, 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); + InkStrokeProperties.Instance?.moveControlPtHandle(delta[0] * screenScale, delta[1] * screenScale, controlIndex); return false; }), action(() => { diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 72912ff20..00201fa56 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -45,8 +45,8 @@ export class InkStrokeProperties { if (ink) { const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const ptsXscale = NumCast(doc._width) / (oldXrange.max - oldXrange.min); - const ptsYscale = NumCast(doc._height) / (oldYrange.max - oldYrange.min); + const ptsXscale = NumCast(doc._width) / ((oldXrange.max - oldXrange.min) || 1); + const ptsYscale = NumCast(doc._height) / ((oldYrange.max - oldYrange.min) || 1); const newPoints = func(doc, ink, ptsXscale, ptsYscale); if (newPoints) { const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X)); @@ -185,7 +185,7 @@ export class InkStrokeProperties { */ @undoBatch @action - moveControl = (deltaX: number, deltaY: number, controlIndex: number) => + moveControlPtHandle = (deltaX: number, deltaY: number, controlIndex: number) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const order = controlIndex % 4; const closed = InkingStroke.IsClosed(ink); @@ -258,7 +258,7 @@ export class InkStrokeProperties { (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) { - return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); + return this.moveControlPtHandle((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); } } return false; @@ -278,7 +278,7 @@ export class InkStrokeProperties { const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]]; const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI); const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint); - const inkCopy = ink.slice(); + const inkCopy = ink.slice(); // have to make a new copy of the array to keep from corrupting undo/redo. without slicing, the same array will be stored in each undo step meaning earlier undo steps will be inadvertently updated to store the latest value. inkCopy[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference); return inkCopy; } @@ -330,7 +330,7 @@ export class InkStrokeProperties { */ @undoBatch @action - moveHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => + moveTangentHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const closed = InkingStroke.IsClosed(ink); const oldHandlePoint = ink[handleIndex]; diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 9e37e005b..df5bebf31 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -37,7 +37,7 @@ export class InkTangentHandles extends React.Component { setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent"); if (e.altKey) this.onBreakTangent(controlIndex); - InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); + InkStrokeProperties.Instance?.moveTangentHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); return false; }, () => { controlUndo?.end(); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 42a09fa52..5438350d8 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -123,8 +123,8 @@ export class InkingStroke extends ViewBoxBaseComponent Date: Thu, 30 Sep 2021 13:58:59 -0400 Subject: fixed jitter of position when moving ink points around --- src/client/views/InkStrokeProperties.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 00201fa56..68169f246 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -45,14 +45,14 @@ export class InkStrokeProperties { if (ink) { const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const ptsXscale = NumCast(doc._width) / ((oldXrange.max - oldXrange.min) || 1); - const ptsYscale = NumCast(doc._height) / ((oldYrange.max - oldYrange.min) || 1); + const ptsXscale = ((NumCast(doc._width) - NumCast(doc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; + const ptsYscale = ((NumCast(doc._height) - NumCast(doc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; const newPoints = func(doc, ink, ptsXscale, ptsYscale); if (newPoints) { const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X)); const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y)); - doc._width = (newXrange.max - newXrange.min) * ptsXscale; - doc._height = (newYrange.max - newYrange.min) * ptsYscale; + doc._width = (newXrange.max - newXrange.min) * ptsXscale + NumCast(doc.strokeWidth); + doc._height = (newYrange.max - newYrange.min) * ptsYscale + NumCast(doc.strokeWidth); doc.x = (oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale); doc.y = (oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale); Doc.GetProto(doc).data = new InkField(newPoints); -- cgit v1.2.3-70-g09d2 From c457efd3d57359c285bd00a65c9fdce7a27e27f6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 16:06:27 -0400 Subject: fixed ink rotation --- src/client/views/InkStrokeProperties.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 68169f246..ef4976b77 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -171,8 +171,8 @@ export class InkStrokeProperties { const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 }; const newPoints = ink.map(i => { const pt = { X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y }; - const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y; - const newY = Math.sin(angle) * pt.X + Math.cos(angle) * pt.Y; + const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale; + const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y; return { X: newX + centerPoint.X, Y: newY + centerPoint.Y }; }); doc.rotation = NumCast(doc.rotation) + angle; -- cgit v1.2.3-70-g09d2 From c654a67db0da26abbf0ab920e8925d872443d22a Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 1 Oct 2021 14:58:16 -0400 Subject: added stroke highlighting when following links. turned off display of centerline stroke when stroke is just selected but control point editing is not active. fixed snapping ink to make closed curves. --- src/client/views/InkStrokeProperties.ts | 4 ++-- src/client/views/InkingStroke.tsx | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index ef4976b77..ee30caa3d 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -252,8 +252,8 @@ export class InkStrokeProperties { // so we scale the X & Y distances by the internal ink scale factor and then transform the final distance by the ScreenToLocal.Scale of the inkDoc itself. const oldXrange = (xs => ({ coord: NumCast(inkDoc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(inkDoc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const ptsXscale = NumCast(inkDoc._width) / (oldXrange.max - oldXrange.min); - const ptsYscale = NumCast(inkDoc._height) / (oldYrange.max - oldYrange.min); + const ptsXscale = ((NumCast(inkDoc._width) - NumCast(inkDoc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; + const ptsYscale = ((NumCast(inkDoc._height) - NumCast(inkDoc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale + (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index ff90563e1..d05a4a6e4 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -6,7 +6,7 @@ import { Doc } from "../../fields/Doc"; import { documentSchema } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool } from "../../fields/InkField"; import { makeInterface } from "../../fields/Schema"; -import { Cast, NumCast, StrCast } from "../../fields/Types"; +import { Cast, NumCast, StrCast, BoolCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; @@ -160,11 +160,11 @@ export class InkingStroke extends ViewBoxBaseComponent - {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, - StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier), - "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} {!this._properties?._controlButton ? (null) : <> + {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, + StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier), + "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}