diff options
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/documents/Documents.ts | 7 | ||||
| -rw-r--r-- | src/client/util/InteractionUtils.tsx | 139 | ||||
| -rw-r--r-- | src/client/views/GestureOverlay.tsx | 123 | ||||
| -rw-r--r-- | src/client/views/InkingControl.tsx | 46 | ||||
| -rw-r--r-- | src/client/views/InkingStroke.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss | 36 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx | 274 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 126 | ||||
| -rw-r--r-- | src/client/views/nodes/ColorBox.tsx | 6 |
11 files changed, 740 insertions, 24 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f7e19eecd..71bf8a516 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -630,12 +630,13 @@ export namespace Docs { return doc; } - export function InkDocument(color: string, tool: number, strokeWidth: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { + export function InkDocument(color: string, tool: number, strokeWidth: string, strokeBezier: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { const I = new Doc(); I.type = DocumentType.INK; I.layout = InkingStroke.LayoutString("data"); I.color = color; I.strokeWidth = strokeWidth; + I.strokeBezier = strokeBezier; I.tool = tool; I.title = "ink"; I.x = options.x; @@ -936,8 +937,8 @@ export namespace Docs { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; } else if (field instanceof InkField) { - const { selectedColor, selectedWidth, selectedTool } = InkingControl.Instance; - created = Docs.Create.InkDocument(selectedColor, selectedTool, selectedWidth, (field).inkData, resolved); + const { selectedColor, selectedWidth, selectedTool, selectedBezier } = InkingControl.Instance; + created = Docs.Create.InkDocument(selectedColor, selectedTool, selectedWidth, selectedBezier, (field).inkData, resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { created = Docs.Create.StackingDocument(DocListCast(field), resolved); diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 3a5345c80..ab1ccb25a 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,4 +1,7 @@ import React = require("react"); +import * as beziercurve from 'bezier-curve'; +import * as fitCurve from 'fit-curve'; +import InkOptionsMenu from "../views/collections/collectionFreeForm/InkOptionsMenu"; export namespace InteractionUtils { export const MOUSETYPE = "mouse"; @@ -87,8 +90,45 @@ export namespace InteractionUtils { return myTouches; } - export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string) { - const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); + export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string, bezier: string) { + var pts = ""; + var shape = ""; + if (InkOptionsMenu.Instance._circle) { + shape = "circle"; + } else if (InkOptionsMenu.Instance._rectangle) { + shape = "rectangle"; + } else if (InkOptionsMenu.Instance._triangle) { + shape = "triangle"; + } else if (InkOptionsMenu.Instance._arrow) { + shape = "arrow"; + } else if (InkOptionsMenu.Instance._line) { + shape = "line"; + } + if (shape !== "") { + //if any of the shape are true + const shapePts = makePolygon(shape, points); + pts = shapePts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); + } + 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: number[][] = []; + const newPts: { X: number; Y: number; }[] = []; + //convert to [][] for fitcurve module + for (var i = 0; i < points.length - 1; i++) { + newPoints.push([points[i].X, points[i].Y]); + } + const bezierCurves = fitCurve(newPoints, parseInt(bezier)); + for (var i = 0; i < bezierCurves.length; i++) { + for (var t = 0; t < 1; t += 0.01) { + const point = beziercurve(t, bezierCurves[i]); + newPts.push({ X: point[0], Y: point[1] }); + } + } + pts = newPts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); + } else { + //in the middle of drawing + pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); + } return ( <polyline points={pts} @@ -103,6 +143,101 @@ export namespace InteractionUtils { ); } + export function makePolygon(shape: string, points: { X: number, Y: number }[]) { + if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) { + //pointer is up (first and last points are the same) + if (shape === "arrow" || shape === "line") { + //if arrow or line, the two end points should be the starting and the ending point + var left = points[0].X; + var top = points[0].Y; + var right = points[1].X; + var bottom = points[1].Y; + } else { + //otherwise take max and min + const xs = points.map(p => p.X); + const ys = points.map(p => p.Y); + right = Math.max(...xs); + left = Math.min(...xs); + bottom = Math.max(...ys); + top = Math.min(...ys); + } + } else { + //if in the middle of drawing + //take first and last points + right = points[points.length - 1].X; + left = points[0].X; + bottom = points[points.length - 1].Y; + top = points[0].Y; + if (shape !== "arrow" && shape !== "line") { + //switch left/right and top/bottom if needed + if (left > right) { + const temp = right; + right = left; + left = temp; + } + if (top > bottom) { + const temp = top; + top = bottom; + bottom = temp; + } + } + } + points = []; + switch (shape) { + case "rectangle": + points.push({ X: left, Y: top }); + points.push({ X: right, Y: top }); + points.push({ X: right, Y: bottom }); + points.push({ X: left, Y: bottom }); + points.push({ X: left, Y: top }); + return points; + case "triangle": + points.push({ X: left, Y: bottom }); + points.push({ X: right, Y: bottom }); + points.push({ X: (right + left) / 2, Y: top }); + points.push({ X: left, Y: bottom }); + return points; + case "circle": + const centerX = (right + left) / 2; + const centerY = (bottom + top) / 2; + const radius = bottom - centerY; + for (var y = top; y < bottom; y++) { + const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + points.push({ X: x, Y: y }); + } + for (var y = bottom; y > top; y--) { + const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + const newX = centerX - (x - centerX); + points.push({ X: newX, Y: y }); + } + points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top }); + return points; + case "arrow": + const x1 = left; + const y1 = top; + const x2 = right; + const y2 = bottom; + const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + (Math.pow(Math.abs(y1 - y2), 2))); + const L2 = L1 / 5; + const angle = 0.785398; + const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle)); + const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle)); + const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle)); + const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle)); + points.push({ X: x1, Y: y1 }); + points.push({ X: x2, Y: y2 }); + points.push({ X: x3, Y: y3 }); + points.push({ X: x4, Y: y4 }); + points.push({ X: x2, Y: y2 }); + return points; + case "line": + points.push({ X: left, Y: top }); + points.push({ X: right, Y: bottom }); + return points; + default: + return points; + } + } /** * Returns whether or not the pointer event passed in is of the type passed in * @param e - pointer event. this event could be from a mouse, a pen, or a finger diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 4352ac52c..5714970c1 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -32,6 +32,7 @@ import { MobileInkOverlayContent } from "../../server/Message"; import MobileInkOverlay from "../../mobile/MobileInkOverlay"; import { RadialMenu } from "./nodes/RadialMenu"; import { SelectionManager } from "../util/SelectionManager"; +import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu"; @observer @@ -581,7 +582,8 @@ export default 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 }); if (MobileInterface.Instance && MobileInterface.Instance.drawingInk) { const { selectedColor, selectedWidth } = InkingControl.Instance; DocServer.Mobile.dispatchGesturePoints({ @@ -630,6 +632,23 @@ export default class GestureOverlay extends Touchable { break; } } + //if any of the shape is activated in the InkOptionsMenu + else if (InkOptionsMenu.Instance._circle || InkOptionsMenu.Instance._triangle || InkOptionsMenu.Instance._rectangle || InkOptionsMenu.Instance._line || InkOptionsMenu.Instance._arrow) { + if (InkOptionsMenu.Instance._circle) { + this.makePolygon("circle", false); + } else if (InkOptionsMenu.Instance._triangle) { + this.makePolygon("triangle", false); + } else if (InkOptionsMenu.Instance._rectangle) { + this.makePolygon("rectangle", false); + } else if (InkOptionsMenu.Instance._line) { + this.makePolygon("line", false); + } else if (InkOptionsMenu.Instance._arrow) { + this.makePolygon("arrow", false); + } + this.dispatchGesture(GestureUtils.Gestures.Stroke); + this._points = []; + InkOptionsMenu.Instance.allFalse(); + } // if we're not drawing in a toolglass try to recognize as gesture else { const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points)); @@ -651,6 +670,15 @@ export default class GestureOverlay extends Touchable { case GestureUtils.Gestures.Line: actionPerformed = this.handleLineGesture(); break; + case GestureUtils.Gestures.Triangle: + this.makePolygon("triangle", true); + break; + case GestureUtils.Gestures.Circle: + this.makePolygon("circle", true); + break; + case GestureUtils.Gestures.Rectangle: + this.makePolygon("rectangle", true); + break; case GestureUtils.Gestures.Scribble: console.log("scribble"); break; @@ -671,6 +699,95 @@ export default class GestureOverlay extends Touchable { document.removeEventListener("pointerup", this.onPointerUp); } + makePolygon = (shape: string, gesture: boolean) => { + const xs = this._points.map(p => p.X); + const ys = this._points.map(p => p.Y); + var right = Math.max(...xs); + var left = Math.min(...xs); + var bottom = Math.max(...ys); + var top = Math.min(...ys); + + if (!gesture) { + //if shape options is activated in inkOptionMenu + //take second to last point because _point[length-1] is _points[0] + right = this._points[this._points.length - 2].X; + left = this._points[0].X; + bottom = this._points[this._points.length - 2].Y; + top = this._points[0].Y; + if (shape !== "arrow" && shape !== "line") { + if (left > right) { + const temp = right; + right = left; + left = temp; + } + if (top > bottom) { + const temp = top; + top = bottom; + bottom = temp; + } + } + } + this._points = []; + switch (shape) { + //must push an extra point in the end so InteractionUtils knows pointer is up. + //must be (points[0].X,points[0]-1) + case "rectangle": + this._points.push({ X: left, Y: top }); + this._points.push({ X: right, Y: top }); + this._points.push({ X: right, Y: bottom }); + this._points.push({ X: left, Y: bottom }); + this._points.push({ X: left, Y: top }); + this._points.push({ X: left, Y: top - 1 }); + break; + case "triangle": + this._points.push({ X: left, Y: bottom }); + this._points.push({ X: right, Y: bottom }); + this._points.push({ X: (right + left) / 2, Y: top }); + this._points.push({ X: left, Y: bottom }); + this._points.push({ X: left, Y: bottom - 1 }); + break; + case "circle": + const centerX = (right + left) / 2; + const centerY = (bottom + top) / 2; + const radius = bottom - centerY; + for (var y = top; y < bottom; y++) { + const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + this._points.push({ X: x, Y: y }); + } + for (var y = bottom; y > top; y--) { + const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + const newX = centerX - (x - centerX); + this._points.push({ X: newX, Y: y }); + } + this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top }); + this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top - 1 }); + break; + case "line": + this._points.push({ X: left, Y: top }); + this._points.push({ X: right, Y: bottom }); + this._points.push({ X: right, Y: bottom - 1 }); + break; + case "arrow": + const x1 = left; + const y1 = top; + const x2 = right; + const y2 = bottom; + const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + (Math.pow(Math.abs(y1 - y2), 2))); + const L2 = L1 / 5; + const angle = 0.785398; + const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle)); + const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle)); + const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle)); + const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle)); + this._points.push({ X: x1, Y: y1 }); + this._points.push({ X: x2, Y: y2 }); + this._points.push({ X: x3, Y: y3 }); + this._points.push({ X: x4, Y: y4 }); + this._points.push({ X: x2, Y: y2 }); + this._points.push({ X: x1, Y: y1 - 1 }); + } + } + dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => { const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y); target?.dispatchEvent( @@ -710,11 +827,11 @@ export default class GestureOverlay extends Touchable { [this._strokes.map(l => { const b = this.getBounds(l); return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}> - {InteractionUtils.CreatePolyline(l, b.left, b.top, InkingControl.Instance.selectedColor, InkingControl.Instance.selectedWidth)} + {InteractionUtils.CreatePolyline(l, b.left, b.top, InkingControl.Instance.selectedColor, InkingControl.Instance.selectedWidth, InkingControl.Instance.selectedBezier)} </svg>; }), this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}> - {InteractionUtils.CreatePolyline(this._points, B.left, B.top, InkingControl.Instance.selectedColor, InkingControl.Instance.selectedWidth)} + {InteractionUtils.CreatePolyline(this._points, B.left, B.top, InkingControl.Instance.selectedColor, InkingControl.Instance.selectedWidth, InkingControl.Instance.selectedBezier)} </svg>] ]; } diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 41ee36d05..83109db1c 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -9,14 +9,15 @@ import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; import GestureOverlay from "./GestureOverlay"; import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; +import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu"; export class InkingControl { @observable static Instance: InkingControl; @computed private get _selectedTool(): InkTool { return FieldValue(NumCast(Doc.UserDoc().inkTool)) ?? InkTool.None; } @computed private get _selectedColor(): string { return CurrentUserUtils.ActivePen ? FieldValue(StrCast(CurrentUserUtils.ActivePen.backgroundColor)) ?? "rgb(0, 0, 0)" : "rgb(0, 0, 0)"; } @computed private get _selectedWidth(): string { return FieldValue(StrCast(Doc.UserDoc().inkWidth)) ?? "2"; } + @computed private get _selectedBezier(): string { return FieldValue(StrCast(Doc.UserDoc().inkBezier)) ?? "2"; } @observable public _open: boolean = false; - constructor() { InkingControl.Instance = this; } @@ -32,10 +33,21 @@ export class InkingControl { return (number < 16 ? "0" : "") + number.toString(16).toUpperCase(); } + @action + inkOptionsMenuChangeColor = (color: string) => { + const col: ColorState = { + hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" }, + rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "", + }; + this.switchColor(col); + InkOptionsMenu.Instance._colorBt = false; + } + @undoBatch switchColor = action((color: ColorState): void => { Doc.UserDoc().backgroundColor = color.hex.startsWith("#") ? color.hex + (color.rgb.a ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex; + InkOptionsMenu.Instance._color = StrCast(Doc.UserDoc().backgroundColor); CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = color.hex); if (InkingControl.Instance.selectedTool === InkTool.None) { @@ -60,6 +72,23 @@ export class InkingControl { if (!isNaN(parseInt(width))) { Doc.UserDoc().inkWidth = width; } + InkOptionsMenu.Instance._widthBt = false; + } + + @action + switchBezier = (bezier: string): void => { + if (!isNaN(parseInt(bezier))) { + Doc.UserDoc().inkBezier = bezier; + } + } + + @action + inkOptionsMenuChangeBezier = (e: React.PointerEvent): void => { + if (InkOptionsMenu.Instance._bezierBt === true) { + Doc.UserDoc().inkBezier = "300"; + } else { + Doc.UserDoc().inkBezier = "0"; + } } @computed @@ -83,8 +112,21 @@ export class InkingControl { return this._selectedWidth; } + @computed + get selectedBezier() { + return this._selectedBezier; + } } -Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); }); +Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { + InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); + //setup InkOptionsMenu(change jumpto value if necessary.Currenlty hardcoded to 300,300) + pen ? InkOptionsMenu.Instance.jumpTo(300, 300) : InkOptionsMenu.Instance.fadeOut(true); + InkOptionsMenu.Instance.changeColor = InkingControl.Instance.inkOptionsMenuChangeColor; + InkOptionsMenu.Instance.changeBezier = InkingControl.Instance.inkOptionsMenuChangeBezier; + InkOptionsMenu.Instance.changeWidth = InkingControl.Instance.switchWidth; + InkOptionsMenu.Instance._widthSelected = width; + InkOptionsMenu.Instance._color = color; +}); Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); }); Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); }); Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); }); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 8938e8b6c..3dc0a5b20 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -40,7 +40,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume const bottom = Math.max(...ys); const points = InteractionUtils.CreatePolyline(data, left, top, StrCast(this.layoutDoc.color, InkingControl.Instance.selectedColor), - StrCast(this.layoutDoc.strokeWidth, InkingControl.Instance.selectedWidth)); + StrCast(this.layoutDoc.strokeWidth, InkingControl.Instance.selectedWidth), + StrCast(this.layoutDoc.strokeBezier, InkingControl.Instance.selectedBezier)); const width = right - left; const height = bottom - top; const scaleX = this.props.PanelWidth() / width; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a1d1b0ece..3677746cd 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -33,6 +33,7 @@ import SharingManager from '../util/SharingManager'; import { Transform } from '../util/Transform'; import { CollectionDockingView } from './collections/CollectionDockingView'; import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu'; +import InkOptionsMenu from './collections/collectionFreeForm/InkOptionsMenu'; import { CollectionLinearView } from './collections/CollectionLinearView'; import { CollectionView, CollectionViewType } from './collections/CollectionView'; import { ContextMenu } from './ContextMenu'; @@ -567,6 +568,7 @@ export class MainView extends React.Component { <RadialMenu /> <PDFMenu /> <MarqueeOptionsMenu /> + <InkOptionsMenu /> <RichTextMenu /> <OverlayView /> <TimelineMenu /> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c753a703d..fb7784b58 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -458,7 +458,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P case GestureUtils.Gestures.Stroke: const points = ge.points; const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); - const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, InkingControl.Instance.selectedWidth, points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height }); + const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, InkingControl.Instance.selectedWidth, InkingControl.Instance.selectedBezier, points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height }); this.addDocument(inkDoc); e.stopPropagation(); break; diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss new file mode 100644 index 000000000..a7f4d4e53 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss @@ -0,0 +1,36 @@ +.antimodeMenu-button { + .color-preview { + width: 100%; + height: 100%; + } + + +} + +.sketch-picker { + background: #323232; + + .flexbox-fit { + background: #323232; + } +} + +.btn-group { + display: grid; + grid-template-columns: auto auto auto auto; + /* Make the buttons appear below each other */ +} + +.btn2-group { + display: block; + background: #323232; + grid-template-columns: auto; + + /* Make the buttons appear below each other */ + .antimodeMenu-button { + background: #323232; + display: block; + + + } +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx new file mode 100644 index 000000000..44488cbcf --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx @@ -0,0 +1,274 @@ +import React = require("react"); +import AntimodeMenu from "../../AntimodeMenu"; +import { observer } from "mobx-react"; +import { unimplementedFunction } from "../../../../Utils"; +import { observable, action } from "mobx"; +import "./InkOptionsMenu.scss"; + + +@observer +export default class InkOptionsMenu extends AntimodeMenu { + static Instance: InkOptionsMenu; + public changeColor: (color: string) => void = unimplementedFunction; + public changeBezier: (e: React.PointerEvent) => void = unimplementedFunction; + public changeWidth: (color: string) => void = unimplementedFunction; + + private _palette: (string)[]; + private _width: (string)[]; + + + public _circle: boolean; + public _triangle: boolean; + public _rectangle: boolean; + public _arrow: boolean; + public _line: boolean; + public _widthSelected: string; + + @observable public _circleBt: boolean; + @observable public _triangleBt: boolean; + @observable public _rectangleBt: boolean; + @observable public _arrowBt: boolean; + @observable public _lineBt: boolean; + @observable public _colorBt: boolean; + @observable public _color: string; + @observable public _bezierBt: boolean; + @observable public _widthBt: boolean; + + + + constructor(props: Readonly<{}>) { + super(props); + InkOptionsMenu.Instance = this; + this._canFade = false; + + this._circle = false; + this._triangle = false; + this._rectangle = false; + this._arrow = false; + this._line = false; + this._circleBt = false; + this._triangleBt = false; + this._rectangleBt = false; + this._arrowBt = false; + this._lineBt = false; + this._colorBt = false; + this._bezierBt = false; + this._widthBt = false; + + this._color = ""; + this._widthSelected = ""; + + + this._palette = [ + "D0021B", "F5A623", "F8E71C", "8B572A", "7ED321", "417505", "9013FE", "4A90E2", "50E3C2", "B8E986", "000000", "4A4A4A", "9B9B9B", "FFFFFF", + ]; + + this._width = [ + "1", "5", "10", "100", "200", "300" + ]; + + } + + + + drag = (e: React.PointerEvent) => { + this.dragStart(e); + } + + + + + + @action + toggleCircle = (e: React.PointerEvent) => { + const curr = this._circle; + this.allFalse(); + curr ? this._circle = false : this._circle = true; + this._circleBt = this._circle; + } + @action + toggleTriangle = (e: React.PointerEvent) => { + const curr = this._triangle; + this.allFalse(); + curr ? this._triangle = false : this._triangle = true; + this._triangleBt = this._triangle; + } + @action + toggleRectangle = (e: React.PointerEvent) => { + const curr = this._rectangle; + this.allFalse(); + curr ? this._rectangle = false : this._rectangle = true; + this._rectangleBt = this._rectangle; + } + @action + toggleArrow = (e: React.PointerEvent) => { + const curr = this._arrow; + this.allFalse(); + curr ? this._arrow = false : this._arrow = true; + this._arrowBt = this._arrow; + } + @action + toggleLine = (e: React.PointerEvent) => { + const curr = this._line; + this.allFalse(); + curr ? this._line = false : this._line = true; + this._lineBt = this._line; + } + + @action + changeBezierClick = (e: React.PointerEvent) => { + const curr = this._bezierBt; + this.allFalse(); + curr ? this._bezierBt = false : this._bezierBt = true; + this.changeBezier(e); + } + + @action + changeWidthClick = (e: React.PointerEvent) => { + this._widthBt ? this._widthBt = false : this._widthBt = true; + } + @action + changeColorClick = (e: React.PointerEvent) => { + this._colorBt ? this._colorBt = false : this._colorBt = true; + } + + allFalse = () => { + this._circle = false; + this._triangle = false; + this._rectangle = false; + this._arrow = false; + this._line = false; + this._circleBt = false; + this._triangleBt = false; + this._rectangleBt = false; + this._arrowBt = false; + this._lineBt = false; + this._bezierBt = false; + } + + render() { + var widthPicker; + if (this._widthBt) { + widthPicker = <div className="btn2-group"> + <button + className="antimodeMenu-button" + key="width" + onPointerDown={this.changeWidthClick} + style={this._widthBt ? { backgroundColor: "121212" } : {}}> + W + </button> + {this._width.map(wid => { + return <button + className="antimodeMenu-button" + key={wid} + onPointerDown={() => this.changeWidth(wid)} + style={this._colorBt ? { backgroundColor: "121212" } : {}}> + {wid} + </button>; + + })} + </div>; + } else { + widthPicker = <button + className="antimodeMenu-button" + key="width" + onPointerDown={this.changeWidthClick} + style={this._widthBt ? { backgroundColor: "121212" } : {}}> + W + </button>; + } + + var colorPicker; + if (this._colorBt) { + colorPicker = <div className="btn-group"> + <button + className="antimodeMenu-button" + key="color" + onPointerDown={this.changeColorClick} + style={this._colorBt ? { backgroundColor: "121212" } : {}}> + <div className="color-preview" style={this._color === "" ? { backgroundColor: "121212" } : { backgroundColor: this._color }}></div> + </button> + {this._palette.map(color => { + return <button + className="antimodeMenu-button" + key={color} + onPointerDown={() => this.changeColor(color)} + style={this._colorBt ? { backgroundColor: "121212" } : {}}> + <div className="color-preview" style={{ backgroundColor: color }}></div> + </button>; + })} + </div>; + } else { + colorPicker = <button + className="antimodeMenu-button" + title="colorChanger" + key="color" + onPointerDown={this.changeColorClick} + style={this._colorBt ? { backgroundColor: "121212" } : {}}> + <div className="color-preview" style={this._color === "" ? { backgroundColor: "121212" } : { backgroundColor: this._color }}></div> + </button>; + } + + + const buttons = [ + <button + className="antimodeMenu-button" + title="Drag" + key="drag" + onPointerDown={this.drag}> + ✜ + </button>, + <button + className="antimodeMenu-button" + title="Draw Circle" + key="circle" + onPointerDown={this.toggleCircle} + style={this._circleBt ? { backgroundColor: "121212" } : {}}> + O + </button>, + <button + className="antimodeMenu-button" + title="Draw Traingle" + key="triangle" + onPointerDown={this.toggleTriangle} + style={this._triangleBt ? { backgroundColor: "121212" } : {}}> + ∆ + </button>, + <button + className="antimodeMenu-button" + title="Draw Rectangle" + key="rectangle" + onPointerDown={this.toggleRectangle} + style={this._rectangleBt ? { backgroundColor: "121212" } : {}}> + ロ + </button>, + <button + className="antimodeMenu-button" + title="Draw Arrow" + key="arrow" + onPointerDown={this.toggleArrow} + style={this._arrowBt ? { backgroundColor: "121212" } : {}}> + ➜ + </button>, + <button + className="antimodeMenu-button" + title="Draw Line" + key="line" + onPointerDown={this.toggleLine} + style={this._lineBt ? { backgroundColor: "121212" } : {}}> + – + </button>, + <button + className="antimodeMenu-button" + title="Bezier changer" + key="bezier" + onPointerDown={this.changeBezierClick} + style={this._bezierBt ? { backgroundColor: "121212" } : {}}> + B + </button>, + widthPicker, + colorPicker, + ]; + return this.getElement(buttons); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index cdfeeaa6b..97244ed06 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -42,6 +42,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @observable _downY: number = 0; @observable _visible: boolean = false; _commandExecuted = false; + @observable _pointsX: number[] = []; + @observable _pointsY: number[] = []; + @observable _freeHand: boolean = false; componentDidMount() { this.props.setPreviewCursor?.(this.setPreviewCursor); @@ -57,6 +60,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (hideMarquee) { this._visible = false; } + this._pointsX = []; + this._pointsY = []; + this._freeHand = false; } @undoBatch @@ -191,6 +197,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque onPointerMove = (e: PointerEvent): void => { this._lastX = e.pageX; this._lastY = e.pageY; + this._pointsX.push(e.clientX); + this._pointsY.push(e.clientY); if (!e.cancelBubble) { if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) { @@ -519,6 +527,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } this.cleanupInteractions(false); } + if (e.key === "r") { + this._commandExecuted = true; + e.stopPropagation(); + e.preventDefault(); + this.changeFreeHand(true); + } + } + + @action + changeFreeHand = (x: boolean) => { + this._freeHand = x; } // @action // marqueeInkSelect(ink: Map<any, any>) { @@ -559,7 +578,51 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque // this.ink = new InkField(idata); // } // } + touchesLine(r1: { left: number, top: number, width: number, height: number }) { + for (var i = 0; i < this._pointsX.length; i++) { + const topLeft = this.props.getTransform().transformPoint(this._pointsX[i], this._pointsY[i]); + if (topLeft[0] > r1.left && + topLeft[0] < r1.left + r1.width && + topLeft[1] > r1.top && + topLeft[1] < r1.top + r1.height) { + return true; + } + } + return false; + } + boundingShape(r1: { left: number, top: number, width: number, height: number }) { + const trueLeft = this.props.getTransform().transformPoint(Math.min(...this._pointsX), Math.min(...this._pointsY))[0]; + const trueTop = this.props.getTransform().transformPoint(Math.min(...this._pointsX), Math.min(...this._pointsY))[1]; + const trueRight = this.props.getTransform().transformPoint(Math.max(...this._pointsX), Math.max(...this._pointsY))[0]; + const trueBottom = this.props.getTransform().transformPoint(Math.max(...this._pointsX), Math.max(...this._pointsY))[1]; + + if (r1.left > trueLeft && r1.top > trueTop && r1.left + r1.width < trueRight && r1.top + r1.height < trueBottom) { + var hasTop = false; + var hasLeft = false; + var hasBottom = false; + var hasRight = false; + for (var i = 0; i < this._pointsX.length; i++) { + const truePoint = this.props.getTransform().transformPoint(this._pointsX[i], this._pointsY[i]); + if (!hasLeft && (truePoint[0] > trueLeft && truePoint[0] < r1.left) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height)) { + hasLeft = true; + } + if (!hasTop && (truePoint[1] > trueTop && truePoint[1] < r1.top) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width)) { + hasTop = true; + } + if (!hasRight && (truePoint[0] < trueRight && truePoint[0] > r1.left + r1.width) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height)) { + hasRight = true; + } + if (!hasBottom && (truePoint[1] < trueBottom && truePoint[1] > r1.top + r1.height) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width)) { + hasBottom = true; + } + if (hasTop && hasLeft && hasBottom && hasRight) { + return true; + } + } + } + return false; + } marqueeSelect(selectBackgrounds: boolean = true) { const selRect = this.Bounds; const selection: Doc[] = []; @@ -569,8 +632,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const y = NumCast(doc.y); const w = NumCast(layoutDoc._width); const h = NumCast(layoutDoc._height); - if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { - selection.push(doc); + if (this._freeHand === false) { + if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { + selection.push(doc); + } + } else { + if (this.touchesLine({ left: x, top: y, width: w, height: h }) || + this.boundingShape({ left: x, top: y, width: w, height: h })) { + selection.push(doc); + } } }); if (!selection.length && selectBackgrounds) { @@ -597,8 +667,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const y = NumCast(doc.y); const w = NumCast(layoutDoc._width); const h = NumCast(layoutDoc._height); - if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { - selection.push(doc); + if (this._freeHand === false) { + if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { + selection.push(doc); + } + } else { + if (this.touchesLine({ left: x, top: y, width: w, height: h }) || + this.boundingShape({ left: x, top: y, width: w, height: h })) { + selection.push(doc); + } } }); } @@ -614,13 +691,40 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque * This contains the "C for collection, ..." text on marquees. * Commented out by syip2 when the marquee menu was added. */ - return <div className="marquee" style={{ - transform: `translate(${p[0]}px, ${p[1]}px)`, - width: `${Math.abs(v[0])}`, - height: `${Math.abs(v[1])}`, zIndex: 2000 - }} > - {/* <span className="marquee-legend" /> */} - </div>; + if (!this._freeHand) { + return <div className="marquee" style={{ + transform: `translate(${p[0]}px, ${p[1]}px)`, + width: `${Math.abs(v[0])}`, + height: `${Math.abs(v[1])}`, zIndex: 2000 + }} > + {/* <span className="marquee-legend" /> */} + </div>; + + } else { + //subtracted 250 for offset + var str: string = ""; + for (var i = 0; i < this._pointsX.length; i++) { + var x = 0; + x = this._pointsX[i] - 250; + str += x.toString(); + str += ","; + str += this._pointsY[i].toString(); + str += (" "); + } + + //hardcoded height and width. + return <div className="marquee" style={{ zIndex: 2000 }}> + <svg height={2000} width={2000}> + <polyline + points={str} + fill="none" + stroke="black" + strokeWidth="1" + strokeDasharray="3" + /> + </svg> + </div>; + } } render() { diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 6d53915ea..2ddf2c74a 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -28,8 +28,12 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument color={StrCast(CurrentUserUtils.ActivePen ? CurrentUserUtils.ActivePen.backgroundColor : undefined, StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} /> <div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}> - <div>{InkingControl.Instance.selectedWidth ?? 2}</div> + <div> {InkingControl.Instance.selectedWidth ?? 2}</div> <input type="range" value={InkingControl.Instance.selectedWidth ?? 2} defaultValue={2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => InkingControl.Instance.switchWidth(e.target.value)} /> + <div> {InkingControl.Instance.selectedBezier ?? 2}</div> + <input type="range" value={InkingControl.Instance.selectedBezier ?? 2} defaultValue={2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => InkingControl.Instance.switchBezier(e.target.value)} /> + <br /> + <br /> </div> </div>; } |
