From 0e3892e63758accd7dae274072ad7893934c3624 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 18 Oct 2022 15:07:31 -0400 Subject: cleaned up gestureutils a bit. added recognize gestures option, but it probably shouldn't be used until the geometry gesture recognition descriptions are improved. --- src/pen-gestures/GestureUtils.ts | 48 ++++---- src/pen-gestures/ndollar.ts | 230 ++++++++++++++++++++++----------------- 2 files changed, 154 insertions(+), 124 deletions(-) (limited to 'src/pen-gestures') diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 65f2bf80c..2d3b1fdb8 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -1,40 +1,34 @@ -import { Rect } from "react-measure"; -import { PointData } from "../fields/InkField"; -import { NDollarRecognizer } from "./ndollar"; +import { Rect } from 'react-measure'; +import { PointData } from '../fields/InkField'; +import { NDollarRecognizer } from './ndollar'; export namespace GestureUtils { export class GestureEvent { - constructor( - readonly gesture: Gestures, - readonly points: PointData[], - readonly bounds: Rect, - readonly text?: any - ) { } + constructor(readonly gesture: Gestures, readonly points: PointData[], readonly bounds: Rect, readonly text?: any) {} } - export interface GestureEventDisposer { (): void; } + export interface GestureEventDisposer { + (): void; + } - export function MakeGestureTarget( - element: HTMLElement, - func: (e: Event, ge: GestureEvent) => void - ): GestureEventDisposer { + export function MakeGestureTarget(element: HTMLElement, func: (e: Event, ge: GestureEvent) => void): GestureEventDisposer { const handler = (e: Event) => func(e, (e as CustomEvent).detail); - element.addEventListener("dashOnGesture", handler); - return () => element.removeEventListener("dashOnGesture", handler); + element.addEventListener('dashOnGesture', handler); + return () => element.removeEventListener('dashOnGesture', handler); } export enum Gestures { - Box = "box", - Line = "line", - StartBracket = "startbracket", - EndBracket = "endbracket", - Stroke = "stroke", - Scribble = "scribble", - Text = "text", - Triangle = "triangle", - Circle = "circle", - Rectangle = "rectangle", + Line = 'line', + StartBracket = 'startbracket', + EndBracket = 'endbracket', + Stroke = 'stroke', + Scribble = 'scribble', + Text = 'text', + Triangle = 'triangle', + Circle = 'circle', + Rectangle = 'rectangle', + Arrow = 'arrow', } export const GestureRecognizer = new NDollarRecognizer(false); -} \ No newline at end of file +} diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index ecd8df3e7..b10a9da17 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -1,4 +1,4 @@ -import { GestureUtils } from "./GestureUtils"; +import { GestureUtils } from './GestureUtils'; /** * The $N Multistroke Recognizer (JavaScript version) @@ -69,20 +69,20 @@ import { GestureUtils } from "./GestureUtils"; * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. -**/ + **/ // // Point class // export class Point { - constructor(public X: number, public Y: number) { } + constructor(public X: number, public Y: number) {} } // // Rectangle class // export class Rectangle { - constructor(public X: number, public Y: number, public Width: number, public Height: number) { } + constructor(public X: number, public Y: number, public Width: number, public Height: number) {} } // @@ -113,8 +113,11 @@ export class Multistroke { public NumStrokes: number; public Unistrokes: Unistroke[]; - constructor(public Name: string, useBoundedRotationInvariance: boolean, strokes: any[]) // constructor - { + constructor( + public Name: string, + useBoundedRotationInvariance: boolean, + strokes: any[] // constructor + ) { this.NumStrokes = strokes.length; // number of individual strokes const order = new Array(strokes.length); // array of integer indices @@ -136,13 +139,13 @@ export class Multistroke { // Result class // export class Result { - constructor(public Name: string, public Score: any, public Time: any) { } + constructor(public Name: string, public Score: any, public Time: any) {} } // // NDollarRecognizer constants // -const NumMultistrokes = 7; +const NumMultistrokes = 6; const NumPoints = 96; const SquareSize = 250.0; const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35) @@ -152,7 +155,7 @@ const HalfDiagonal = 0.5 * Diagonal; const AngleRange = Deg2Rad(45.0); const AnglePrecision = Deg2Rad(2.0); const Phi = 0.5 * (-1.0 + Math.sqrt(5.0)); // Golden Ratio -const StartAngleIndex = (NumPoints / 8); // eighth of gesture length +const StartAngleIndex = NumPoints / 8; // eighth of gesture length const AngleSimilarityThreshold = Deg2Rad(30.0); // @@ -164,48 +167,68 @@ export class NDollarRecognizer { /** * @IMPORTANT - IF YOU'RE ADDING A NEW GESTURE, BE SURE TO INCREMENT THE NumMultiStrokes CONST RIGHT ABOVE THIS CLASS. */ - constructor(useBoundedRotationInvariance: boolean) // constructor - { + constructor( + useBoundedRotationInvariance: boolean // constructor + ) { // // one predefined multistroke for each multistroke type // this.Multistrokes = new Array(NumMultistrokes); - this.Multistrokes[0] = new Multistroke(GestureUtils.Gestures.Box, useBoundedRotationInvariance, new Array( + this.Multistrokes[0] = new Multistroke( + GestureUtils.Gestures.Rectangle, + useBoundedRotationInvariance, new Array( - new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), - new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), - new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), - new Point(106, 146), //new Point(80, 150), new Point(50, 146), - new Point(30, 143)) - )); - this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array( - new Array(new Point(12, 347), new Point(119, 347)) - )); - this.Multistrokes[2] = new Multistroke(GestureUtils.Gestures.StartBracket, useBoundedRotationInvariance, new Array( - // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150)) - new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150)) - )); - this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array( - // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152)) - // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150)) - new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100)) - )); - this.Multistrokes[4] = new Multistroke(GestureUtils.Gestures.Triangle, useBoundedRotationInvariance, new Array( - new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)) - )); - this.Multistrokes[5] = new Multistroke(GestureUtils.Gestures.Circle, useBoundedRotationInvariance, new Array( - new Array(new Point(200, 250), new Point(240, 230), new Point(248, 210), new Point(248, 190), new Point(240, 170), new Point(200, 150), new Point(160, 170), new Point(151, 190), new Point(151, 210), new Point(160, 230), new Point(201, 250)) - )); - this.Multistrokes[6] = new Multistroke(GestureUtils.Gestures.Rectangle, useBoundedRotationInvariance, new Array( + new Array( + new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), + new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), + new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), + new Point(106, 146), //new Point(80, 150), new Point(50, 146), + new Point(30, 143) + ) + ) + ); + this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))); + this.Multistrokes[2] = new Multistroke( + GestureUtils.Gestures.StartBracket, + useBoundedRotationInvariance, new Array( - new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), - new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), - new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), - new Point(106, 146), //new Point(80, 150), new Point(50, 146), - new Point(30, 143), - new Point(29, 220)) - )); - + // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150)) + new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150)) + ) + ); + this.Multistrokes[3] = new Multistroke( + GestureUtils.Gestures.EndBracket, + useBoundedRotationInvariance, + new Array( + // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152)) + // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150)) + new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100)) + ) + ); + this.Multistrokes[4] = new Multistroke( + GestureUtils.Gestures.Triangle, // equilateral + useBoundedRotationInvariance, + new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) + ); + this.Multistrokes[5] = new Multistroke( + GestureUtils.Gestures.Circle, + useBoundedRotationInvariance, + new Array( + new Array( + new Point(200, 250), + new Point(240, 230), + new Point(248, 210), + new Point(248, 190), + new Point(240, 170), + new Point(200, 150), + new Point(160, 170), + new Point(151, 190), + new Point(151, 210), + new Point(160, 230), + new Point(201, 250) + ) + ) + ); // // PREDEFINED STROKES // @@ -281,23 +304,25 @@ export class NDollarRecognizer { Recognize = (strokes: any[], useBoundedRotationInvariance: boolean = false, requireSameNoOfStrokes: boolean = false, useProtractor: boolean = true) => { const t0 = Date.now(); const points = CombineStrokes(strokes); // make one connected unistroke from the given strokes - const candidate = new Unistroke("", useBoundedRotationInvariance, points); + const candidate = new Unistroke('', useBoundedRotationInvariance, points); var u = -1; var b = +Infinity; - for (var i = 0; i < this.Multistrokes.length; i++) // for each multistroke template - { - if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) // optional -- only attempt match when same # of component strokes - { - for (const unistroke of this.Multistrokes[i].Unistrokes) // for each unistroke within this multistroke - { - if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction - { + for ( + var i = 0; + i < this.Multistrokes.length; + i++ // for each multistroke template + ) { + if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) { + // optional -- only attempt match when same # of component strokes + for (const unistroke of this.Multistrokes[i].Unistrokes) { + // for each unistroke within this multistroke + if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) { + // strokes start in the same direction var d; if (useProtractor) { d = OptimalCosineDistance(unistroke.Vector, candidate.Vector); // Protractor - } - else { + } else { d = DistanceAtBestAngle(candidate.Points, unistroke, -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N) } if (d < b) { @@ -309,8 +334,8 @@ export class NDollarRecognizer { } } const t1 = Date.now(); - return (u === -1) ? null : new Result(this.Multistrokes[u].Name, useProtractor ? (1.0 - b) : (1.0 - b / HalfDiagonal), t1 - t0); - } + return u === -1 ? null : new Result(this.Multistrokes[u].Name, useProtractor ? 1.0 - b : 1.0 - b / HalfDiagonal, t1 - t0); + }; AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => { this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes); @@ -321,15 +346,14 @@ export class NDollarRecognizer { } } return num; - } + }; DeleteUserGestures = () => { this.Multistrokes.length = NumMultistrokes; // clear any beyond the original set return NumMultistrokes; - } + }; } - // // Private helper functions from here on down // @@ -339,11 +363,13 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) { } else { for (var i = 0; i < n; i++) { HeapPermute(n - 1, order, orders); - if (n % 2 === 1) { // swap 0, n-1 + if (n % 2 === 1) { + // swap 0, n-1 const tmp = order[0]; order[0] = order[n - 1]; order[n - 1] = tmp; - } else { // swap i, n-1 + } else { + // swap i, n-1 const tmp = order[i]; order[i] = order[n - 1]; order[n - 1] = tmp; @@ -355,15 +381,18 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) { function MakeUnistrokes(strokes: any, orders: any) { const unistrokes = new Array(); // array of point arrays for (const order of orders) { - for (var b = 0; b < Math.pow(2, order.length); b++) // use b's bits for directions - { + for ( + var b = 0; + b < Math.pow(2, order.length); + b++ // use b's bits for directions + ) { const unistroke = new Array(); // array of points for (var i = 0; i < order.length; i++) { var pts; - if (((b >> i) & 1) === 1) {// is b's bit at index i on? + if (((b >> i) & 1) === 1) { + // is b's bit at index i on? pts = strokes[order[i]].slice().reverse(); // copy and reverse - } - else { + } else { pts = strokes[order[i]].slice(); // copy } for (const point of pts) { @@ -391,17 +420,17 @@ function Resample(points: any, n: any) { const newpoints = new Array(points[0]); for (var i = 1; i < points.length; i++) { const d = Distance(points[i - 1], points[i]); - if ((D + d) >= I) { + if (D + d >= I) { const qx = points[i - 1].X + ((I - D) / d) * (points[i].X - points[i - 1].X); const qy = points[i - 1].Y + ((I - D) / d) * (points[i].Y - points[i - 1].Y); const q = new Point(qx, qy); newpoints[newpoints.length] = q; // append new point 'q' points.splice(i, 0, q); // insert 'q' at position i in points s.t. 'q' will be the next i D = 0.0; - } - else D += d; + } else D += d; } - if (newpoints.length === n - 1) {// sometimes we fall a rounding-error short of adding the last point, so add it if so + if (newpoints.length === n - 1) { + // sometimes we fall a rounding-error short of adding the last point, so add it if so newpoints[newpoints.length] = new Point(points[points.length - 1].X, points[points.length - 1].Y); } return newpoints; @@ -410,8 +439,8 @@ function IndicativeAngle(points: any) { const c = Centroid(points); return Math.atan2(c.Y - points[0].Y, c.X - points[0].X); } -function RotateBy(points: any, radians: any) // rotates points around centroid -{ +function RotateBy(points: any, radians: any) { + // rotates points around centroid const c = Centroid(points); const cos = Math.cos(radians); const sin = Math.sin(radians); @@ -423,8 +452,8 @@ function RotateBy(points: any, radians: any) // rotates points around centroid } return newpoints; } -function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniformly for 1D, non-uniformly for 2D -{ +function ScaleDimTo(points: any, size: any, ratio1D: any) { + // scales bbox uniformly for 1D, non-uniformly for 2D const B = BoundingBox(points); const uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test const newpoints = new Array(); @@ -435,8 +464,8 @@ function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniform } return newpoints; } -function TranslateTo(points: any, pt: any) // translates points' centroid -{ +function TranslateTo(points: any, pt: any) { + // translates points' centroid const c = Centroid(points); const newpoints = new Array(); for (const { X, Y } of points) { @@ -446,8 +475,8 @@ function TranslateTo(points: any, pt: any) // translates points' centroid } return newpoints; } -function Vectorize(points: any, useBoundedRotationInvariance: any) // for Protractor -{ +function Vectorize(points: any, useBoundedRotationInvariance: any) { + // for Protractor var cos = 1.0; var sin = 0.0; if (useBoundedRotationInvariance) { @@ -471,8 +500,8 @@ function Vectorize(points: any, useBoundedRotationInvariance: any) // for Protra } return vector; } -function OptimalCosineDistance(v1: any, v2: any) // for Protractor -{ +function OptimalCosineDistance(v1: any, v2: any) { + // for Protractor var a = 0.0; var b = 0.0; for (var i = 0; i < v1.length; i += 2) { @@ -509,7 +538,8 @@ function DistanceAtAngle(points: any, T: any, radians: any) { return PathDistance(newpoints, T.Points); } function Centroid(points: any) { - var x = 0.0, y = 0.0; + var x = 0.0, + y = 0.0; for (const point of points) { x += point.X; y += point.Y; @@ -519,7 +549,10 @@ function Centroid(points: any) { return new Point(x, y); } function BoundingBox(points: any) { - var minX = +Infinity, maxX = -Infinity, minY = +Infinity, maxY = -Infinity; + var minX = +Infinity, + maxX = -Infinity, + minY = +Infinity, + maxY = -Infinity; for (const { X, Y } of points) { minX = Math.min(minX, X); minY = Math.min(minY, Y); @@ -528,38 +561,41 @@ function BoundingBox(points: any) { } return new Rectangle(minX, minY, maxX - minX, maxY - minY); } -function PathDistance(pts1: any, pts2: any) // average distance between corresponding points in two paths -{ +function PathDistance(pts1: any, pts2: any) { + // average distance between corresponding points in two paths var d = 0.0; - for (var i = 0; i < pts1.length; i++) {// assumes pts1.length == pts2.length + for (var i = 0; i < pts1.length; i++) { + // assumes pts1.length == pts2.length d += Distance(pts1[i], pts2[i]); } return d / pts1.length; } -function PathLength(points: any) // length traversed by a point path -{ +function PathLength(points: any) { + // length traversed by a point path var d = 0.0; for (var i = 1; i < points.length; i++) { d += Distance(points[i - 1], points[i]); } return d; } -function Distance(p1: any, p2: any) // distance between two points -{ +function Distance(p1: any, p2: any) { + // distance between two points const dx = p2.X - p1.X; const dy = p2.Y - p1.Y; return Math.sqrt(dx * dx + dy * dy); } -function CalcStartUnitVector(points: any, index: any) // start angle from points[0] to points[index] normalized as a unit vector -{ +function CalcStartUnitVector(points: any, index: any) { + // start angle from points[0] to points[index] normalized as a unit vector const v = new Point(points[index]?.X - points[0]?.X, points[index]?.Y - points[0]?.Y); const len = Math.sqrt(v.X * v.X + v.Y * v.Y); return new Point(v.X / len, v.Y / len); } -function AngleBetweenUnitVectors(v1: any, v2: any) // gives acute angle between unit vectors from (0,0) to v1, and (0,0) to v2 -{ - const n = (v1.X * v2.X + v1.Y * v2.Y); +function AngleBetweenUnitVectors(v1: any, v2: any) { + // gives acute angle between unit vectors from (0,0) to v1, and (0,0) to v2 + const n = v1.X * v2.X + v1.Y * v2.Y; const c = Math.max(-1.0, Math.min(1.0, n)); // ensure [-1,+1] return Math.acos(c); // arc cosine of the vector dot product } -function Deg2Rad(d: any) { return (d * Math.PI / 180.0); } \ No newline at end of file +function Deg2Rad(d: any) { + return (d * Math.PI) / 180.0; +} -- cgit v1.2.3-70-g09d2 From a81ab2e6f75681bc4fb3a7b49d2056144e396b94 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 19 Oct 2022 12:38:19 -0400 Subject: fixed erasing straight line stroke segments caused by intersections with other strokes. cleaned up more with gestures. changed docView lock icon to be part of docDecorations. --- src/client/views/DocumentDecorations.scss | 28 ++++++- src/client/views/DocumentDecorations.tsx | 41 +++++++++- src/client/views/GestureOverlay.tsx | 21 +++--- src/client/views/StyleProvider.scss | 18 ++--- src/client/views/StyleProvider.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 58 ++++++-------- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 5 +- src/client/views/nodes/button/FontIconBox.tsx | 20 ++--- src/pen-gestures/GestureUtils.ts | 2 - src/pen-gestures/ndollar.ts | 88 +++++++++------------- 11 files changed, 148 insertions(+), 137 deletions(-) (limited to 'src/pen-gestures') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 5c8dd36cc..4e0b061a6 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -1,7 +1,7 @@ @import 'global/globalCssVariables'; $linkGap: 3px; -$headerHeight: 20px; +$headerHeight: 25px; $resizeHandler: 8px; .documentDecorations-Dark, @@ -54,6 +54,8 @@ $resizeHandler: 8px; grid-column-end: 4; flex-direction: row; gap: 2px; + pointer-events: all; + cursor: move; .documentDecorations-openButton { display: flex; @@ -236,8 +238,8 @@ $resizeHandler: 8px; .documentDecorations-borderRadius { position: absolute; border-radius: 100%; - left: 3px; - top: 23px; + left: 7px; + top: 27px; background: $medium-gray; height: 10; width: 10; @@ -245,6 +247,21 @@ $resizeHandler: 8px; cursor: nwse-resize; } + .documentDecorations-lock { + position: absolute; + background: black; + right: 11; + top: 30px; + color: gray; + height: 14; + width: 14; + pointer-events: all; + margin: auto; + display: flex; + align-items: center; + flex-direction: column; + } + .documentDecorations-rotationPath { position: absolute; width: 100%; @@ -260,6 +277,7 @@ $resizeHandler: 8px; cursor: nwse-resize; background: unset; opacity: 1; + transform: scale(2); } .documentDecorations-topLeftResizer { @@ -314,6 +332,7 @@ $resizeHandler: 8px; .documentDecorations-topLeftResizer:hover, .documentDecorations-bottomRightResizer:hover { opacity: 1; + background: black; } .documentDecorations-bottomRightResizer { @@ -325,6 +344,7 @@ $resizeHandler: 8px; cursor: nesw-resize; background: unset; opacity: 1; + transform: scale(2); } .documentDecorations-topRightResizer { @@ -339,7 +359,6 @@ $resizeHandler: 8px; .documentDecorations-topRightResizer:hover, .documentDecorations-bottomLeftResizer:hover { - cursor: nesw-resize; background: black; opacity: 1; } @@ -392,6 +411,7 @@ $resizeHandler: 8px; justify-content: center; align-items: center; gap: 5px; + top: 4px; background: $light-gray; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9881ef9f1..6cf7df357 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -140,6 +140,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } }; + @action onContainerDown = (e: React.PointerEvent): void => { + setupMoveUpEvents( + this, + e, + e => this.onBackgroundMove(true, e), + e => {}, + emptyFunction + ); + }; + @action onTitleDown = (e: React.PointerEvent): void => { setupMoveUpEvents( this, @@ -311,6 +321,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); }; + @action + onLockDown = (e: React.PointerEvent): void => { + // Call util move event function + setupMoveUpEvents( + this, // target + e, // pointerEvent + returnFalse, // moveEvent + emptyFunction, // upEvent + e => { + UndoManager.RunInBatch( + () => + SelectionManager.Views().map(dv => { + dv.rootDoc._lockedPosition = !dv.rootDoc._lockedPosition; + dv.rootDoc._pointerEvents = dv.rootDoc._lockedPosition ? 'none' : undefined; + }), + 'toggleBackground' + ); + } // clickEvent + ); + }; + setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => { const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]); const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; @@ -791,7 +822,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px', }}> -
+
{hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} {hideResizers ?
: titleArea} @@ -817,21 +848,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P key="rad" style={{ background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`, - left: `${radiusHandleLocation + 3}`, - top: `${radiusHandleLocation + 23}`, + transform: `translate(${radiusHandleLocation}px, ${radiusHandleLocation}px)`, }} className={`documentDecorations-borderRadius`} onPointerDown={this.onRadiusDown} onContextMenu={e => e.preventDefault()} /> )} +
e.preventDefault()}> + +
{hideDocumentButtonBar ? null : (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 23b03bc50..362aa3c86 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,7 +1,7 @@ import React = require('react'); import * as fitCurve from 'fit-curve'; import { action, computed, observable, runInAction, trace } from 'mobx'; -import { Doc } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; import { InkData, InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; @@ -49,7 +49,7 @@ interface GestureOverlayProps { export class GestureOverlay extends Touchable { static Instance: GestureOverlay; - @observable public InkShape: string = ''; + @observable public InkShape: Opt; @observable public SavedColor?: string; @observable public SavedWidth?: number; @observable public Tool: ToolglassTools = ToolglassTools.None; @@ -644,8 +644,6 @@ export class GestureOverlay extends Touchable { if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - //push first points to so interactionUtil knows pointer is up - this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); const initialPoint = this._points[0]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; @@ -685,10 +683,10 @@ export class GestureOverlay extends Touchable { //if any of the shape is activated in the CollectionFreeFormViewChrome else if (this.InkShape) { this.makeBezierPolygon(this.InkShape, false); - this.dispatchGesture(GestureUtils.Gestures.Stroke); + this.dispatchGesture(this.InkShape); this._points.length = 0; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { - this.InkShape = ''; + this.InkShape = undefined; Doc.ActiveTool = InkTool.None; } } @@ -703,12 +701,13 @@ export class GestureOverlay extends Touchable { case GestureUtils.Gestures.Rectangle: case GestureUtils.Gestures.Circle: this.makeBezierPolygon(result.Name, true); - case GestureUtils.Gestures.StartBracket: - case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture(result.Name); break; case GestureUtils.Gestures.Line: - actionPerformed = this.handleLineGesture(); + if (!(actionPerformed = this.handleLineGesture())) { + this.makeBezierPolygon(result.Name, true); + actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Stroke); + } break; case GestureUtils.Gestures.Scribble: console.log('scribble'); @@ -972,7 +971,7 @@ export class GestureOverlay extends Touchable { ActiveDash(), 1, 1, - this.InkShape, + this.InkShape ?? '', 'none', 1.0, false @@ -999,7 +998,7 @@ export class GestureOverlay extends Touchable { ActiveDash(), 1, 1, - this.InkShape, + this.InkShape ?? '', 'none', 1.0, false diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss index 8929954c8..b1c97164a 100644 --- a/src/client/views/StyleProvider.scss +++ b/src/client/views/StyleProvider.scss @@ -1,11 +1,11 @@ .styleProvider-lock { - font-size: 12px; - width: 20; - height: 20; - position: absolute; - right: -25; - top: -5; - background: transparent; + font-size: 10; + width: 15; + height: 15; + position: absolute; + right: -0; + top: 0; + background: black; pointer-events: all; opacity: 0.3; display: flex; @@ -15,7 +15,7 @@ cursor: default; } .styleProvider-lock:hover { - opacity:1; + opacity: 1; } .styleProvider-treeView-icon, @@ -26,4 +26,4 @@ .styleProvider-treeView-icon { opacity: 0; -} \ No newline at end of file +} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index aadcd7169..a5a886f42 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -276,7 +276,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 && ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index daf69d4f6..983cf4f9b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -568,6 +568,8 @@ export class CollectionFreeFormView extends CollectionSubView { switch (ge.gesture) { + case GestureUtils.Gestures.Line: + break; default: case GestureUtils.Gestures.Circle: case GestureUtils.Gestures.Rectangle: @@ -602,33 +604,6 @@ export class CollectionFreeFormView extends CollectionSubView p.X)), Math.min(...ge.points.map(p => p.Y))); - const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); - const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] }; - const bWidth = bounds.r - bounds.x; - const bHeight = bounds.b - bounds.y; - const sel = this.getActiveDocuments().filter(doc => { - const l = NumCast(doc.x); - const r = l + doc[WidthSym](); - const t = NumCast(doc.y); - const b = t + doc[HeightSym](); - const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t); - if (pass) { - doc.x = l - bounds.x - bWidth / 2; - doc.y = t - bounds.y - bHeight / 2; - } - return pass; - }); - this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); - sel.forEach(d => this.props.removeDocument?.(d)); - e.stopPropagation(); - break; - case GestureUtils.Gestures.StartBracket: - const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y))); - this._inkToTextStartX = start[0]; - this._inkToTextStartY = start[1]; - break; - case GestureUtils.Gestures.EndBracket: if (this._inkToTextStartX && this._inkToTextStartY) { const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color); @@ -805,15 +780,15 @@ export class CollectionFreeFormView extends CollectionSubView ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve } @@ -862,6 +837,15 @@ export class CollectionFreeFormView extends CollectionSubView { + if ((otherCurve as any)._linear) { + return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); + } + return curve.intersects(otherCurve); + }; + /** * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all * ink strokes in the current collection. @@ -886,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView ({ x: p.X, y: p.Y }))); - curve.intersects(otherCurve).forEach((val: string | number, i: number) => { + this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1c48d47e9..040e03150 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1082,7 +1082,7 @@ export class DocumentViewInternal extends DocComponent )} {audioView} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 959c641a8..461d6984d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast } from '../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -77,10 +77,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'), { fireImmediately: true, delay: 1000 } ); + const layoutDoc = this.layoutDoc; this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { - if (true || !this.layoutDoc._height) { + if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) { this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 6eaf3c31a..fd0c0d141 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -771,36 +771,26 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); } -/** INK - * setActiveTool - * setStrokeWidth - * setStrokeColor - **/ - -ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) { +ScriptingGlobals.add(function setActiveTool(tool: InkTool | GestureUtils.Gestures, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); if (checkResult) { return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent'; } - if ([GestureUtils.Gestures.Circle, GestureUtils.Gestures.Rectangle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Triangle].includes(tool as any)) { + if (Object.values(GestureUtils.Gestures).includes(tool as any)) { if (GestureOverlay.Instance.InkShape === tool) { Doc.ActiveTool = InkTool.None; - GestureOverlay.Instance.InkShape = InkTool.None; + GestureOverlay.Instance.InkShape = undefined; } else { Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool; + GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; } } else if (tool) { // pen or eraser if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { Doc.ActiveTool = InkTool.None; - } else if (tool == InkTool.Write) { - // console.log("write mode selected - create groupDoc here!", tool) - Doc.ActiveTool = tool; - GestureOverlay.Instance.InkShape = ''; } else { Doc.ActiveTool = tool as any; - GestureOverlay.Instance.InkShape = ''; + GestureOverlay.Instance.InkShape = undefined; } } else { Doc.ActiveTool = InkTool.None; diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 2d3b1fdb8..41917aac9 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -19,8 +19,6 @@ export namespace GestureUtils { export enum Gestures { Line = 'line', - StartBracket = 'startbracket', - EndBracket = 'endbracket', Stroke = 'stroke', Scribble = 'scribble', Text = 'text', diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index b10a9da17..3ee9506cb 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -145,7 +145,7 @@ export class Result { // // NDollarRecognizer constants // -const NumMultistrokes = 6; +let NumMultistrokes = 0; const NumPoints = 96; const SquareSize = 250.0; const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35) @@ -162,73 +162,59 @@ const AngleSimilarityThreshold = Deg2Rad(30.0); // NDollarRecognizer class // export class NDollarRecognizer { - public Multistrokes: Multistroke[]; + public Multistrokes: Multistroke[] = []; - /** - * @IMPORTANT - IF YOU'RE ADDING A NEW GESTURE, BE SURE TO INCREMENT THE NumMultiStrokes CONST RIGHT ABOVE THIS CLASS. - */ constructor( useBoundedRotationInvariance: boolean // constructor ) { // // one predefined multistroke for each multistroke type // - this.Multistrokes = new Array(NumMultistrokes); - this.Multistrokes[0] = new Multistroke( - GestureUtils.Gestures.Rectangle, - useBoundedRotationInvariance, - new Array( + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Rectangle, + useBoundedRotationInvariance, new Array( - new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), - new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), - new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), - new Point(106, 146), //new Point(80, 150), new Point(50, 146), - new Point(30, 143) + new Array( + new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), + new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), + new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), + new Point(106, 146), //new Point(80, 150), new Point(50, 146), + new Point(30, 143) + ) ) ) ); - this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))); - this.Multistrokes[2] = new Multistroke( - GestureUtils.Gestures.StartBracket, - useBoundedRotationInvariance, - new Array( - // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150)) - new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150)) + this.Multistrokes.push(new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347))))); + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Triangle, // equilateral + useBoundedRotationInvariance, + new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) ) ); - this.Multistrokes[3] = new Multistroke( - GestureUtils.Gestures.EndBracket, - useBoundedRotationInvariance, - new Array( - // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152)) - // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150)) - new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100)) - ) - ); - this.Multistrokes[4] = new Multistroke( - GestureUtils.Gestures.Triangle, // equilateral - useBoundedRotationInvariance, - new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) - ); - this.Multistrokes[5] = new Multistroke( - GestureUtils.Gestures.Circle, - useBoundedRotationInvariance, - new Array( + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Circle, + useBoundedRotationInvariance, new Array( - new Point(200, 250), - new Point(240, 230), - new Point(248, 210), - new Point(248, 190), - new Point(240, 170), - new Point(200, 150), - new Point(160, 170), - new Point(151, 190), - new Point(151, 210), - new Point(160, 230), - new Point(201, 250) + new Array( + new Point(200, 250), + new Point(240, 230), + new Point(248, 210), + new Point(248, 190), + new Point(240, 170), + new Point(200, 150), + new Point(160, 170), + new Point(151, 190), + new Point(151, 210), + new Point(160, 230), + new Point(201, 250) + ) ) ) ); + NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes // // PREDEFINED STROKES // -- cgit v1.2.3-70-g09d2