feat: add eraser tool trail (#7511)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Are 2024-01-11 17:10:15 +01:00 committed by GitHub
parent 872973f145
commit 86cfeb714c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 482 additions and 356 deletions

View file

@ -384,8 +384,7 @@ import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
import { StaticCanvas, InteractiveCanvas } from "./canvases";
import { Renderer } from "../scene/Renderer";
import { ShapeCache } from "../scene/ShapeCache";
import { LaserToolOverlay } from "./LaserTool/LaserTool";
import { LaserPathManager } from "./LaserTool/LaserPathManager";
import { SVGLayer } from "./SVGLayer";
import {
setEraserCursor,
setCursor,
@ -401,6 +400,10 @@ import { ElementCanvasButton } from "./MagicButton";
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
import { EditorLocalStorage } from "../data/EditorLocalStorage";
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 { getRenderOpacity } from "../renderer/renderElement";
@ -537,7 +540,29 @@ class App extends React.Component<AppProps, AppState> {
lastPointerMoveEvent: PointerEvent | null = null;
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<
[
@ -1471,7 +1496,9 @@ class App extends React.Component<AppProps, AppState> {
<div className="excalidraw-textEditorContainer" />
<div className="excalidraw-contextMenuContainer" />
<div className="excalidraw-eye-dropper-container" />
<LaserToolOverlay manager={this.laserPathManager} />
<SVGLayer
trails={[this.laserTrails, this.eraserTrail]}
/>
{selectedElements.length === 1 &&
this.state.showHyperlinkPopup && (
<Hyperlink
@ -2394,7 +2421,8 @@ class App extends React.Component<AppProps, AppState> {
this.removeEventListeners();
this.scene.destroy();
this.library.destroy();
this.laserPathManager.destroy();
this.laserTrails.stop();
this.eraserTrail.stop();
this.onChangeEmitter.clear();
ShapeCache.destroy();
SnapCache.destroy();
@ -2619,6 +2647,10 @@ class App extends React.Component<AppProps, AppState> {
this.updateLanguage();
}
if (isEraserActive(prevState) && !isEraserActive(this.state)) {
this.eraserTrail.endPath();
}
if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
this.setState({ viewModeEnabled: !!this.props.viewModeEnabled });
}
@ -5070,6 +5102,8 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState: PointerDownState,
scenePointer: { x: number; y: number },
) => {
this.eraserTrail.addPointToPath(scenePointer.x, scenePointer.y);
let didChange = false;
const processElements = (elements: ExcalidrawElement[]) => {
@ -5500,7 +5534,7 @@ class App extends React.Component<AppProps, AppState> {
this.state.activeTool.type,
);
} else if (this.state.activeTool.type === "laser") {
this.laserPathManager.startPath(
this.laserTrails.startPath(
pointerDownState.lastCoords.x,
pointerDownState.lastCoords.y,
);
@ -5521,6 +5555,13 @@ class App extends React.Component<AppProps, AppState> {
event,
);
if (this.state.activeTool.type === "eraser") {
this.eraserTrail.startPath(
pointerDownState.lastCoords.x,
pointerDownState.lastCoords.y,
);
}
const onPointerMove =
this.onPointerMoveFromPointerDownHandler(pointerDownState);
@ -6784,7 +6825,7 @@ class App extends React.Component<AppProps, AppState> {
}
if (this.state.activeTool.type === "laser") {
this.laserPathManager.addPointToPath(pointerCoords.x, pointerCoords.y);
this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
}
const [gridX, gridY] = getGridPoint(
@ -7793,6 +7834,8 @@ class App extends React.Component<AppProps, AppState> {
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
if (isEraserActive(this.state) && pointerStart && pointerEnd) {
this.eraserTrail.endPath();
const draggedDistance = distance2d(
pointerStart.clientX,
pointerStart.clientY,
@ -8041,7 +8084,7 @@ class App extends React.Component<AppProps, AppState> {
}
if (activeTool.type === "laser") {
this.laserPathManager.endPath();
this.laserTrails.endPath();
return;
}