Drawing board control refinement (#10121)

* Drawing toolbar controls switching: return pen, after panning and color selection.

Signed-off-by: Denis Gladkiy <denis.gladkiy@hardcoreeng.com>

* Middle mouse button panning.

Signed-off-by: Denis Gladkiy <denis.gladkiy@hardcoreeng.com>

---------

Signed-off-by: Denis Gladkiy <denis.gladkiy@hardcoreeng.com>
This commit is contained in:
Denis 'GeneralGDA' Gladkiy
2025-10-21 19:47:03 +06:00
committed by GitHub
parent 925da2047c
commit 0ee7e4ed6d
4 changed files with 117 additions and 18 deletions
@@ -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')
})
})
})
})
@@ -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
}
}}
>
+60 -17
View File
@@ -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()
}
@@ -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
}
}}
>