aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoryunahi <60233430+yunahi@users.noreply.github.com>2020-07-19 04:44:51 +0900
committeryunahi <60233430+yunahi@users.noreply.github.com>2020-07-19 04:44:51 +0900
commit53713fc7ab10257d1117193941cda2f3e0cabcf5 (patch)
tree01269e78f3ec8679f2f2f665dc158731697407e2 /src
parentfcc7d375deb197854367ec40691fe0e72fba74f5 (diff)
added control points
Diffstat (limited to 'src')
-rw-r--r--src/client/util/InteractionUtils.tsx9
-rw-r--r--src/client/views/GestureOverlay.tsx17
-rw-r--r--src/client/views/InkingStroke.tsx82
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx59
4 files changed, 167 insertions, 0 deletions
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 07adbb8b1..1d7655748 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -99,6 +99,15 @@ export namespace InteractionUtils {
if (shape) { //if any of the shape are true
pts = makePolygon(shape, points);
}
+ else if (points.length > 9 && points[3].X === points[4].X && points[7].X === points[8].X) {
+ for (var i = 0; i < points.length; i += 4) {
+ const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
+ for (var t = 0; t < 1; t += 0.01) {
+ const point = beziercurve(t, array);
+ pts.push({ X: point[0], Y: point[1] });
+ }
+ }
+ }
else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
//pointer is up (first and last points are the same)
const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index a48b7b673..90d8b370e 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -23,6 +23,7 @@ import HorizontalPalette from "./Palette";
import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
+import * as fitCurve from 'fit-curve';
@observer
export default class GestureOverlay extends Touchable {
@@ -632,6 +633,22 @@ export default class GestureOverlay extends Touchable {
// if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
if (!actionPerformed) {
+ const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
+ newPoints.pop();
+ const controlPoints: { X: number, Y: number }[] = [];
+
+ const bezierCurves = fitCurve(newPoints, 10);
+ for (const curve of bezierCurves) {
+
+ controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
+ controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
+ controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
+ controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
+
+
+ }
+ this._points = controlPoints;
+
this.dispatchGesture(GestureUtils.Gestures.Stroke);
}
this._points = [];
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index abc698e62..974921be8 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -17,6 +17,7 @@ import { Scripting } from "../util/Scripting";
import { Doc } from "../../fields/Doc";
import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
import { action } from "mobx";
+import { setupMoveUpEvents } from "../../Utils";
library.add(faPaintBrush);
@@ -45,6 +46,38 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
FormatShapePane.Instance.Pinned = true;
}
+ private _prevX = 0;
+ private _prevY = 0;
+ private _controlNum = 0;
+ @action
+ onControlDown = (e: React.PointerEvent, i: number): void => {
+ setupMoveUpEvents(this, e, this.onControlMove, this.onControlup, (e) => { });
+ this._prevX = e.clientX;
+ this._prevY = e.clientY;
+ this._controlNum = i;
+ }
+
+ @action
+ changeCurrPoint = (i: number) => {
+ FormatShapePane.Instance._currPoint = i;
+ }
+
+ @action
+ onControlMove = (e: PointerEvent, down: number[]): boolean => {
+ const xDiff = this._prevX - e.clientX;
+ const yDiff = this._prevY - e.clientY;
+ FormatShapePane.Instance.control(xDiff, yDiff, this._controlNum);
+ this._prevX = e.clientX;
+ this._prevY = e.clientY;
+ return false;
+ }
+
+ onControlup = (e: PointerEvent) => {
+ this._prevX = 0;
+ this._prevY = 0;
+ this._controlNum = 0;
+ }
+
render() {
TraceMobx();
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
@@ -69,6 +102,51 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15),
StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
"none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true);
+
+ const controlPoints: { X: number, Y: number, I: number }[] = [];
+ const handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = [];
+ const handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = [];
+
+ if (data.length > 5) {
+ for (var i = 0; i <= data.length - 4; i += 4) {
+ controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
+ controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
+ handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
+ handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
+ }
+ handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 });
+ for (var i = 2; i < data.length - 2; i += 4) {
+ handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
+ }
+ handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+
+
+ }
+ const controls = controlPoints.map((pts, i) =>
+
+ <svg height="10" width="10">
+ <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r="5" stroke="black" stroke-width="1" fill="red"
+ onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="all-scroll" />
+ </svg>);
+ const handles = handlePoints.map((pts, i) =>
+
+ <svg height="10" width="10">
+ <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r="5" stroke="black" stroke-width="1" fill="green"
+ onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="all-scroll" display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
+ </svg>);
+ const handleLines = handleLine.map((pts, i) =>
+
+ <svg height="100" width="100">
+ <line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+ x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="black" stroke-width="1"
+ display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
+ <line x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+ x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="black" stroke-width="1"
+ display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
+
+ </svg>);
+
+
return (
<svg className="inkingStroke"
width={width}
@@ -91,6 +169,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
</defs>
{hpoints}
{points}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? controls : ""}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handles : ""}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handleLines : ""}
+
</svg>
);
}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index 9d9ce7f36..a2dc241c0 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -28,6 +28,9 @@ export default class FormatShapePane extends AntimodeMenu {
@observable private _lock = false;
@observable private _fillBtn = false;
@observable private _lineBtn = false;
+ @observable _controlBtn = false;
+ @observable private _controlPoints: { X: number, Y: number }[] = [];
+ @observable _currPoint = -1;
getField(key: string) {
return this.selectedInk?.reduce((p, i) =>
@@ -158,6 +161,51 @@ export default class FormatShapePane extends AntimodeMenu {
}));
}
+ @undoBatch
+ @action
+ control = (xDiff: number, yDiff: number, controlNum: number) => {
+ this.selectedInk?.forEach(action(inkView => {
+ if (this.selectedInk?.length === 1) {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+
+ const newPoints: { X: number, Y: number }[] = [];
+ const order = controlNum % 4;
+ for (var i = 0; i < ink.length; i++) {
+ if (controlNum === i ||
+ (order === 0 && i === controlNum + 1) ||
+ (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
+ (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
+ (order === 3 && i === controlNum - 1) ||
+ (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
+ (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2)) {
+ newPoints.push({ X: ink[i].X - (xDiff), Y: ink[i].Y - (yDiff) });
+ }
+ else {
+ newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ }
+ const oldx = doc.x;
+ const oldy = doc.y;
+ doc.data = new InkField(newPoints);
+ const xs2 = newPoints.map(p => p.X);
+ const ys2 = newPoints.map(p => p.Y);
+ const left2 = Math.min(...xs2);
+ const top2 = Math.min(...ys2);
+ const right2 = Math.max(...xs2);
+ const bottom2 = Math.max(...ys2);
+ doc._height = (bottom2 - top2) * inkView.props.ScreenToLocalTransform().Scale;
+ doc._width = (right2 - left2) * inkView.props.ScreenToLocalTransform().Scale;
+ doc.x = oldx;
+ doc.y = oldy;
+ }
+ }
+ }
+ }));
+ }
+
colorPicker(setter: (color: string) => {}) {
return <div className="btn-group-palette" key="colorpicker" >
@@ -193,6 +241,14 @@ export default class FormatShapePane extends AntimodeMenu {
</>;
}
+ controlPointsButton() {
+ return <>
+ <button className="antimodeMenu-button" key="fill" onPointerDown={action(() => this._controlBtn = this._controlBtn ? false : true)} style={{ position: "absolute", right: 80, backgroundColor: this._controlBtn ? "black" : "" }}>
+ <FontAwesomeIcon icon="bezier-curve" size="lg" />
+ </button>
+ <br /> <br />
+ </>;
+ }
@computed get fillButton() { return this.colorButton(this.colorFil, () => this._fillBtn = !this._fillBtn); }
@computed get lineButton() { return this.colorButton(this.colorStk, () => this._lineBtn = !this._lineBtn); }
@@ -206,6 +262,8 @@ export default class FormatShapePane extends AntimodeMenu {
@computed get XpsInput() { return this.inputBox("Xps", this.shapeXps, (val: string) => this.shapeXps = val); }
@computed get YpsInput() { return this.inputBox("Yps", this.shapeYps, (val: string) => this.shapeYps = val); }
+ @computed get controlPoints() { return this.controlPointsButton(); }
+
@computed get propertyGroupItems() {
const fillCheck = <div key="fill" style={{ display: this._subOpen[0] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
<input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={undoBatch(action(() => this.unFilled = true))} />
@@ -260,6 +318,7 @@ export default class FormatShapePane extends AntimodeMenu {
<br /> <br />
Rotation {this.rotInput}
<br /> <br />
+ Edit Points {this.controlPoints}
</div>;
const positionCheck = <div key="posCheck" style={{ display: this._subOpen[3] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>