diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index fd9156c6..0efed036 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -185,11 +185,13 @@ export const ShapesSwitcher = ({ elementType, setAppState, onImageAction, + appState, }: { canvas: HTMLCanvasElement | null; elementType: AppState["elementType"]; setAppState: React.Component["setState"]; onImageAction: (data: { pointerType: PointerType | null }) => void; + appState: AppState; }) => ( <> {SHAPES.map(({ value, icon, key }, index) => { @@ -217,7 +219,7 @@ export const ShapesSwitcher = ({ multiElement: null, selectedElementIds: {}, }); - setCursorForShape(canvas, value); + setCursorForShape(canvas, { ...appState, elementType: value }); if (value === "image") { onImageAction({ pointerType }); } diff --git a/src/components/App.tsx b/src/components/App.tsx index fa3682f7..417969f0 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -214,6 +214,7 @@ import { withBatchedUpdates, wrapEvent, withBatchedUpdatesThrottled, + setEraserCursor, } from "../utils"; import ContextMenu, { ContextMenuOption } from "./ContextMenu"; import LayerUI from "./LayerUI"; @@ -1051,6 +1052,9 @@ class App extends React.Component { ) { this.setState({ elementType: "selection" }); } + if (prevState.theme !== this.state.theme) { + setEraserCursor(this.canvas, this.state.theme); + } // Hide hyperlink popup if shown when element type is not selection if ( prevState.elementType === "selection" && @@ -1873,7 +1877,7 @@ class App extends React.Component { } else if (this.state.elementType === "selection") { resetCursor(this.canvas); } else { - setCursorForShape(this.canvas, this.state.elementType); + setCursorForShape(this.canvas, this.state); this.setState({ selectedElementIds: {}, selectedGroupIds: {}, @@ -1899,7 +1903,7 @@ class App extends React.Component { private selectShapeTool(elementType: AppState["elementType"]) { if (!isHoldingSpace) { - setCursorForShape(this.canvas, elementType); + setCursorForShape(this.canvas, this.state); } if (isToolIcon(document.activeElement)) { this.focusContainer(); @@ -2043,7 +2047,7 @@ class App extends React.Component { editingElement: null, }); if (this.state.elementLocked) { - setCursorForShape(this.canvas, this.state.elementType); + setCursorForShape(this.canvas, this.state); } this.focusContainer(); @@ -2525,7 +2529,7 @@ class App extends React.Component { if (isOverScrollBar) { resetCursor(this.canvas); } else { - setCursorForShape(this.canvas, this.state.elementType); + setCursorForShape(this.canvas, this.state); } } @@ -2575,7 +2579,7 @@ class App extends React.Component { const { points, lastCommittedPoint } = multiElement; const lastPoint = points[points.length - 1]; - setCursorForShape(this.canvas, this.state.elementType); + setCursorForShape(this.canvas, this.state); if (lastPoint === lastCommittedPoint) { // if we haven't yet created a temp point and we're beyond commit-zone @@ -2689,7 +2693,9 @@ class App extends React.Component { scenePointer, hitElement, ); - + if (isEraserActive(this.state)) { + return; + } if ( this.hitLinkElement && !this.state.selectedElementIds[this.hitLinkElement.id] @@ -2706,8 +2712,6 @@ class App extends React.Component { !this.state.showHyperlinkPopup ) { this.setState({ showHyperlinkPopup: "info" }); - } else if (isEraserActive(this.state)) { - setCursor(this.canvas, CURSOR_TYPE.AUTO); } else if (this.state.elementType === "text") { setCursor( this.canvas, @@ -2998,8 +3002,6 @@ class App extends React.Component { hitElement, ); } - if (isEraserActive(this.state)) { - } if ( this.hitLinkElement && !this.state.selectedElementIds[this.hitLinkElement.id] @@ -3128,7 +3130,7 @@ class App extends React.Component { if (this.state.viewModeEnabled) { setCursor(this.canvas, CURSOR_TYPE.GRAB); } else { - setCursorForShape(this.canvas, this.state.elementType); + setCursorForShape(this.canvas, this.state); } } this.setState({ @@ -3253,7 +3255,7 @@ class App extends React.Component { const onPointerUp = withBatchedUpdates(() => { isDraggingScrollBar = false; - setCursorForShape(this.canvas, this.state.elementType); + setCursorForShape(this.canvas, this.state); lastPointerUp = null; this.setState({ cursorButton: "up", diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 97190b43..822693fd 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -343,6 +343,7 @@ const LayerUI = ({ {heading} { } }; +let eraserCanvasCache: any; +let previewDataURL: string; +export const setEraserCursor = ( + canvas: HTMLCanvasElement | null, + theme: AppState["theme"], +) => { + const cursorImageSizePx = 20; + + const drawCanvas = () => { + const isDarkTheme = theme === THEME.DARK; + eraserCanvasCache = document.createElement("canvas"); + eraserCanvasCache.theme = theme; + eraserCanvasCache.height = cursorImageSizePx; + eraserCanvasCache.width = cursorImageSizePx; + const context = eraserCanvasCache.getContext("2d")!; + context.lineWidth = 1; + context.beginPath(); + context.arc( + eraserCanvasCache.width / 2, + eraserCanvasCache.height / 2, + 5, + 0, + 2 * Math.PI, + ); + context.fillStyle = isDarkTheme ? oc.black : oc.white; + context.fill(); + context.strokeStyle = isDarkTheme ? oc.white : oc.black; + context.stroke(); + previewDataURL = eraserCanvasCache.toDataURL(MIME_TYPES.svg) as DataURL; + }; + if (!eraserCanvasCache || eraserCanvasCache.theme !== theme) { + drawCanvas(); + } + + setCursor( + canvas, + `url(${previewDataURL}) ${cursorImageSizePx / 2} ${ + cursorImageSizePx / 2 + }, auto`, + ); +}; + export const setCursorForShape = ( canvas: HTMLCanvasElement | null, - shape: string, + appState: AppState, ) => { if (!canvas) { return; } - if (shape === "selection") { + if (appState.elementType === "selection") { resetCursor(canvas); - } else if (shape === "eraser") { - resetCursor(canvas); - + } else if (appState.elementType === "eraser") { + setEraserCursor(canvas, appState.theme); // do nothing if image tool is selected which suggests there's // a image-preview set as the cursor - } else if (shape !== "image") { + } else if (appState.elementType !== "image") { canvas.style.cursor = CURSOR_TYPE.CROSSHAIR; } };