浏览代码

Feat (Whiteboards): Pen mode (palm rejection) (#10222)

* introduce pen mode
* add isPrimary check
Konstantinos 2 年之前
父节点
当前提交
bdba1ff86f

+ 1 - 1
src/main/frontend/components/container.css

@@ -524,7 +524,7 @@ html[data-theme='dark'] {
   }
 
   &-btn {
-    @apply fixed bottom-4 right-8;
+    @apply fixed bottom-4 right-4 sm:right-8;
 
     > .inner {
       @apply font-bold

+ 1 - 1
src/main/frontend/components/whiteboard.css

@@ -205,7 +205,7 @@ input.tl-text-input {
 
   .tl-action-bar {
     left: 0.5rem;
-    bottom: 0.5rem;
+    bottom: 0;
   }
 
   .tl-primary-tools {

+ 1 - 0
src/resources/dicts/en.edn

@@ -465,6 +465,7 @@
  :whiteboard/dashboard-card-edited "Edited "
  :whiteboard/toggle-grid "Toggle grid"
  :whiteboard/snap-to-grid "Snap to grid"
+ :whiteboard/toggle-pen-mode "Toggle pen mode"
  :flashcards/modal-welcome-title "Time to create a card!"
  :flashcards/modal-welcome-desc-1 "You can add \"#card\" to any block to turn it into a card or trigger \"/cloze\" to add some clozes."
  :flashcards/modal-welcome-desc-2 "You can "

+ 21 - 3
tldraw/apps/tldraw-logseq/src/components/ActionBar/ActionBar.tsx

@@ -41,10 +41,14 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
     app.api.toggleSnapToGrid()
   }, [app])
 
+  const togglePenMode = React.useCallback(() => {
+    app.api.togglePenMode()
+  }, [app])
+
   return (
     <div className="tl-action-bar" data-html2canvas-ignore="true">
       {!app.readOnly && (
-        <div className="tl-toolbar tl-history-bar">
+        <div className="tl-toolbar tl-history-bar mr-2 mb-2">
           <Button tooltip={t('whiteboard/undo')} onClick={undo}>
             <TablerIcon name="arrow-back-up" />
           </Button>
@@ -54,7 +58,7 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
         </div>
       )}
 
-      <div className={`tl-toolbar tl-zoom-bar ${app.readOnly ? '' : 'ml-4'}`}>
+      <div className={'tl-toolbar tl-zoom-bar mr-2 mb-2'}>
         <Button tooltip={t('whiteboard/zoom-in')} onClick={zoomIn} id="tl-zoom-in">
           <TablerIcon name="plus" />
         </Button>
@@ -65,7 +69,7 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
         <ZoomMenu />
       </div>
 
-      <div className={'tl-toolbar tl-grid-bar ml-4'}>
+      <div className={'tl-toolbar tl-grid-bar mr-2 mb-2'}>
         <ToggleInput
             tooltip={t('whiteboard/toggle-grid')}
             className="tl-button"
@@ -88,6 +92,20 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
           </ToggleInput>
         )}
       </div>
+
+      {!app.readOnly && (
+        <div className="tl-toolbar tl-pen-mode-bar mb-2">
+          <ToggleInput
+            tooltip={t('whiteboard/toggle-pen-mode')}
+            className="tl-button"
+            pressed={app.settings.penMode}
+            id="tl-toggle-pen-mode"
+            onPressedChange={togglePenMode}
+          >
+          <TablerIcon name={app.settings.penMode ? "pencil" : "pencil-off"} />
+        </ToggleInput>
+        </div>
+      )}
     </div>
   )
 })

+ 1 - 1
tldraw/apps/tldraw-logseq/src/styles.css

@@ -166,7 +166,7 @@ html[data-theme='light'] {
 }
 
 .tl-action-bar {
-  @apply absolute bottom-0 flex items-center border-0 left-10 bottom-10;
+  @apply absolute flex items-center border-0 left-10 bottom-8 flex-wrap-reverse pr-12;
 
   z-index: 100000;
   user-select: none;

+ 7 - 0
tldraw/packages/core/src/lib/TLApi/TLApi.ts

@@ -178,6 +178,13 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
     return this
   }
 
