aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkingStroke.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-07-22 14:11:30 -0400
committerbobzel <zzzman@gmail.com>2022-07-22 14:11:30 -0400
commit358f9e266ef264442aea1e2c7d5d959a19f7624c (patch)
treecdcd9ea131ed3c1d516b84f4873e7ae0ff525019 /src/client/views/InkingStroke.tsx
parentea95bd9719623117fdf73a290633ae20839976f0 (diff)
adjusted native dim scaling slightly combining props.scaling and props.ContentScaling into props.NativeDimScaling and fixing some resizing behaviors for fitWidth freeformviews and native-sized text boxes. Also fixed clicking on presboxe elements to not drag.
Diffstat (limited to 'src/client/views/InkingStroke.tsx')
-rw-r--r--src/client/views/InkingStroke.tsx536
1 files changed, 325 insertions, 211 deletions
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index dace9716a..e5de7a0c5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -20,35 +20,37 @@
Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class
*/
-import React = require("react");
-import { action, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, WidthSym } from "../../fields/Doc";
-import { InkData, InkField, InkTool } from "../../fields/InkField";
-import { Cast, NumCast, RTFCast, StrCast } from "../../fields/Types";
-import { TraceMobx } from "../../fields/util";
-import { OmitKeys, returnFalse, setupMoveUpEvents } from "../../Utils";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { InteractionUtils } from "../util/InteractionUtils";
-import { SnappingManager } from "../util/SnappingManager";
-import { Transform } from "../util/Transform";
-import { UndoManager } from "../util/UndoManager";
-import { ContextMenu } from "./ContextMenu";
-import { ViewBoxBaseComponent } from "./DocComponent";
-import { Colors } from "./global/globalEnums";
-import { InkControlPtHandles, InkEndPtHandles } from "./InkControlPtHandles";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { InkTangentHandles } from "./InkTangentHandles";
-import { DocComponentView } from "./nodes/DocumentView";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
-import "./InkStroke.scss";
-import Color = require("color");
+import React = require('react');
+import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, WidthSym } from '../../fields/Doc';
+import { InkData, InkField, InkTool } from '../../fields/InkField';
+import { Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { TraceMobx } from '../../fields/util';
+import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { CognitiveServices } from '../cognitive_services/CognitiveServices';
+import { InteractionUtils } from '../util/InteractionUtils';
+import { SnappingManager } from '../util/SnappingManager';
+import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
+import { ContextMenu } from './ContextMenu';
+import { ViewBoxBaseComponent } from './DocComponent';
+import { Colors } from './global/globalEnums';
+import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
+import { InkStrokeProperties } from './InkStrokeProperties';
+import { InkTangentHandles } from './InkTangentHandles';
+import { DocComponentView } from './nodes/DocumentView';
+import { FieldView, FieldViewProps } from './nodes/FieldView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import './InkStroke.scss';
+import Color = require('color');
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(InkingStroke, fieldStr);
+ }
public static IsClosed(inkData: InkData) {
return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
}
@@ -56,13 +58,15 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
private _selDisposer?: IReactionDisposer;
@observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
- @observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
- @observable _nearestScrPt?: { X: number, Y: number }; // nearst screen point on the ink stroke ""
+ @observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
+ @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke ""
componentDidMount() {
this.props.setContentView?.(this);
- this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
- selected => !selected && (InkStrokeProperties.Instance._controlButton = false));
+ this._selDisposer = reaction(
+ () => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
+ selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
+ );
}
componentWillUnmount() {
this._selDisposer?.();
@@ -70,36 +74,36 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
// transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
// fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
- screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1);
+ screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1);
getAnchor = () => {
console.log(document.activeElement);
return this._subContentView?.getAnchor?.() || this.rootDoc;
- }
+ };
scrollFocus = (textAnchor: Doc, smooth: boolean) => {
return this._subContentView?.scrollFocus?.(textAnchor, smooth);
- }
+ };
/**
- * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
- * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
- * the center of the ink stroke changes as the stroke is rotated.
- */
+ * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
+ * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
+ * the center of the ink stroke changes as the stroke is rotated.
+ */
getCenter = (xf: Transform) => {
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
const angle = -NumCast(this.layoutDoc.rotation);
const newPoints = inkData.map(pt => {
- const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * inkScaleY / inkScaleX;
- const newY = Math.sin(angle) * pt.X * inkScaleX / inkScaleY + Math.cos(angle) * pt.Y;
+ const newX = Math.cos(angle) * pt.X - (Math.sin(angle) * pt.Y * inkScaleY) / inkScaleX;
+ const newY = (Math.sin(angle) * pt.X * inkScaleX) / inkScaleY + Math.cos(angle) * pt.Y;
return { X: newX, Y: newY };
});
const crx = (Math.max(...newPoints.map(np => np.X)) + Math.min(...newPoints.map(np => np.X))) / 2;
const cry = (Math.max(...newPoints.map(np => np.Y)) + Math.min(...newPoints.map(np => np.Y))) / 2;
- const cx = Math.cos(-angle) * crx - Math.sin(-angle) * cry * inkScaleY / inkScaleX;
- const cy = Math.sin(-angle) * crx * inkScaleX / inkScaleY + Math.cos(-angle) * cry;
+ const cx = Math.cos(-angle) * crx - (Math.sin(-angle) * cry * inkScaleY) / inkScaleX;
+ const cy = (Math.sin(-angle) * crx * inkScaleX) / inkScaleY + Math.cos(-angle) * cry;
const tc = xf.transformPoint((cx - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (cy - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2);
return { X: tc[0], Y: tc[1] };
- }
+ };
/**
* analyzes the ink stroke and saves the analysis of the stroke to the 'inkAnalysis' field,
@@ -107,7 +111,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
*/
analyzeStrokes() {
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
- CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
+ CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ['inkAnalysis', 'handwriting'], [data]);
}
/**
@@ -115,12 +119,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
* When displayed as a mask, the stroke is rendered with mixBlendMode set to multiply so that the stroke will
* appear to illuminate what it covers up. At the same time, all pixels that are not under the stroke will be
* dimmed by a semi-opaque overlay mask.
- */
+ */
public static toggleMask = action((inkDoc: Doc) => {
inkDoc.isInkMask = !inkDoc.isInkMask;
- inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
- inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
- inkDoc.color = "#9b9b9bff";
+ inkDoc._backgroundColor = inkDoc.isInkMask ? 'rgba(0,0,0,0.7)' : undefined;
+ inkDoc.mixBlendMode = inkDoc.isInkMask ? 'hard-light' : undefined;
+ inkDoc.color = '#9b9b9bff';
inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
});
/**
@@ -132,46 +136,60 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
this._handledClick = false;
const inkView = this.props.docViewPath().lastElement();
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
- const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint(
- (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const screenPts = inkData
+ .map(point =>
+ this.screenToLocal()
+ .inverse()
+ .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+ )
+ .map(p => ({ X: p[0], Y: p[1] }));
const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
const controlIndex = nearestSeg;
const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
var controlUndo: UndoManager.Batch | undefined;
const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected();
- setupMoveUpEvents(this, e,
- !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => {
- if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
- const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
- const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
- InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
- InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
- return false;
- }),
- !isEditing ? returnFalse : action(() => {
- controlUndo?.end();
- controlUndo = undefined;
- UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
- }),
+ setupMoveUpEvents(
+ this,
+ e,
+ !isEditing
+ ? returnFalse
+ : action((e: PointerEvent, down: number[], delta: number[]) => {
+ if (!controlUndo) controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
+ const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
+ const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
+ InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
+ InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
+ return false;
+ }),
+ !isEditing
+ ? returnFalse
+ : action(() => {
+ controlUndo?.end();
+ controlUndo = undefined;
+ UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
+ }),
action((e: PointerEvent, doubleTap: boolean | undefined) => {
doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
if (doubleTap) {
InkStrokeProperties.Instance._controlButton = true;
InkStrokeProperties.Instance._currentPoint = -1;
- this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
+ this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
if (isEditing) {
this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
}
}
- }), isEditing, isEditing, action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1)));
- }
+ }),
+ isEditing,
+ isEditing,
+ action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1))
+ );
+ };
/**
* @param scrPt a point in the screen coordinate space
* @returns the point in the ink data's coordinate space.
*/
- ptFromScreen = (scrPt: { X: number, Y: number }) => {
+ ptFromScreen = (scrPt: { X: number; Y: number }) => {
const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
const docPt = this.screenToLocal().transformPoint(scrPt.X, scrPt.Y);
const inkPt = {
@@ -179,39 +197,39 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop,
};
return inkPt;
- }
+ };
/**
* @param inkPt a point in the ink data's coordinate space
* @returns the screen point corresponding to the ink point
*/
- ptToScreen = (inkPt: { X: number, Y: number }) => {
+ ptToScreen = (inkPt: { X: number; Y: number }) => {
const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
const docPt = {
X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2
+ Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2,
};
const scrPt = this.screenToLocal().inverse().transformPoint(docPt.X, docPt.Y);
return { X: scrPt[0], Y: scrPt[1] };
- }
+ };
/**
- * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs'
- * @param scrPt - the point to snap to this stroke
- * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments)
- *
- * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point
- */
- snapPt = (scrPt: { X: number, Y: number }, excludeSegs?: number[]) => {
+ * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs'
+ * @param scrPt - the point to snap to this stroke
+ * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments)
+ *
+ * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point
+ */
+ snapPt = (scrPt: { X: number; Y: number }, excludeSegs?: number[]) => {
const { inkData } = this.inkScaledData();
const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []);
return { nearestPt, distance: distance * this.screenToLocal().inverse().Scale };
- }
+ };
/**
- * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale
- * factor for converting between ink and screen space.
- */
+ * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale
+ * factor for converting between ink and screen space.
+ */
inkScaledData = () => {
const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
@@ -228,27 +246,31 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
inkLeft,
inkWidth,
inkHeight,
- inkScaleX: ((this.props.PanelWidth() - inkStrokeWidth) / ((inkWidth - inkStrokeWidth) || 1) || 1),
- inkScaleY: ((this.props.PanelHeight() - inkStrokeWidth) / ((inkHeight - inkStrokeWidth) || 1) || 1)
+ inkScaleX: (this.props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth || 1) || 1,
+ inkScaleY: (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth || 1) || 1,
};
- }
+ };
//
- // this updates the highlight for the nearest point on the curve to the cursor.
+ // this updates the highlight for the nearest point on the curve to the cursor.
// if the user double clicks, this highlighted point will be added as a control point in the curve.
//
@action
onPointerMove = (e: React.PointerEvent) => {
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
- const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint(
- (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const screenPts = inkData
+ .map(point =>
+ this.screenToLocal()
+ .inverse()
+ .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+ )
+ .map(p => ({ X: p[0], Y: p[1] }));
const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
this._nearestT = nearestT;
this._nearestSeg = nearestSeg;
this._nearestScrPt = nearestPt;
- }
+ };
/**
* @returns the nearest screen point to the cursor (to render a highlight for the point to be added)
@@ -263,50 +285,66 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
componentUI = (boundsLeft: number, boundsTop: number) => {
const inkDoc = this.props.Document;
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
- const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
+ const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
const screenInkWidth = this.screenToLocal().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
- const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint(
- (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const screenPts = inkData
+ .map(point =>
+ this.screenToLocal()
+ .inverse()
+ .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+ )
+ .map(p => ({ X: p[0], Y: p[1] }));
const screenHdlPts = screenPts;
const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
const markerScale = NumCast(this.layoutDoc.strokeMarkerScale);
- return SnappingManager.GetIsDragging() ? (null) :
- !InkStrokeProperties.Instance._controlButton ?
- (!this.props.isSelected() || InkingStroke.IsClosed(inkData) ? (null) :
- <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
- <InkEndPtHandles
- inkView={this.props.docViewPath().lastElement()}
- inkDoc={inkDoc}
- startPt={screenPts[0]}
- endPt={screenPts.lastElement()}
- screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
- </div>) :
+ return SnappingManager.GetIsDragging() ? null : !InkStrokeProperties.Instance._controlButton ? (
+ !this.props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
<div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
- {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
- StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier),
- "none", startMarker, endMarker, markerScale * Math.min(screenSpaceCenterlineStrokeWidth, screenInkWidth[0] / screenSpaceCenterlineStrokeWidth), StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
- <InkControlPtHandles
- inkView={this.props.docViewPath().lastElement()}
- inkDoc={inkDoc}
- inkCtrlPoints={inkData}
- screenCtrlPoints={screenHdlPts}
- nearestScreenPt={this.nearestScreenPt}
- screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
- <InkTangentHandles
- inkView={this.props.docViewPath().lastElement()}
- inkDoc={inkDoc}
- screenCtrlPoints={screenHdlPts}
- screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
- ScreenToLocalTransform={this.screenToLocal} />
- </div>;
- }
+ <InkEndPtHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} startPt={screenPts[0]} endPt={screenPts.lastElement()} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
+ </div>
+ )
+ ) : (
+ <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
+ {InteractionUtils.CreatePolyline(
+ screenPts,
+ 0,
+ 0,
+ Colors.MEDIUM_BLUE,
+ screenInkWidth[0],
+ screenSpaceCenterlineStrokeWidth,
+ StrCast(inkDoc.strokeLineJoin),
+ StrCast(this.layoutDoc.strokeLineCap),
+ StrCast(inkDoc.strokeBezier),
+ 'none',
+ startMarker,
+ endMarker,
+ markerScale * Math.min(screenSpaceCenterlineStrokeWidth, screenInkWidth[0] / screenSpaceCenterlineStrokeWidth),
+ StrCast(inkDoc.strokeDash),
+ 1,
+ 1,
+ '',
+ 'none',
+ 1.0,
+ false
+ )}
+ <InkControlPtHandles
+ inkView={this.props.docViewPath().lastElement()}
+ inkDoc={inkDoc}
+ inkCtrlPoints={inkData}
+ screenCtrlPoints={screenHdlPts}
+ nearestScreenPt={this.nearestScreenPt}
+ screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
+ />
+ <InkTangentHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} screenCtrlPoints={screenHdlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
+ </div>
+ );
+ };
_subContentView: DocComponentView | undefined;
- setSubContentView = (doc: DocComponentView) => this._subContentView = doc;
+ setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
render() {
TraceMobx();
const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
@@ -315,105 +353,181 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1);
const closed = InkingStroke.IsClosed(inkData);
- const fillColor = StrCast(this.layoutDoc.fillColor, "transparent");
- const strokeColor = !closed && fillColor && fillColor !== "transparent" ? fillColor : StrCast(this.layoutDoc.color);
+ const fillColor = StrCast(this.layoutDoc.fillColor, 'transparent');
+ const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color);
// Visually renders the polygonal line made by the user.
- const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth,
- StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
- StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
- markerScale, StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false);
+ const inkLine = InteractionUtils.CreatePolyline(
+ inkData,
+ inkLeft,
+ inkTop,
+ strokeColor,
+ inkStrokeWidth,
+ inkStrokeWidth,
+ StrCast(this.layoutDoc.strokeLineJoin),
+ StrCast(this.layoutDoc.strokeLineCap),
+ StrCast(this.layoutDoc.strokeBezier),
+ !closed ? 'none' : fillColor === 'transparent' ? 'none' : fillColor,
+ startMarker,
+ endMarker,
+ markerScale,
+ StrCast(this.layoutDoc.strokeDash),
+ inkScaleX,
+ inkScaleY,
+ '',
+ 'none',
+ 1.0,
+ false
+ );
const highlightIndex = /*BoolCast(this.props.Document.isLinkButton) && */ Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
- const highlightColor = !highlightIndex ?
- StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") :
- ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
+ const highlightColor = !highlightIndex
+ ? StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent')
+ : ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'yellow', 'magenta', 'cyan', 'orange'][highlightIndex];
// Invisible polygonal line that enables the ink to be selected by the user.
- const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
- inkStrokeWidth, fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? closed ? 0 : (highlightIndex + 2) : 0),
- StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
- StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" || suppressFill ? "none" : fillColor, startMarker, endMarker,
- markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? "none" : "visiblepainted"), 0.0,
- false, downHdlr);
- const fsize = +(StrCast(this.props.Document.fontSize, "12px").replace("px", ""));
- // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
+ const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) =>
+ InteractionUtils.CreatePolyline(
+ inkData,
+ inkLeft,
+ inkTop,
+ highlightColor,
+ inkStrokeWidth,
+ fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0),
+ StrCast(this.layoutDoc.strokeLineJoin),
+ StrCast(this.layoutDoc.strokeLineCap),
+ StrCast(this.layoutDoc.strokeBezier),
+ !closed ? 'none' : fillColor === 'transparent' || suppressFill ? 'none' : fillColor,
+ startMarker,
+ endMarker,
+ markerScale,
+ undefined,
+ inkScaleX,
+ inkScaleY,
+ '',
+ this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? 'none' : 'visiblepainted'),
+ 0.0,
+ false,
+ downHdlr
+ );
+ const fsize = +StrCast(this.props.Document.fontSize, '12px').replace('px', '');
+ // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
// this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429
// see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/
- const lineHeightGuess = (+getComputedStyle(document.body).lineHeight.replace("px", "")) / (+getComputedStyle(document.body).fontSize.replace("px", ""));
+ const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
const interactions = {
- onPointerLeave: action(() => this._nearestScrPt = undefined),
+ onPointerLeave: action(() => (this._nearestScrPt = undefined)),
onPointerMove: this.props.isSelected() ? this.onPointerMove : undefined,
onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
onContextMenu: () => {
const cm = ContextMenu.Instance;
- !Doc.noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
- cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
- cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
- }
+ !Doc.noviceMode && cm?.addItem({ description: 'Recognize Writing', event: this.analyzeStrokes, icon: 'paint-brush' });
+ cm?.addItem({ description: 'Toggle Mask', event: () => InkingStroke.toggleMask(this.rootDoc), icon: 'paint-brush' });
+ cm?.addItem({ description: 'Edit Points', event: action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)), icon: 'paint-brush' });
+ },
};
- return <div className="inkStroke-wrapper">
- <svg className="inkStroke"
- style={{
- transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
- mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
- cursor: this.props.isSelected() ? "default" : undefined
- }}
- {...(!closed ? interactions : {})}
- >
- {closed ? inkLine : clickableLine(this.onPointerDown)}
- {closed ? clickableLine(this.onPointerDown) : inkLine}
- </svg>
- {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? (null) :
- <div className="inkStroke-text" style={{
- color: StrCast(this.layoutDoc.textColor, "black"),
- pointerEvents: this.props.isDocumentActive?.() ? "all" : undefined,
- width: this.layoutDoc[WidthSym](),
- transform: `scale(${this.props.scaling?.() || 1})`,
- transformOrigin: "top left",
- top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.scaling?.() || 1)) / 2
- }}>
- <FormattedTextBox
- {...OmitKeys(this.props, ['children']).omit}
- setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
- yPadding={10}
- xPadding={10}
- fieldKey={"text"}
- fontSize={fsize}
- dontRegisterView={true}
- noSidebar={true}
- dontScale={true}
- isContentActive={this.isContentActive}
- />
- </div>
- }
- {!closed ? null : <svg className="inkStroke"
- style={{
- transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
- mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
- cursor: this.props.isSelected() ? "default" : undefined, position: "absolute"
- }}
- {...interactions}
- >
- {clickableLine(this.onPointerDown, true)}
- </svg>}
- </div>;
+ return (
+ <div className="inkStroke-wrapper">
+ <svg
+ className="inkStroke"
+ style={{
+ transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+ cursor: this.props.isSelected() ? 'default' : undefined,
+ }}
+ {...(!closed ? interactions : {})}>
+ {closed ? inkLine : clickableLine(this.onPointerDown)}
+ {closed ? clickableLine(this.onPointerDown) : inkLine}
+ </svg>
+ {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? null : (
+ <div
+ className="inkStroke-text"
+ style={{
+ color: StrCast(this.layoutDoc.textColor, 'black'),
+ pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
+ width: this.layoutDoc[WidthSym](),
+ transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
+ transformOrigin: 'top left',
+ top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
+ }}>
+ <FormattedTextBox
+ {...OmitKeys(this.props, ['children']).omit}
+ setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
+ yPadding={10}
+ xPadding={10}
+ fieldKey={'text'}
+ fontSize={fsize}
+ dontRegisterView={true}
+ noSidebar={true}
+ dontScale={true}
+ isContentActive={this.isContentActive}
+ />
+ </div>
+ )}
+ {!closed ? null : (
+ <svg
+ className="inkStroke"
+ style={{
+ transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+ cursor: this.props.isSelected() ? 'default' : undefined,
+ position: 'absolute',
+ }}
+ {...interactions}>
+ {clickableLine(this.onPointerDown, true)}
+ </svg>
+ )}
+ </div>
+ );
}
}
-
-export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
-export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
-export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
-export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
-export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
-export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
-export function SetActiveArrowScale(value: number) { ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); }
-export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
-export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
-export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
-export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
-export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
-export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
-export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); }
-export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
-export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); }
-export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
+export function SetActiveInkWidth(width: string): void {
+ !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
+}
+export function SetActiveBezierApprox(bezier: string): void {
+ ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
+}
+export function SetActiveInkColor(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
+}
+export function SetActiveFillColor(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
+}
+export function SetActiveArrowStart(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
+}
+export function SetActiveArrowEnd(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
+}
+export function SetActiveArrowScale(value: number) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
+}
+export function SetActiveDash(dash: string): void {
+ !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
+}
+export function ActiveInkPen(): Doc {
+ return Doc.UserDoc();
+}
+export function ActiveInkColor(): string {
+ return StrCast(ActiveInkPen()?.activeInkColor, 'black');
+}
+export function ActiveFillColor(): string {
+ return StrCast(ActiveInkPen()?.activeFillColor, '');
+}
+export function ActiveArrowStart(): string {
+ return StrCast(ActiveInkPen()?.activeArrowStart, '');
+}
+export function ActiveArrowEnd(): string {
+ return StrCast(ActiveInkPen()?.activeArrowEnd, '');
+}
+export function ActiveArrowScale(): number {
+ return NumCast(ActiveInkPen()?.activeArrowScale, 1);
+}
+export function ActiveDash(): string {
+ return StrCast(ActiveInkPen()?.activeDash, '0');
+}
+export function ActiveInkWidth(): number {
+ return Number(ActiveInkPen()?.activeInkWidth);
+}
+export function ActiveInkBezierApprox(): string {
+ return StrCast(ActiveInkPen()?.activeInkBezier);
+}