aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/documents/Documents.ts7
-rw-r--r--src/client/util/InteractionUtils.tsx139
-rw-r--r--src/client/views/GestureOverlay.tsx123
-rw-r--r--src/client/views/InkingControl.tsx46
-rw-r--r--src/client/views/InkingStroke.tsx3
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss36
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx274
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx126
-rw-r--r--src/client/views/nodes/ColorBox.tsx6
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>;
}