|
@@ -384,8 +384,7 @@ import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
|
import { StaticCanvas, InteractiveCanvas } from "./canvases";
|
|
import { StaticCanvas, InteractiveCanvas } from "./canvases";
|
|
import { Renderer } from "../scene/Renderer";
|
|
import { Renderer } from "../scene/Renderer";
|
|
import { ShapeCache } from "../scene/ShapeCache";
|
|
import { ShapeCache } from "../scene/ShapeCache";
|
|
-import { LaserToolOverlay } from "./LaserTool/LaserTool";
|
|
|
|
-import { LaserPathManager } from "./LaserTool/LaserPathManager";
|
|
|
|
|
|
+import { SVGLayer } from "./SVGLayer";
|
|
import {
|
|
import {
|
|
setEraserCursor,
|
|
setEraserCursor,
|
|
setCursor,
|
|
setCursor,
|
|
@@ -401,6 +400,10 @@ import { ElementCanvasButton } from "./MagicButton";
|
|
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
|
|
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
|
|
import { EditorLocalStorage } from "../data/EditorLocalStorage";
|
|
import { EditorLocalStorage } from "../data/EditorLocalStorage";
|
|
import FollowMode from "./FollowMode/FollowMode";
|
|
import FollowMode from "./FollowMode/FollowMode";
|
|
|
|
+
|
|
|
|
+import { AnimationFrameHandler } from "../animation-frame-handler";
|
|
|
|
+import { AnimatedTrail } from "../animated-trail";
|
|
|
|
+import { LaserTrails } from "../laser-trails";
|
|
import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
|
|
import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
|
|
import { getRenderOpacity } from "../renderer/renderElement";
|
|
import { getRenderOpacity } from "../renderer/renderElement";
|
|
|
|
|
|
@@ -537,7 +540,29 @@ class App extends React.Component<AppProps, AppState> {
|
|
lastPointerMoveEvent: PointerEvent | null = null;
|
|
lastPointerMoveEvent: PointerEvent | null = null;
|
|
lastViewportPosition = { x: 0, y: 0 };
|
|
lastViewportPosition = { x: 0, y: 0 };
|
|
|
|
|
|
- laserPathManager: LaserPathManager = new LaserPathManager(this);
|
|
|
|
|
|
+ animationFrameHandler = new AnimationFrameHandler();
|
|
|
|
+
|
|
|
|
+ laserTrails = new LaserTrails(this.animationFrameHandler, this);
|
|
|
|
+ eraserTrail = new AnimatedTrail(this.animationFrameHandler, this, {
|
|
|
|
+ streamline: 0.2,
|
|
|
|
+ size: 5,
|
|
|
|
+ keepHead: true,
|
|
|
|
+ sizeMapping: (c) => {
|
|
|
|
+ const DECAY_TIME = 200;
|
|
|
|
+ const DECAY_LENGTH = 10;
|
|
|
|
+ const t = Math.max(0, 1 - (performance.now() - c.pressure) / DECAY_TIME);
|
|
|
|
+ const l =
|
|
|
|
+ (DECAY_LENGTH -
|
|
|
|
+ Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) /
|
|
|
|
+ DECAY_LENGTH;
|
|
|
|
+
|
|
|
|
+ return Math.min(easeOut(l), easeOut(t));
|
|
|
|
+ },
|
|
|
|
+ fill: () =>
|
|
|
|
+ this.state.theme === THEME.LIGHT
|
|
|
|
+ ? "rgba(0, 0, 0, 0.2)"
|
|
|
|
+ : "rgba(255, 255, 255, 0.2)",
|
|
|
|
+ });
|
|
|
|
|
|
onChangeEmitter = new Emitter<
|
|
onChangeEmitter = new Emitter<
|
|
[
|
|
[
|
|
@@ -1471,7 +1496,9 @@ class App extends React.Component<AppProps, AppState> {
|
|
<div className="excalidraw-textEditorContainer" />
|
|
<div className="excalidraw-textEditorContainer" />
|
|
<div className="excalidraw-contextMenuContainer" />
|
|
<div className="excalidraw-contextMenuContainer" />
|
|
<div className="excalidraw-eye-dropper-container" />
|
|
<div className="excalidraw-eye-dropper-container" />
|
|
- <LaserToolOverlay manager={this.laserPathManager} />
|
|
|
|
|
|
+ <SVGLayer
|
|
|
|
+ trails={[this.laserTrails, this.eraserTrail]}
|
|
|
|
+ />
|
|
{selectedElements.length === 1 &&
|
|
{selectedElements.length === 1 &&
|
|
this.state.showHyperlinkPopup && (
|
|
this.state.showHyperlinkPopup && (
|
|
<Hyperlink
|
|
<Hyperlink
|
|
@@ -2394,7 +2421,8 @@ class App extends React.Component<AppProps, AppState> {
|
|
this.removeEventListeners();
|
|
this.removeEventListeners();
|
|
this.scene.destroy();
|
|
this.scene.destroy();
|
|
this.library.destroy();
|
|
this.library.destroy();
|
|
- this.laserPathManager.destroy();
|
|
|
|
|
|
+ this.laserTrails.stop();
|
|
|
|
+ this.eraserTrail.stop();
|
|
this.onChangeEmitter.clear();
|
|
this.onChangeEmitter.clear();
|
|
ShapeCache.destroy();
|
|
ShapeCache.destroy();
|
|
SnapCache.destroy();
|
|
SnapCache.destroy();
|
|
@@ -2619,6 +2647,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
this.updateLanguage();
|
|
this.updateLanguage();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (isEraserActive(prevState) && !isEraserActive(this.state)) {
|
|
|
|
+ this.eraserTrail.endPath();
|
|
|
|
+ }
|
|
|
|
+
|
|
if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
|
|
if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
|
|
this.setState({ viewModeEnabled: !!this.props.viewModeEnabled });
|
|
this.setState({ viewModeEnabled: !!this.props.viewModeEnabled });
|
|
}
|
|
}
|
|
@@ -5070,6 +5102,8 @@ class App extends React.Component<AppProps, AppState> {
|
|
pointerDownState: PointerDownState,
|
|
pointerDownState: PointerDownState,
|
|
scenePointer: { x: number; y: number },
|
|
scenePointer: { x: number; y: number },
|
|
) => {
|
|
) => {
|
|
|
|
+ this.eraserTrail.addPointToPath(scenePointer.x, scenePointer.y);
|
|
|
|
+
|
|
let didChange = false;
|
|
let didChange = false;
|
|
|
|
|
|
const processElements = (elements: ExcalidrawElement[]) => {
|
|
const processElements = (elements: ExcalidrawElement[]) => {
|
|
@@ -5500,7 +5534,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
this.state.activeTool.type,
|
|
this.state.activeTool.type,
|
|
);
|
|
);
|
|
} else if (this.state.activeTool.type === "laser") {
|
|
} else if (this.state.activeTool.type === "laser") {
|
|
- this.laserPathManager.startPath(
|
|
|
|
|
|
+ this.laserTrails.startPath(
|
|
pointerDownState.lastCoords.x,
|
|
pointerDownState.lastCoords.x,
|
|
pointerDownState.lastCoords.y,
|
|
pointerDownState.lastCoords.y,
|
|
);
|
|
);
|
|
@@ -5521,6 +5555,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
event,
|
|
event,
|
|
);
|
|
);
|
|
|
|
|
|
|
|
+ if (this.state.activeTool.type === "eraser") {
|
|
|
|
+ this.eraserTrail.startPath(
|
|
|
|
+ pointerDownState.lastCoords.x,
|
|
|
|
+ pointerDownState.lastCoords.y,
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
const onPointerMove =
|
|
const onPointerMove =
|
|
this.onPointerMoveFromPointerDownHandler(pointerDownState);
|
|
this.onPointerMoveFromPointerDownHandler(pointerDownState);
|
|
|
|
|
|
@@ -6784,7 +6825,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
}
|
|
}
|
|
|
|
|
|
if (this.state.activeTool.type === "laser") {
|
|
if (this.state.activeTool.type === "laser") {
|
|
- this.laserPathManager.addPointToPath(pointerCoords.x, pointerCoords.y);
|
|
|
|
|
|
+ this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
|
|
}
|
|
}
|
|
|
|
|
|
const [gridX, gridY] = getGridPoint(
|
|
const [gridX, gridY] = getGridPoint(
|
|
@@ -7793,6 +7834,8 @@ class App extends React.Component<AppProps, AppState> {
|
|
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
|
|
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
|
|
|
|
|
|
if (isEraserActive(this.state) && pointerStart && pointerEnd) {
|
|
if (isEraserActive(this.state) && pointerStart && pointerEnd) {
|
|
|
|
+ this.eraserTrail.endPath();
|
|
|
|
+
|
|
const draggedDistance = distance2d(
|
|
const draggedDistance = distance2d(
|
|
pointerStart.clientX,
|
|
pointerStart.clientX,
|
|
pointerStart.clientY,
|
|
pointerStart.clientY,
|
|
@@ -8041,7 +8084,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
}
|
|
}
|
|
|
|
|
|
if (activeTool.type === "laser") {
|
|
if (activeTool.type === "laser") {
|
|
- this.laserPathManager.endPath();
|
|
|
|
|
|
+ this.laserTrails.endPath();
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|