diff --git a/packages/presentation/src/___tests___/drawing.test.ts b/packages/presentation/src/___tests___/drawing.test.ts index 703cfe5fb96..14ed0a25baf 100644 --- a/packages/presentation/src/___tests___/drawing.test.ts +++ b/packages/presentation/src/___tests___/drawing.test.ts @@ -593,5 +593,54 @@ describe('drawing module tests', () => { ]) }) }) + + describe('tools selection', () => { + it('select color after panning', () => { + const colorsList: ColorsList = [ + ['alpha', new ThemeAwareColor('red', 'yellow')], + ['beta', new ThemeAwareColor('blue', 'blue')] + ] + + const toolChangedSpy = jest.fn() + + const initialPenColor: ColorMetaNameOrHex = 'alpha' + const systemUnderTest = drawing(drawingPlugInPoint, { + colorsList, + getCurrentTheme: () => 'theme-light', + subscribeOnThemeChange: () => {}, + readonly: false, + imageWidth: 400, + imageHeight: 300, + commands: [], + tool: 'pen', + penColor: initialPenColor, + toolChanged: toolChangedSpy + }) + + systemUnderTest.update?.({ + colorsList, + getCurrentTheme: () => 'theme-light', + subscribeOnThemeChange: () => {}, + readonly: false, + tool: 'pan', + toolChanged: toolChangedSpy + }) + + expect(toolChangedSpy).toHaveBeenCalledWith('pan') + toolChangedSpy.mockClear() + + const newColor: ColorMetaNameOrHex = 'beta' + systemUnderTest.update?.({ + colorsList, + getCurrentTheme: () => 'theme-light', + subscribeOnThemeChange: () => {}, + readonly: false, + penColor: newColor, + toolChanged: toolChangedSpy + }) + + expect(toolChangedSpy).toHaveBeenCalledWith('pen') + }) + }) }) }) diff --git a/packages/presentation/src/components/DrawingBoard.svelte b/packages/presentation/src/components/DrawingBoard.svelte index fe64e017048..d7cba893a47 100644 --- a/packages/presentation/src/components/DrawingBoard.svelte +++ b/packages/presentation/src/components/DrawingBoard.svelte @@ -230,6 +230,7 @@ penWidth, eraserWidth, fontSize, + enableMiddleMousePanning: false, changingCmdId, cmdAdded: addCommand, cmdChanging: showCommandProps, @@ -240,6 +241,9 @@ cmdDeleted: deleteCommand, editorCreated: (editor) => { cmdEditor = editor + }, + toolChanged: (newTool) => { + tool = newTool } }} > diff --git a/packages/presentation/src/drawing.ts b/packages/presentation/src/drawing.ts index f788163f084..8573286654d 100644 --- a/packages/presentation/src/drawing.ts +++ b/packages/presentation/src/drawing.ts @@ -57,6 +57,7 @@ export interface DrawingProps { changingCmdId?: CommandUid personCursorPos?: Point personCursorVisible?: boolean + enableMiddleMousePanning?: boolean cmdAdded?: (cmd: DrawingCmd) => void cmdChanging?: (id: CommandUid) => void cmdUnchanged?: (id: CommandUid) => void @@ -67,6 +68,7 @@ export interface DrawingProps { personCursorMoved?: (nodePos: Point) => void panning?: (offset: Point) => void panned?: (offset: Point) => void + toolChanged?: (tool: DrawingTool) => void } export type DrawingTool = 'pen' | 'erase' | 'pan' | 'text' @@ -81,6 +83,7 @@ type PointStatus = 'last-point' | 'intermediate-point' class DrawState { on = false + usedBeforePanningTool: DrawingTool | undefined = undefined tool: DrawingTool = 'pen' penColor: ColorMetaNameOrHex = 'alpha' penWidth = 4 @@ -373,6 +376,7 @@ export function drawing ( resizeObserver.observe(canvas) let touchId: number | undefined + let isMiddleMousePanning = false function findTouch (touches: TouchList, id: number | undefined = touchId): Touch | undefined { for (let i = 0; i < touches.length; i++) { @@ -398,7 +402,8 @@ export function drawing ( } const touch = e.changedTouches[0] touchId = touch.identifier - drawStart(touchToNodePoint(touch, canvas)) + const forcePan = false + drawStart(touchToNodePoint(touch, canvas), forcePan) } canvas.ontouchmove = (e) => { @@ -407,7 +412,8 @@ export function drawing ( } const touch = findTouch(e.changedTouches) if (touch !== undefined) { - drawContinue(touchToNodePoint(touch, canvas)) + const forcePan = false + drawContinue(touchToNodePoint(touch, canvas), forcePan) } } @@ -417,7 +423,8 @@ export function drawing ( } const touch = findTouch(e.changedTouches) if (touch !== undefined) { - drawEnd(touchToNodePoint(touch, canvas)) + const forcePan = false + drawEnd(touchToNodePoint(touch, canvas), forcePan) } touchId = undefined } @@ -428,12 +435,22 @@ export function drawing ( if (readonly) { return } + const MiddleMouseButton = 1 + if (e.button === MiddleMouseButton && props.enableMiddleMousePanning === true) { + e.preventDefault() + isMiddleMousePanning = true + canvas.setPointerCapture(e.pointerId) + const forcePan = true + drawStart(pointerToNodePoint(e), forcePan) + return + } if (e.button !== 0) { return } e.preventDefault() canvas.setPointerCapture(e.pointerId) - drawStart(pointerToNodePoint(e)) + const forcePan = false + drawStart(pointerToNodePoint(e), forcePan) } canvas.onpointermove = (e) => { @@ -441,7 +458,8 @@ export function drawing ( return } e.preventDefault() - drawContinue(pointerToNodePoint(e)) + const forcePan = isMiddleMousePanning + drawContinue(pointerToNodePoint(e), forcePan) } canvas.onpointerup = (e) => { @@ -450,10 +468,23 @@ export function drawing ( } e.preventDefault() canvas.releasePointerCapture(e.pointerId) - drawEnd(pointerToNodePoint(e)) + const forcePan = isMiddleMousePanning + drawEnd(pointerToNodePoint(e), forcePan) + if (e.button === 1) { + isMiddleMousePanning = false + } } - canvas.onpointercancel = canvas.onpointerup + canvas.onpointercancel = (e) => { + if (readonly) { + return + } + e.preventDefault() + canvas.releasePointerCapture(e.pointerId) + const forcePan = isMiddleMousePanning + drawEnd(pointerToNodePoint(e), forcePan) + isMiddleMousePanning = false + } canvas.onpointerenter = () => { if (!readonly && draw.isDrawingTool()) { @@ -472,20 +503,20 @@ export function drawing ( return makeMouseScaledPoint(scaled.x, scaled.y) } - function drawStart (p: NodePoint): void { + function drawStart (p: NodePoint, forcePan: boolean): void { const scaledPoint = rescaleWithCss(p) draw.on = true draw.points = [] prevPos = scaledPoint - if (draw.isDrawingTool()) { + if (!forcePan && draw.isDrawingTool()) { draw.addPoint(scaledPoint) } } - function drawContinue (p: NodePoint): void { + function drawContinue (p: NodePoint, forcePan: boolean): void { const scaledPoint = rescaleWithCss(p) - if (draw.isDrawingTool()) { + if (!forcePan && draw.isDrawingTool()) { const cursorSize = draw.cursorWidth() const canvasOffsetInParent = offsetInParent(node, canvas) @@ -501,7 +532,7 @@ export function drawing ( } } - if (draw.on && draw.tool === 'pan') { + if (draw.on && (draw.tool === 'pan' || forcePan)) { requestAnimationFrame(() => { draw.offset.x += scaledPoint.x - prevPos.x draw.offset.y += scaledPoint.y - prevPos.y @@ -513,22 +544,22 @@ export function drawing ( }) } - if (draw.on && draw.tool === 'text') { + if (draw.on && draw.tool === 'text' && !forcePan) { prevPos = scaledPoint } - if (props.pointerMoved !== undefined) { + if (props.pointerMoved !== undefined && !forcePan) { props.pointerMoved(draw.mouseToCanvasPoint(scaledPoint)) } } - function drawEnd (p: NodePoint): void { + function drawEnd (p: NodePoint, forcePan: boolean): void { const scaledPoint = rescaleWithCss(p) if (draw.on) { - if (draw.isDrawingTool()) { + if (!forcePan && draw.isDrawingTool()) { draw.drawLine(scaledPoint, 'last-point', props.getCurrentTheme()) storeLineCommand() - } else if (draw.tool === 'pan') { + } else if (draw.tool === 'pan' || forcePan) { props.panned?.(draw.offset) } else if (draw.tool === 'text') { if (liveTextBox !== undefined) { @@ -1024,6 +1055,11 @@ export function drawing ( } if (props.tool !== undefined) { if (draw.tool !== props.tool) { + if (props.tool === 'pan') { + draw.usedBeforePanningTool = draw.tool + } else { + draw.usedBeforePanningTool = props.tool + } draw.tool = props.tool syncToolCursor = true toolChanged = true @@ -1031,6 +1067,10 @@ export function drawing ( } if (props.penColor !== undefined) { if (draw.penColor !== props.penColor) { + if (draw.tool === 'pan' && draw.usedBeforePanningTool != null) { + draw.tool = draw.usedBeforePanningTool + toolChanged = true + } draw.penColor = props.penColor syncLiveTextBox = true syncToolCursor = true @@ -1084,6 +1124,9 @@ export function drawing ( } } + if (toolChanged) { + props.toolChanged?.(draw.tool) + } if (syncToolCursor) { updateToolCursor() } diff --git a/plugins/text-editor-resources/src/components/DrawingBoardEditor.svelte b/plugins/text-editor-resources/src/components/DrawingBoardEditor.svelte index e2c2cdadb10..331d324fc24 100644 --- a/plugins/text-editor-resources/src/components/DrawingBoardEditor.svelte +++ b/plugins/text-editor-resources/src/components/DrawingBoardEditor.svelte @@ -32,7 +32,6 @@ import { Loading, Component, themeStore } from '@hcengineering/ui' import { onMount, onDestroy } from 'svelte' import { Array as YArray, Map as YMap, Doc as YDoc } from 'yjs' - import { get } from 'svelte/store' export let boardId: string export let document: YDoc @@ -262,6 +261,7 @@ eraserWidth, fontSize, personCursorPos: personCursorCanvasPos, + enableMiddleMousePanning: true, changingCmdId, cmdAdded: (commandToAdd) => { commandsProcessor.addCommand(commandToAdd) @@ -288,6 +288,9 @@ }, personCursorMoved: (nodePos) => { personCursorNodePos = nodePos + }, + toolChanged: (newTool) => { + tool = newTool } }} >