aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2021-12-10 13:36:12 -0500
committerbobzel <zzzman@gmail.com>2021-12-10 13:36:12 -0500
commite54c1ef16b4ce0a324fac3747defdc6501834de5 (patch)
treee956e5bbe07e53a36e5ead3d637e6f7c2b01671b /src/client/views/collections/collectionFreeForm
parent8176b94970b86bd3c1669130f6fef2ccd70d0b84 (diff)
parentf8ce34c8ed42646691d1e392effe79bc27daf810 (diff)
Merge branch 'master' into ink_v1
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx89
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx239
2 files changed, 188 insertions, 140 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index bb4cae8c6..c35bb3581 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -5,13 +5,14 @@ import { Id } from "../../../../fields/FieldSymbols";
import { List } from "../../../../fields/List";
import { NumCast } from "../../../../fields/Types";
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
import { LinkManager } from "../../../util/LinkManager";
-import { ColorScheme } from "../../../util/SettingsManager";
+import { SelectionManager } from "../../../util/SelectionManager";
import { SnappingManager } from "../../../util/SnappingManager";
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinkView.scss";
import React = require("react");
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
+import { Colors } from "../../global/globalEnums";
export interface CollectionFreeFormLinkViewProps {
@@ -139,6 +140,39 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
return left; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
}
+ @action
+ toggleProperties = () => {
+ if (CurrentUserUtils.propertiesWidth > 0) {
+ CurrentUserUtils.propertiesWidth = 0;
+ } else {
+ CurrentUserUtils.propertiesWidth = 250;
+ }
+ }
+
+ onClickLine = () => {
+ SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true)
+ this.toggleProperties()
+ }
+
+ // componentToHex = (c: number) => {
+ // let hex = c.toString(16);
+ // return hex.length == 1 ? "0" + hex : hex;
+ // }
+
+ // rgbToHex = (rgbString: string) => {
+ // if (rgbString != "black") {
+ // const splitString = rgbString.split(/rgb|\(|\)|,| /)
+ // let values: number[] = []
+ // splitString.forEach(elt => {
+ // if (elt) {
+ // values.push(parseInt(elt))
+ // }
+ // })
+ // return "#" + this.componentToHex(values[0]) + this.componentToHex(values[1]) + this.componentToHex(values[2]);
+ // }
+ // return "#000000"
+ // }
+
@computed.struct get renderData() {
this._start; SnappingManager.GetIsDragging();
const { A, B, LinkDocs } = this.props;
@@ -154,28 +188,44 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const atop = this.visibleY(adiv);
const btop = this.visibleY(bdiv);
if (!a.width || !b.width) return undefined;
+ const aDocBounds = (A.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
+ const bDocBounds = (B.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
+ const acentX = (a.left + a.right) / 2;
+ const acentY = (a.top + a.bottom) / 2;
+ const bcentX = (b.left + b.right) / 2;
+ const bcentY = (b.top + b.bottom) / 2;
+ const pt1Arc = ((acentX - aDocBounds.left) > 0.1 && (aDocBounds.right - acentX) > 0.1) ||
+ ((acentY - aDocBounds.top) > 0.1 && (aDocBounds.bottom - acentY) > 0.1);
+ const pt2Arc = ((bcentX - bDocBounds.left) > 0.1 && (bDocBounds.right - bcentX) > 0.1) ||
+ ((bcentY - bDocBounds.top) > 0.1 && (bDocBounds.bottom - bcentY) > 0.1);
const atop2 = this.visibleY(adiv);
const btop2 = this.visibleY(bdiv);
const aleft = this.visibleX(adiv);
const bleft = this.visibleX(bdiv);
const clipped = aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top;
- const apt = Utils.closestPtBetweenRectangles(aleft, atop, a.width, a.height, bleft, btop, b.width, b.height, a.left + a.width / 2, a.top + a.height / 2);
- const bpt = Utils.closestPtBetweenRectangles(bleft, btop, b.width, b.height, aleft, atop, a.width, a.height, apt.point.x, apt.point.y);
- const pt1 = [apt.point.x, apt.point.y];
- const pt2 = [bpt.point.x, bpt.point.y];
- const pt1vec = [pt1[0] - (aleft + a.width / 2), pt1[1] - (atop + a.height / 2)];
- const pt2vec = [pt2[0] - (bleft + b.width / 2), pt2[1] - (btop + b.height / 2)];
+ const pt1 = [aleft + a.width / 2, atop + a.height / 2];
+ const pt2 = [bleft + b.width / 2, btop + b.width / 2];
+ const pt1vec = [pt1[0] - (aDocBounds.left + aDocBounds.right) / 2, pt1[1] - (aDocBounds.top + aDocBounds.bottom) / 2];
+ const pt2vec = [pt2[0] - (bDocBounds.left + bDocBounds.right) / 2, pt2[1] - (bDocBounds.top + bDocBounds.bottom) / 2];
const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
- const pt1norm = clipped ? [0, 0] : [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
- const pt2norm = clipped ? [0, 0] : [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
+ const pt1norm = clipped ? [0, 0] : !pt1Arc ? [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen] :
+ Math.abs(acentY - aDocBounds.top) < 0.01 ||
+ Math.abs(acentY - aDocBounds.bottom) < 0.01 ? [0, (pt2[1] - pt1[1]) / 2] : [(pt2[0] - pt1[0]) / 2, 0];
+ const pt2norm = clipped ? [0, 0] : !pt2Arc ? [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen] :
+ Math.abs(bcentY - bDocBounds.top) < 0.01 ||
+ Math.abs(bcentY - bDocBounds.bottom) < 0.01 ? [0, (pt1[1] - pt2[1]) / 2] : [(pt1[0] - pt2[0]) / 2, 0];
+ const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1;
+ const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
+ const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
+ const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen];
const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc);
const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc);
const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX);
const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY);
- return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 };
+ return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13], pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13] };
}
render() {
@@ -193,14 +243,27 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
//access stroke color using index of the relationship in the color list (default black)
const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? "black" : linkColorList[currRelationshipIndex];
+ // const hexStroke = this.rgbToHex(stroke)
//calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has)
//thickness varies linearly from 3px to 12px for increasing link count
const strokeWidth = linkSize === -1 ? "3px" : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + "px";
+ if (this.props.LinkDocs[0].displayArrow == undefined) {
+ this.props.LinkDocs[0].displayArrow = false;
+ }
+
return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, /*strokeDasharray: "2 2",*/ stroke, strokeWidth }}
- d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
+ <defs>
+ <marker id="arrowhead" markerWidth="4" markerHeight="3"
+ refX="0" refY="1.5" orient="auto">
+ <polygon points="0 0, 3 1.5, 0 3" fill={Colors.DARK_GRAY} />
+ </marker>
+ </defs>
+ <path className="collectionfreeformlinkview-linkLine" style={{ pointerEvents: "all", opacity: this._opacity, stroke: SelectionManager.SelectedSchemaDoc() === this.props.LinkDocs[0] ? Colors.MEDIUM_BLUE : stroke, strokeWidth }}
+ onClick={this.onClickLine}
+ d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
+ markerEnd={this.props.LinkDocs[0].displayArrow ? "url(#arrowhead)" : ""} />
{textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
{Field.toString(this.props.LinkDocs[0].description as any as Field)}
</text>}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bb1d560d6..aeda71d01 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,11 +1,12 @@
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap } from "mobx";
+import { Bezier } from "bezier-js";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { DateField } from "../../../../fields/DateField";
import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
import { Id } from "../../../../fields/FieldSymbols";
-import { InkData, InkField, InkTool, PointData, Intersection, Segment } from "../../../../fields/InkField";
+import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { ObjectField } from "../../../../fields/ObjectField";
import { RichTextField } from "../../../../fields/RichTextField";
@@ -27,14 +28,15 @@ import { InteractionUtils } from "../../../util/InteractionUtils";
import { LinkManager } from "../../../util/LinkManager";
import { SearchUtil } from "../../../util/SearchUtil";
import { SelectionManager } from "../../../util/SelectionManager";
+import { ColorScheme } from "../../../util/SettingsManager";
import { SnappingManager } from "../../../util/SnappingManager";
import { Transform } from "../../../util/Transform";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/global/globalCssVariables.scss";
import { Timeline } from "../../animationtimeline/Timeline";
import { ContextMenu } from "../../ContextMenu";
-import { DocumentDecorations } from "../../DocumentDecorations";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkWidth, SetActiveFillColor, SetActiveInkColor } from "../../InkingStroke";
+import { GestureOverlay } from "../../GestureOverlay";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from "../../InkingStroke";
import { LightboxView } from "../../LightboxView";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
@@ -50,10 +52,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-import { ColorScheme } from "../../../util/SettingsManager";
-import { Bezier } from "bezier-js";
-import { GestureOverlay } from "../../GestureOverlay";
-import { constants } from "perf_hooks";
export const panZoomSchema = createSchema({
_panX: "number",
@@ -115,8 +113,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@observable _pullDirection: string = "";
@observable _showAnimTimeline = false;
@observable _clusterSets: (Doc[])[] = [];
- @observable _prevPoint: PointData = { X: -1, Y: -1 };
- @observable _currPoint: PointData = { X: -1, Y: -1 };
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeRef = React.createRef<HTMLDivElement>();
@@ -153,7 +149,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
}
@computed get cachedGetTransform(): Transform {
- return this.getTransformOverlay().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
+ return this.getContainerTransform().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
}
@action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set;
@@ -172,11 +168,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX);
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY);
zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1));
- contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ contentTransform = () => !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 ? "" : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
getTransform = () => this.cachedGetTransform.copy();
getLocalTransform = () => this.cachedGetLocalTransform.copy();
getContainerTransform = () => this.cachedGetContainerTransform.copy();
- getTransformOverlay = () => this.getContainerTransform().translate(1, 1);
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
isAnyChildContentActive = () => this.props.isAnyChildContentActive();
addLiveTextBox = (newBox: Doc) => {
@@ -228,7 +223,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (!de.embedKey && !this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
if (!super.onInternalDrop(e, de)) return false;
const refDoc = docDragData.droppedDocuments[0];
- const [xpo, ypo] = this.getTransformOverlay().transformPoint(de.x, de.y);
+ const [xpo, ypo] = this.getContainerTransform().transformPoint(de.x, de.y);
const z = NumCast(refDoc.z);
const x = (z ? xpo : xp) - docDragData.offset[0];
const y = (z ? ypo : yp) - docDragData.offset[1];
@@ -440,27 +435,29 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) ||
- ([InkTool.Pen, InkTool.Highlighter].includes(CurrentUserUtils.SelectedTool))) {
- return;
- }
- this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
+ this._downX = this._lastX = e.pageX;
+ this._downY = this._lastY = e.pageY;
if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
- // if not using a pen and in no ink mode
- if (CurrentUserUtils.SelectedTool === InkTool.None) {
- this._downX = this._lastX = e.pageX;
- this._downY = this._lastY = e.pageY;
- }
- // eraser plus anything else mode
- else {
- this._batch = UndoManager.StartBatch("collectionErase");
- this._prevPoint = { X: e.clientX, Y: e.clientY };
- e.stopPropagation();
- e.preventDefault();
+ if (!e.nativeEvent.cancelBubble &&
+ !this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
+ !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
+ !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ switch (CurrentUserUtils.SelectedTool) {
+ case InkTool.Highlighter:
+ case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
+ case InkTool.Eraser:
+ document.addEventListener("pointermove", this.onEraserMove);
+ document.addEventListener("pointerup", this.onEraserUp);
+ this._batch = UndoManager.StartBatch("collectionErase");
+ e.stopPropagation();
+ e.preventDefault();
+ break;
+ case InkTool.None:
+ this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ break;
+ }
}
}
}
@@ -601,6 +598,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
}
+ @action
+ onEraserUp = (e: PointerEvent): void => {
+ if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
+ document.removeEventListener("pointermove", this.onEraserMove);
+ document.removeEventListener("pointerup", this.onEraserUp);
+ this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc));
+ this._deleteList = [];
+ this._batch?.end();
+ }
+ }
@action
onPointerUp = (e: PointerEvent): void => {
@@ -609,12 +616,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
document.removeEventListener("pointerup", this.onPointerUp);
this.removeMoveListeners();
this.removeEndListeners();
- if (CurrentUserUtils.SelectedTool !== InkTool.None) {
- this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc));
- this._prevPoint = this._currPoint = { X: -1, Y: -1 };
- this._deleteList = [];
- this._batch?.end();
- }
}
}
@@ -639,99 +640,82 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this._lastY = e.clientY;
}
+ /**
+ * Erases strokes by intersecting them with an invisible "eraser stroke".
+ * By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
+ * and deletes the original stroke.
+ * However, if Shift is held, then no segmentation is done -- instead any intersected stroke is deleted in its entirety.
+ */
+ @action
+ onEraserMove = (e: PointerEvent) => {
+ const currPoint = { X: e.clientX, Y: e.clientY };
+ this.getEraserIntersections({ X: this._lastX, Y: this._lastY }, currPoint).forEach(intersect => {
+ if (!this._deleteList.includes(intersect.inkView)) {
+ this._deleteList.push(intersect.inkView);
+ SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || "1");
+ SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || "black");
+ // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
+ !e.shiftKey && this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
+ GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke,
+ segment.reduce((data, curve) => [...data, ...curve.points
+ .map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })
+ ], [] as PointData[])));
+ // Lower ink opacity to give the user a visual indicator of deletion.
+ intersect.inkView.layoutDoc.opacity = 0.5;
+ }
+ });
+ this._lastX = currPoint.X;
+ this._lastY = currPoint.Y;
+
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
+ }
+
@action
onPointerMove = (e: PointerEvent): void => {
- if (this.props.Document._isGroup) return; // groups don't pan when dragged -- instead let the event go through to allow the group itself to drag
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return;
- if (CurrentUserUtils.SelectedTool !== InkTool.None) {
- this._currPoint = { X: e.clientX, Y: e.clientY };
- // Erasing ink strokes if intersections occur.
- this.eraseInkStrokes(e, this.getEraserIntersections());
- }
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
if (this.props.isContentActive(true)) e.stopPropagation();
} else if (!e.cancelBubble) {
- if (CurrentUserUtils.SelectedTool === InkTool.None) {
- if (this.tryDragCluster(e, this._hitCluster)) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- }
- else this.pan(e);
+ if (this.tryDragCluster(e, this._hitCluster)) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
}
+ else this.pan(e);
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
}
}
/**
- * Iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
- * and deletes the original stroke.
- * @param eraserIntersections The intersections made by the eraser.
- */
- eraseInkStrokes = (e: PointerEvent, eraserIntersections: Intersection[]) => {
- eraserIntersections.forEach(intersect => {
- const ink = intersect.ink;
- if (ink && !this._deleteList.includes(ink)) {
- this._deleteList.push(ink);
- SetActiveInkWidth(StrCast(ink.rootDoc.strokeWidth?.toString()) || "1");
- SetActiveInkColor(StrCast(ink.rootDoc.color?.toString()) || "black");
- // Piecewise ink deletion mode if the 'Alt' is not held down.
- if (!e.altKey) {
- // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
- this.segmentInkStroke(ink, intersect.t ?? 0).forEach(segment =>
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke,
- segment.reduce((data, curve) => [...data, ...curve.points
- .map(p => ink.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })
- ], [] as PointData[])));
- }
- // Lower ink opacity to give the user a visual indicator of deletion.
- ink.layoutDoc.opacity = 0.5;
- }
- });
- }
-
- /**
* Determines if the Eraser tool has intersected with an ink stroke in the current freeform collection.
- * @returns A dictionary mapping the t-value intersection of the eraser with the corresponding ink DocumentView.
+ * @returns an array of tuples containing the intersected ink DocumentView and the t-value where it was intersected
*/
- getEraserIntersections = (): Intersection[] => {
- const intersections: Intersection[] = [];
- this.childDocs
- .filter(doc => doc.type === DocumentType.INK)
- .forEach(doc => {
- const inkView = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView);
- const inkStroke = inkView?.ComponentView as InkingStroke;
- const { inkData } = inkStroke?.inkScaledData();
+ getEraserIntersections = (lastPoint: { X: number, Y: number }, currPoint: { X: number, Y: number }) => {
+ const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) };
+ const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
+ return this.childDocs
+ .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
+ .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
+ .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
+ .filter(({ inkViewBounds }) => inkViewBounds && // bounding box of eraser segment and ink stroke overlap
+ eraserMin.X <= inkViewBounds.right && eraserMin.Y <= inkViewBounds.bottom &&
+ eraserMax.X >= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top)
+ .reduce((intersections, { inkStroke, inkView }) => {
+ const { inkData } = inkStroke.inkScaledData();
+ // Convert from screen space to ink space for the intersection.
+ const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
+ const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
for (var i = 0; i < inkData.length - 3; i += 4) {
- const array = inkData.slice(i, i + 4);
- // Converting from screen space to ink space for the intersection.
- const prevPointInkSpace = inkStroke?.ptFromScreen?.(this._prevPoint);
- const currPointInkSpace = inkStroke?.ptFromScreen?.(this._currPoint);
- if (prevPointInkSpace && currPointInkSpace) {
- const curve = new Bezier(array.map(p => ({ x: p.X, y: p.Y })));
- const intersects = curve.intersects({
- p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
- p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }
- });
- if (inkView && intersects) {
- for (const val of intersects) {
- // Casting t-value from type: (string | number) to number for comparisons.
- const t = +(Number(val) + Math.floor(i / 4)).toString(); // add start of curve segment to convert from local t value to t value along complete curve
- var unique: boolean = true;
- // Ensuring there are no duplicate intersections in the list returned.
- for (const prevIntersect of intersections) {
- if (prevIntersect.t === t) {
- unique = false;
- break;
- }
- }
- if (unique) intersections.push({ t: +t.toString(), ink: inkView, curve: curve });
- }
- }
- }
+ const intersects = Array.from(new Set(InkField.Segment(inkData, i).intersects({ // compute all unique intersections
+ p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
+ p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }
+ }) as (number | string)[])); // convert to more manageable union array type
+ // return tuples of the inkingStroke intersected, and the t value of the intersection
+ intersections.push(...intersects.map(t => ({ inkView, t: (+t) + Math.floor(i / 4) })));// convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
}
- });
- return intersections;
+ return intersections;
+ }, [] as { t: number, inkView: DocumentView }[]);
}
/**
@@ -750,23 +734,23 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
// This iterates through all segments of the curve and splits them where they intersect another curve.
// if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted)
for (var i = 0; i < inkData.length - 3; i += 4) {
- const curve = new Bezier(inkData.slice(i, i + 4).map(p => ({ x: p.X, y: p.Y })));
+ const inkSegment = InkField.Segment(inkData, i);
// Getting all t-value intersections of the current curve with all other curves.
- const tVals = this.getInkIntersections(i, ink, curve).sort();
+ const tVals = this.getInkIntersections(i, ink, inkSegment).sort();
if (tVals.length) {
tVals.forEach((t, index) => {
const docCurveTVal = t + Math.floor(i / 4);
if (excludeT < startSegmentT || excludeT > docCurveTVal) {
const localStartTVal = startSegmentT - Math.floor(i / 4);
- segment.push(curve.split(localStartTVal < 0 ? 0 : localStartTVal, t));
+ segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
segment.length && segments.push(segment);
}
// start a new segment from the intersection t value
- segment = tVals.length - 1 === index ? [curve.split(t).right] : [];
+ segment = tVals.length - 1 === index ? [inkSegment.split(t).right] : [];
startSegmentT = docCurveTVal;
});
} else {
- segment.push(curve);
+ segment.push(inkSegment);
}
}
if (excludeT < startSegmentT || excludeT > (inkData.length / 4)) {
@@ -790,7 +774,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
.filter(doc => doc.type === DocumentType.INK)
.forEach(doc => {
const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke;
- const { inkData: otherInkData } = otherInk.inkScaledData();
+ const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
for (var j = 0; j < otherCtrlPts.length - 3; j += 4) {
@@ -970,7 +954,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
else if (this.props.isContentActive(true) && !this.Document._isGroup) {
e.stopPropagation();
e.preventDefault();
- this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
}
}
@@ -1177,13 +1161,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
rootSelected={childData ? this.rootSelected : returnFalse}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={childLayout.z ? this.getTransformOverlay : this.getTransform}
+ ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
PanelWidth={childLayout[WidthSym]}
PanelHeight={childLayout[HeightSym]}
docFilters={this.childDocFilters}
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- isContentActive={returnFalse}
+ isContentActive={emptyFunction}
isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
focus={this.focusDocument}
addDocTab={this.addDocTab}
@@ -1202,7 +1186,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
bringToFront={this.bringToFront}
showTitle={this.props.childShowTitle}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
- pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" :
+ pointerEvents={this.props.isContentActive() === false ? "none" : this.backgroundActive || this.props.childPointerEvents ? "all" :
(this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents}
jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
//fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
@@ -1570,7 +1554,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
onPointerOver = (e: React.PointerEvent) => {
- (DocumentDecorations.Instance.Interacting || (this.props.layerProvider?.(this.props.Document) !== false && SnappingManager.GetIsDragging())) && this.setupDragLines(e.ctrlKey || e.shiftKey);
e.stopPropagation();
}
@@ -1643,6 +1626,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
{this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)}
<CollectionFreeFormViewPannableContents
isAnnotationOverlay={this.isAnnotationOverlay}
+ isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
transform={this.contentTransform}
zoomScaling={this.zoomScaling}
presPaths={BoolCast(this.Document.presPathView)}
@@ -1751,6 +1735,7 @@ interface CollectionFreeFormViewPannableContentsProps {
progressivize?: boolean;
presPinView?: boolean;
isAnnotationOverlay: boolean | undefined;
+ isAnnotationOverlayScrollable: boolean | undefined;
}
@observer