+
+  togglePenMode = (): this => {
+    const { settings } = this.app
+    settings.update({ penMode: !settings.penMode })
+    return this
+  }
+
   setColor = (color: string): this => {
     const { settings } = this.app
 

+ 2 - 0
tldraw/packages/core/src/lib/TLSettings.ts

@@ -4,6 +4,7 @@ import { observable, makeObservable, action } from 'mobx'
 export interface TLSettingsProps {
   mode: 'light' | 'dark'
   showGrid: boolean
+  penMode: boolean
   snapToGrid: boolean
   color: string
   scaleLevel: string
@@ -17,6 +18,7 @@ export class TLSettings implements TLSettingsProps {
   @observable mode: 'dark' | 'light' = 'light'
   @observable showGrid = true
   @observable snapToGrid = true
+  @observable penMode = false
   @observable scaleLevel = 'md'
   @observable color = ''
 

+ 1 - 0
tldraw/packages/react/src/components/AppCanvas.tsx

@@ -27,6 +27,7 @@ export const AppCanvas = observer(function InnerApp<S extends TLReactShape>(
       shapes={app.shapes} // TODO: use shapes in viewport later?
       assets={app.assets}
       showGrid={app.settings.showGrid}
+      penMode={app.settings.penMode}
       showSelection={app.showSelection}
       showSelectionRotation={app.showSelectionRotation}
       showResizeHandles={app.showResizeHandles}

+ 1 - 0
tldraw/packages/react/src/components/Canvas/Canvas.tsx

@@ -58,6 +58,7 @@ export interface TLCanvasProps<S extends TLReactShape> {
   cursorRotation: number
   selectionRotation: number
   onEditingEnd: () => void
+  penMode: boolean
   showGrid: boolean
   showSelection: boolean
   showHandles: boolean

+ 20 - 0
tldraw/packages/react/src/hooks/useCanvasEvents.ts

@@ -13,11 +13,19 @@ export function useCanvasEvents() {
 
   const events = React.useMemo(() => {
     const onPointerMove: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       callbacks.onPointerMove?.({ type: TLTargetType.Canvas, order }, e)
     }
 
     const onPointerDown: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       if (!order) e.currentTarget?.setPointerCapture(e.pointerId)
 
@@ -41,17 +49,29 @@ export function useCanvasEvents() {
     }
 
     const onPointerUp: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       if (!order) e.currentTarget?.releasePointerCapture(e.pointerId)
       callbacks.onPointerUp?.({ type: TLTargetType.Canvas, order }, e)
     }
 
     const onPointerEnter: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       callbacks.onPointerEnter?.({ type: TLTargetType.Canvas, order }, e)
     }
 
     const onPointerLeave: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       callbacks.onPointerLeave?.({ type: TLTargetType.Canvas, order }, e)
     }

+ 22 - 0
tldraw/packages/react/src/hooks/useShapeEvents.ts

@@ -1,23 +1,33 @@
 import * as React from 'react'
 import { TLTargetType } from '@tldraw/core'
+import { useApp } from './useApp'
 import { useRendererContext } from '.'
 import { DOUBLE_CLICK_DURATION } from '../constants'
 import type { TLReactShape } from '../lib'
 import type { TLReactCustomEvents } from '../types'
 
 export function useShapeEvents<S extends TLReactShape>(shape: S) {
+  const app = useApp()
   const { inputs, callbacks } = useRendererContext()
 
   const rDoubleClickTimer = React.useRef<number>(-1)
 
   const events = React.useMemo(() => {
     const onPointerMove: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       callbacks.onPointerMove?.({ type: TLTargetType.Shape, shape, order }, e)
       e.order = order + 1
     }
 
     const onPointerDown: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       if (!order) e.currentTarget?.setPointerCapture(e.pointerId)
       callbacks.onPointerDown?.({ type: TLTargetType.Shape, shape, order }, e)
@@ -25,6 +35,10 @@ export function useShapeEvents<S extends TLReactShape>(shape: S) {
     }
 
     const onPointerUp: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       if (!order) e.currentTarget?.releasePointerCapture(e.pointerId)
       callbacks.onPointerUp?.({ type: TLTargetType.Shape, shape, order }, e)
@@ -42,12 +56,20 @@ export function useShapeEvents<S extends TLReactShape>(shape: S) {
     }
 
     const onPointerEnter: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       callbacks.onPointerEnter?.({ type: TLTargetType.Shape, shape, order }, e)
       e.order = order + 1
     }
 
     const onPointerLeave: TLReactCustomEvents['pointer'] = e => {
+      if (app.settings.penMode && (e.pointerType !== 'pen' || !e.isPrimary)) {
+        return
+      }
+
       const { order = 0 } = e
       callbacks.onPointerLeave?.({ type: TLTargetType.Shape, shape, order }, e)
       e.order = order + 1