diff --git a/src/actions/actionCanvas.tsx b/src/actions/actionCanvas.tsx index ff937d98..81864ca6 100644 --- a/src/actions/actionCanvas.tsx +++ b/src/actions/actionCanvas.tsx @@ -58,6 +58,8 @@ export const actionClearCanvas = register({ files: {}, theme: appState.theme, elementLocked: appState.elementLocked, + penMode: appState.penMode, + penDetected: appState.penDetected, exportBackground: appState.exportBackground, exportEmbedScene: appState.exportEmbedScene, gridSize: appState.gridSize, diff --git a/src/appState.ts b/src/appState.ts index d226b808..3049febf 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -43,6 +43,8 @@ export const getDefaultAppState = (): Omit< editingLinearElement: null, elementLocked: false, elementType: "selection", + penMode: false, + penDetected: false, errorMessage: null, exportBackground: true, exportScale: defaultExportScale, @@ -129,6 +131,8 @@ const APP_STATE_STORAGE_CONF = (< editingLinearElement: { browser: false, export: false, server: false }, elementLocked: { browser: true, export: false, server: false }, elementType: { browser: true, export: false, server: false }, + penMode: { browser: false, export: false, server: false }, + penDetected: { browser: false, export: false, server: false }, errorMessage: { browser: false, export: false, server: false }, exportBackground: { browser: true, export: false, server: false }, exportEmbedScene: { browser: true, export: false, server: false }, diff --git a/src/components/App.tsx b/src/components/App.tsx index 64fdea9c..6c055c10 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -467,6 +467,7 @@ class App extends React.Component { elements={this.scene.getElements()} onCollabButtonClick={onCollabButtonClick} onLockToggle={this.toggleLock} + onPenModeToggle={this.togglePenMode} onInsertElements={(elements) => this.addElementsFromPasteOrLibrary({ elements, @@ -1501,6 +1502,14 @@ class App extends React.Component { }); }; + togglePenMode = () => { + this.setState((prevState) => { + return { + penMode: !prevState.penMode, + }; + }); + }; + toggleZenMode = () => { this.actionManager.executeAction(actionToggleZenMode); }; @@ -2324,7 +2333,10 @@ class App extends React.Component { gesture.lastCenter = center; const distance = getDistance(Array.from(gesture.pointers.values())); - const scaleFactor = distance / gesture.initialDistance; + const scaleFactor = + this.state.elementType === "freedraw" && this.state.penMode + ? 1 + : distance / gesture.initialDistance; const nextZoom = scaleFactor ? getNormalizedZoom(initialScale * scaleFactor) @@ -2586,6 +2598,17 @@ class App extends React.Component { this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event); this.maybeCleanupAfterMissingPointerUp(event); + //fires only once, if pen is detected, penMode is enabled + //the user can disable this by toggling the penMode button + if (!this.state.penDetected && event.pointerType === "pen") { + this.setState((prevState) => { + return { + penMode: true, + penDetected: true, + }; + }); + } + if (isPanning) { return; } @@ -2630,6 +2653,17 @@ class App extends React.Component { return; } + const allowOnPointerDown = + !this.state.penMode || + event.pointerType !== "touch" || + this.state.elementType === "selection" || + this.state.elementType === "text" || + this.state.elementType === "image"; + + if (!allowOnPointerDown) { + return; + } + if (this.state.elementType === "text") { this.handleTextOnPointerDown(event, pointerDownState); return; diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index f97030ef..5aca7af4 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -36,6 +36,7 @@ import { LibraryMenu } from "./LibraryMenu"; import "./LayerUI.scss"; import "./Toolbar.scss"; +import { PenModeButton } from "./PenModeButton"; interface LayerUIProps { actionManager: ActionManager; @@ -46,6 +47,7 @@ interface LayerUIProps { elements: readonly NonDeletedExcalidrawElement[]; onCollabButtonClick?: () => void; onLockToggle: () => void; + onPenModeToggle: () => void; onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void; zenModeEnabled: boolean; showExitZenModeBtn: boolean; @@ -76,6 +78,7 @@ const LayerUI = ({ elements, onCollabButtonClick, onLockToggle, + onPenModeToggle, onInsertElements, zenModeEnabled, showExitZenModeBtn, @@ -313,6 +316,13 @@ const LayerUI = ({ "zen-mode": zenModeEnabled, })} > + void; onLockToggle: () => void; + onPenModeToggle: () => void; canvas: HTMLCanvasElement | null; isCollaborating: boolean; renderCustomFooter?: (isMobile: boolean, appState: AppState) => JSX.Element; @@ -50,6 +52,7 @@ export const MobileMenu = ({ setAppState, onCollabButtonClick, onLockToggle, + onPenModeToggle, canvas, isCollaborating, renderCustomFooter, @@ -92,6 +95,13 @@ export const MobileMenu = ({ setAppState={setAppState} isMobile /> + {libraryMenu} diff --git a/src/components/PenModeButton.tsx b/src/components/PenModeButton.tsx new file mode 100644 index 00000000..807c36c4 --- /dev/null +++ b/src/components/PenModeButton.tsx @@ -0,0 +1,91 @@ +import "./ToolIcon.scss"; + +import clsx from "clsx"; +import { ToolButtonSize } from "./ToolButton"; + +type PenModeIconProps = { + title?: string; + name?: string; + checked: boolean; + onChange?(): void; + zenModeEnabled?: boolean; + isMobile?: boolean; + penDetected: boolean; +}; + +const DEFAULT_SIZE: ToolButtonSize = "medium"; + +const ICONS = { + CHECKED: ( + + + + + + + ), + UNCHECKED: ( + + + + + + + ), +}; + +export const PenModeButton = (props: PenModeIconProps) => { + if (!props.penDetected) { + if (props.isMobile) { + return null; + } + return ( +