From d9b5d949c22658531504c95cc94e30c824519cd1 Mon Sep 17 00:00:00 2001 From: vkalev <50213748+vkalev@users.noreply.github.com> Date: Thu, 1 Jul 2021 12:44:52 -0500 Subject: ink controls resize on hover --- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/InkControl.tsx | 103 ++++++++++++++++++++++ src/client/views/InkStrokeProperties.ts | 141 ++++++++++++++++--------------- src/client/views/InkingStroke.tsx | 35 +------- 4 files changed, 179 insertions(+), 102 deletions(-) create mode 100644 src/client/views/InkControl.tsx (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index bf939d57c..d1682c09d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -201,7 +201,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b (e: PointerEvent, down: number[], delta: number[]) => { const movement = { X: delta[0], Y: e.clientY - down[1] }; const angle = Math.max(1, Math.abs(movement.Y / 10)); - InkStrokeProperties.Instance?.rotate(2 * movement.X / angle * (Math.PI / 180)); + InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180)); return false; }, () => this._rotateUndo?.end(), diff --git a/src/client/views/InkControl.tsx b/src/client/views/InkControl.tsx new file mode 100644 index 000000000..ae0fc0b22 --- /dev/null +++ b/src/client/views/InkControl.tsx @@ -0,0 +1,103 @@ +import React = require("react"); +import { observable, action } from "mobx"; +import { observer } from "mobx-react"; +import { InkStrokeProperties } from "./InkStrokeProperties"; +import { setupMoveUpEvents, emptyFunction } from "../../Utils"; +import { UndoManager } from "../util/UndoManager"; +import { ControlPoint } from "../../fields/InkField"; +import { Transform } from "../util/Transform"; + +export interface InkControlProps { + control: ControlPoint; + left: number; + top: number; + scaleX: number; + scaleY: number; + strokeWidth: number; + ScreenToLocalTransform: () => Transform; +} + +@observer +export class InkControl extends React.Component { + @observable private _overControl = false; + + @action + onPointerEnter = () => { + this._overControl = true; + } + + @action + onPointerLeave = () => { + this._overControl = false; + } + + /** + * Handles the movement of a selected control point when the user clicks and drags. + * @param controlNum The index of the currently selected control point. + */ + @action + onControlDown = (e: React.PointerEvent, controlNum: number): void => { + if (InkStrokeProperties.Instance) { + InkStrokeProperties.Instance.moveControl(0, 0, 1); + const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + const screenScale = this.props.ScreenToLocalTransform().Scale; + setupMoveUpEvents(this, e, + (e: PointerEvent, down: number[], delta: number[]) => { + InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlNum); + return false; + }, + () => controlUndo?.end(), emptyFunction); + } + } + + /** + * Deletes the currently selected points. + * @param e Keyboard Event. + */ + @action + onDelete = (e: KeyboardEvent) => { + if (["-", "Backspace", "Delete"].includes(e.key)) { + if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation(); + } + } + + /** + * Changes the current selected control point. + * @param i The number of the point to be selected. + */ + @action + changeCurrPoint = (i: number) => { + if (InkStrokeProperties.Instance) { + InkStrokeProperties.Instance._currPoint = i; + document.addEventListener("keydown", this.onDelete, true); + } + } + + + render() { + const control = this.props.control; + const left = this.props.left; + const top = this.props.top; + const scaleX = this.props.scaleX; + const scaleY = this.props.scaleY; + const strokeWidth = this.props.strokeWidth; + + return ( + + { this.changeCurrPoint(control.I); this.onControlDown(e, control.I); }} + onMouseEnter={this.onPointerEnter} + onMouseLeave={this.onPointerLeave} + pointerEvents="all" + cursor="default" + /> + + ) + } +} \ No newline at end of file diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 5c5395984..720a89334 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -32,51 +32,52 @@ export class InkStrokeProperties { const inks = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK); return inks.length ? inks : undefined; } - @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; } - @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; } - @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; } - @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; } - @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; } - @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; } - @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; } - @computed get widthStk() { return this.getField("strokeWidth") || "1"; } - @computed get markHead() { return this.getField("strokeStartMarker") || ""; } - @computed get markTail() { return this.getField("strokeEndMarker") || ""; } - @computed get shapeHgt() { return this.getField("_height"); } - @computed get shapeWid() { return this.getField("_width"); } - @computed get shapeXps() { return this.getField("x"); } - @computed get shapeYps() { return this.getField("y"); } - @computed get shapeRot() { return this.getField("rotation"); } - set unFilled(value) { this.colorFil = value ? "" : this._lastFill; } - set solidFil(value) { this.unFilled = !value; } - set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); } - set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); } - set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); } - set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); } - set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; } - set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; } - set dashdStk(value) { - value && (this._lastDash = value) && (this.unStrokd = false); - this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined); - } - set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); } - set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); } - set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); } - set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); } - set shapeWid(value) { - this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => { - const oldWidth = NumCast(i.rootDoc._width); - i.rootDoc._width = Number(value); - this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth); - }); - } - set shapeHgt(value) { - this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => { - const oldHeight = NumCast(i.rootDoc._height); - i.rootDoc._height = Number(value); - this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight); - }); - } + + // @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; } + // @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; } + // @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; } + // @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; } + // @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; } + // @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; } + // @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; } + // @computed get widthStk() { return this.getField("strokeWidth") || "1"; } + // @computed get markHead() { return this.getField("strokeStartMarker") || ""; } + // @computed get markTail() { return this.getField("strokeEndMarker") || ""; } + // @computed get shapeHgt() { return this.getField("_height"); } + // @computed get shapeWid() { return this.getField("_width"); } + // @computed get shapeXps() { return this.getField("x"); } + // @computed get shapeYps() { return this.getField("y"); } + // @computed get shapeRot() { return this.getField("rotation"); } + // set unFilled(value) { this.colorFil = value ? "" : this._lastFill; } + // set solidFil(value) { this.unFilled = !value; } + // set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); } + // set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); } + // set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); } + // set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); } + // set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; } + // set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; } + // set dashdStk(value) { + // value && (this._lastDash = value) && (this.unStrokd = false); + // this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined); + // } + // set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); } + // set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); } + // set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); } + // set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); } + // set shapeWid(value) { + // this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => { + // const oldWidth = NumCast(i.rootDoc._width); + // i.rootDoc._width = Number(value); + // this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth); + // }); + // } + // set shapeHgt(value) { + // this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => { + // const oldHeight = NumCast(i.rootDoc._height); + // i.rootDoc._height = Number(value); + // this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight); + // }); + // } /** * Adds a new control point to the ink instance when editing its format. @@ -191,7 +192,7 @@ export class InkStrokeProperties { */ @undoBatch @action - rotate = (angle: number) => { + rotateInk = (angle: number) => { this.applyFunction((doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); @@ -301,29 +302,29 @@ export class InkStrokeProperties { return newPoints; }); - /** - * Changes the color of the border of the ink instance. - * @param color The new hex value to change the border to. - * @returns true. - */ - @undoBatch - @action - switchStk = (color: ColorState) => { - const val = String(color.hex); - this.colorStk = val; - return true; - } + // /** + // * Changes the color of the border of the ink instance. + // * @param color The new hex value to change the border to. + // * @returns true. + // */ + // @undoBatch + // @action + // switchStk = (color: ColorState) => { + // const val = String(color.hex); + // this.colorStk = val; + // return true; + // } - /** - * Changes the color of the fill of the ink instance. - * @param color The new hex value to change the fill to. - * @returns true. - */ - @undoBatch - @action - switchFil = (color: ColorState) => { - const val = String(color.hex); - this.colorFil = val; - return true; - } + // /** + // * Changes the color of the fill of the ink instance. + // * @param color The new hex value to change the fill to. + // * @returns true. + // */ + // @undoBatch + // @action + // switchFil = (color: ColorState) => { + // const val = String(color.hex); + // this.colorFil = val; + // return true; + // } } \ No newline at end of file diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 163eb05b0..9555557ec 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -18,6 +18,7 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView"; import React = require("react"); import { InkStrokeProperties } from "./InkStrokeProperties"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; +import { InkControl } from "./InkControl"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -79,29 +80,6 @@ export class InkingStroke extends ViewBoxBaseComponent { - if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance._currPoint = i; - document.addEventListener("keydown", this.onDelete, true); - } - } - - /** - * Deletes the currently selected points. - * @param e Keyboard Event. - */ - @action - onDelete = (e: KeyboardEvent) => { - if (["-", "Backspace", "Delete"].includes(e.key)) { - if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation(); - } - } - /** * Handles the movement of the entire ink object when the user clicks and drags. * @param e React Pointer Event. @@ -113,7 +91,7 @@ export class InkingStroke extends ViewBoxBaseComponent 1 && lineRgt - lineLft > 1, false); - const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, strokeWidth / 6, + const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - strokeWidth * 4 * scaleX, lineTop - strokeWidth * 4 * scaleX, "#1F85DE", strokeWidth / 6, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false); @@ -203,12 +181,7 @@ export class InkingStroke extends ViewBoxBaseComponent this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} /> ); // Control points of the ink (blue outlined squares) that are made visible to user when editing its format. - const controls = controlPoints.map((pts, i) => - - { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="default" - /> - ); + const controls = controlPoints.map((pts, i) => ); // Set of two blue lines (each with a handle at the end) that are rendered perpendicular to the current selected point while editing. const handleLines = handleLine.map((pts, i) => -- cgit v1.2.3-70-g09d2