From d80bb183e241fd0a5782516f82c14bc24fb3f288 Mon Sep 17 00:00:00 2001 From: Mateusz Radomski Date: Tue, 28 May 2024 14:05:27 +0200 Subject: [PATCH 1/4] Improve mouse support --- .../frontend/src/store/actions/onMouseDown.ts | 11 ++++++++--- .../frontend/src/store/actions/onMouseMove.ts | 13 +++++++++++-- packages/frontend/src/store/actions/onMouseUp.ts | 9 ++++++--- packages/frontend/src/store/actions/onWheel.ts | 15 ++++++++++++++- packages/frontend/src/store/utils/constants.ts | 13 ++++++++----- 5 files changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/frontend/src/store/actions/onMouseDown.ts b/packages/frontend/src/store/actions/onMouseDown.ts index ea1ec46..a886289 100644 --- a/packages/frontend/src/store/actions/onMouseDown.ts +++ b/packages/frontend/src/store/actions/onMouseDown.ts @@ -1,6 +1,9 @@ import { isResizeHandle } from '../../view/ResizeHandle' import { State } from '../State' -import { LEFT_MOUSE_BUTTON, MIDDLE_MOUSE_BUTTON } from '../utils/constants' +import { + CLICKED_LEFT_MOUSE_BUTTON, + CLICKED_MIDDLE_MOUSE_BUTTON, +} from '../utils/constants' import { toViewCoordinates } from '../utils/coordinates' import { reverseIter } from '../utils/reverseIter' @@ -27,7 +30,7 @@ export function onMouseDown( } } - if (event.button === LEFT_MOUSE_BUTTON && !state.mouseMoveAction) { + if (event.button === CLICKED_LEFT_MOUSE_BUTTON && !state.mouseMoveAction) { if (state.pressed.spaceKey) { const [x, y] = [event.clientX, event.clientY] return { @@ -93,10 +96,12 @@ export function onMouseDown( } } - if (event.button === MIDDLE_MOUSE_BUTTON && !state.mouseMoveAction) { + if (event.button === CLICKED_MIDDLE_MOUSE_BUTTON && !state.mouseMoveAction) { + const [x, y] = [event.clientX, event.clientY] return { pressed: { ...state.pressed, middleMouseButton: true }, mouseMoveAction: 'pan', + mouseMove: { startX: x, startY: y, currentX: x, currentY: y }, } } diff --git a/packages/frontend/src/store/actions/onMouseMove.ts b/packages/frontend/src/store/actions/onMouseMove.ts index 3a504ad..4f16a55 100644 --- a/packages/frontend/src/store/actions/onMouseMove.ts +++ b/packages/frontend/src/store/actions/onMouseMove.ts @@ -1,5 +1,9 @@ import { Box, State } from '../State' -import { LEFT_MOUSE_BUTTON, NODE_WIDTH } from '../utils/constants' +import { + HELD_LEFT_MOUSE_BUTTON_MASK, + HELD_MIDDLE_MOUSE_BUTTON_MASK, + NODE_WIDTH, +} from '../utils/constants' import { toViewCoordinates } from '../utils/coordinates' import { toContainerCoordinates } from '../utils/toContainerCoordinates' import { updateNodePositions } from '../utils/updateNodePositions' @@ -13,7 +17,12 @@ export function onMouseMove( return {} } - if (state.pressed.leftMouseButton && event.button === LEFT_MOUSE_BUTTON) { + const isLeftMouse = + state.pressed.leftMouseButton && event.buttons & HELD_LEFT_MOUSE_BUTTON_MASK + const isMiddleMouse = + state.pressed.middleMouseButton && + event.buttons & HELD_MIDDLE_MOUSE_BUTTON_MASK + if (isLeftMouse || isMiddleMouse) { switch (state.mouseMoveAction) { case undefined: { return { ...state, mouseUpAction: undefined } diff --git a/packages/frontend/src/store/actions/onMouseUp.ts b/packages/frontend/src/store/actions/onMouseUp.ts index 81497e3..4a09f09 100644 --- a/packages/frontend/src/store/actions/onMouseUp.ts +++ b/packages/frontend/src/store/actions/onMouseUp.ts @@ -1,8 +1,11 @@ import { State } from '../State' -import { LEFT_MOUSE_BUTTON, MIDDLE_MOUSE_BUTTON } from '../utils/constants' +import { + CLICKED_LEFT_MOUSE_BUTTON, + CLICKED_MIDDLE_MOUSE_BUTTON, +} from '../utils/constants' export function onMouseUp(state: State, event: MouseEvent): Partial { - if (event.button === LEFT_MOUSE_BUTTON) { + if (event.button === CLICKED_LEFT_MOUSE_BUTTON) { let selectedNodeIds = state.selectedNodeIds if (state.mouseUpAction?.type === 'DeselectOne') { const removeId = state.mouseUpAction.id @@ -20,7 +23,7 @@ export function onMouseUp(state: State, event: MouseEvent): Partial { } } - if (event.button === MIDDLE_MOUSE_BUTTON) { + if (event.button === CLICKED_MIDDLE_MOUSE_BUTTON) { return { pressed: { ...state.pressed, middleMouseButton: false }, mouseMoveAction: diff --git a/packages/frontend/src/store/actions/onWheel.ts b/packages/frontend/src/store/actions/onWheel.ts index ee6e811..7f7b7ff 100644 --- a/packages/frontend/src/store/actions/onWheel.ts +++ b/packages/frontend/src/store/actions/onWheel.ts @@ -20,7 +20,20 @@ export function onWheel( if (event.ctrlKey || event.metaKey) { const rect = view.getBoundingClientRect() - const desiredChange = -deltaY * ZOOM_SENSITIVITY + let desiredChange = -deltaY * ZOOM_SENSITIVITY + if (event.ctrlKey) { + // NOTE(radomski): This is a magic value but there is no other way to + // handle this nicely in a compact way. The `onwheel` event triggers + // for mouse scrolling, touchpad scrolling AND touchpad pinching. + // Pinching is the only case where the numbers are reaaaaaaaly small + // for some reason. We multiply this by 8 to get a delta that feels + // more natural. + // + // You know that the event is a pinch event when the `ctrlKey` is set. + // Yes. Really. I'm not joking. + desiredChange = desiredChange * 8 + } + let newScale = scale * (1 + desiredChange) newScale = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newScale)) const change = newScale / scale - 1 diff --git a/packages/frontend/src/store/utils/constants.ts b/packages/frontend/src/store/utils/constants.ts index 1c9790b..fd457bd 100644 --- a/packages/frontend/src/store/utils/constants.ts +++ b/packages/frontend/src/store/utils/constants.ts @@ -1,5 +1,8 @@ -export const LEFT_MOUSE_BUTTON = 0 -export const MIDDLE_MOUSE_BUTTON = 1 +export const CLICKED_LEFT_MOUSE_BUTTON = 0 +export const CLICKED_MIDDLE_MOUSE_BUTTON = 1 + +export const HELD_LEFT_MOUSE_BUTTON_MASK = 1 +export const HELD_MIDDLE_MOUSE_BUTTON_MASK = 4 export const SPACE_KEY = ' ' export const SHIFT_KEY = 'Shift' @@ -15,9 +18,9 @@ export const NODE_SPACING = 25 export const RESIZE_HANDLE_SPACING = 15 -export const ZOOM_SENSITIVITY = 0.02 -export const MAX_ZOOM = 3 -export const MIN_ZOOM = 0.3 +export const ZOOM_SENSITIVITY = 0.002 +export const MAX_ZOOM = 10 +export const MIN_ZOOM = 0.03 export const SCROLL_LINE_HEIGHT = 40 export const SCROLL_PAGE_HEIGHT = 800 From d73490caabfd01aed7ff63a71ea16ac139357397 Mon Sep 17 00:00:00 2001 From: Mateusz Radomski Date: Tue, 28 May 2024 15:42:29 +0200 Subject: [PATCH 2/4] Improve windows support --- packages/frontend/src/store/actions/onWheel.ts | 7 +++---- packages/frontend/src/store/utils/constants.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/store/actions/onWheel.ts b/packages/frontend/src/store/actions/onWheel.ts index 7f7b7ff..84c511e 100644 --- a/packages/frontend/src/store/actions/onWheel.ts +++ b/packages/frontend/src/store/actions/onWheel.ts @@ -21,7 +21,7 @@ export function onWheel( const rect = view.getBoundingClientRect() let desiredChange = -deltaY * ZOOM_SENSITIVITY - if (event.ctrlKey) { + if (event.ctrlKey && IS_MACOS) { // NOTE(radomski): This is a magic value but there is no other way to // handle this nicely in a compact way. The `onwheel` event triggers // for mouse scrolling, touchpad scrolling AND touchpad pinching. @@ -46,11 +46,10 @@ export function onWheel( }, } } else { - const invert = event.shiftKey && !IS_MACOS return { transform: { - offsetX: offsetX - (!invert ? event.deltaX : deltaY), - offsetY: offsetY - (!invert ? event.deltaY : deltaX), + offsetX: offsetX - deltaX, + offsetY: offsetY - deltaY, scale, }, } diff --git a/packages/frontend/src/store/utils/constants.ts b/packages/frontend/src/store/utils/constants.ts index fd457bd..7468695 100644 --- a/packages/frontend/src/store/utils/constants.ts +++ b/packages/frontend/src/store/utils/constants.ts @@ -22,5 +22,5 @@ export const ZOOM_SENSITIVITY = 0.002 export const MAX_ZOOM = 10 export const MIN_ZOOM = 0.03 -export const SCROLL_LINE_HEIGHT = 40 +export const SCROLL_LINE_HEIGHT = 20 export const SCROLL_PAGE_HEIGHT = 800 From ad2c8d8497d9c46aadb025279d4dd0bd9d6d7f92 Mon Sep 17 00:00:00 2001 From: Mateusz Radomski Date: Tue, 28 May 2024 16:25:28 +0200 Subject: [PATCH 3/4] Support pinching on windows --- packages/frontend/src/store/State.ts | 1 + packages/frontend/src/store/actions/onKeyDown.ts | 5 ++++- packages/frontend/src/store/actions/onKeyUp.ts | 5 ++++- packages/frontend/src/store/actions/onWheel.ts | 3 +-- packages/frontend/src/store/utils/constants.ts | 1 + 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/store/State.ts b/packages/frontend/src/store/State.ts index 6f086d4..e95eb60 100644 --- a/packages/frontend/src/store/State.ts +++ b/packages/frontend/src/store/State.ts @@ -15,6 +15,7 @@ export interface State { readonly middleMouseButton: boolean readonly shiftKey: boolean readonly spaceKey: boolean + readonly ctrlKey: boolean } readonly resizingNode?: { readonly id: string diff --git a/packages/frontend/src/store/actions/onKeyDown.ts b/packages/frontend/src/store/actions/onKeyDown.ts index f8e46b9..d386a90 100644 --- a/packages/frontend/src/store/actions/onKeyDown.ts +++ b/packages/frontend/src/store/actions/onKeyDown.ts @@ -1,11 +1,14 @@ import { State } from '../State' -import { SHIFT_KEY, SPACE_KEY } from '../utils/constants' +import { CTRL_KEY, SHIFT_KEY, SPACE_KEY } from '../utils/constants' import { updateNodePositions } from '../utils/updateNodePositions' export function onKeyDown(state: State, event: KeyboardEvent): Partial { if (event.key === SPACE_KEY) { return { pressed: { ...state.pressed, spaceKey: true } } } + if (event.key === CTRL_KEY) { + return { pressed: { ...state.pressed, ctrlKey: true } } + } if (event.key === SHIFT_KEY) { return updateNodePositions({ ...state, diff --git a/packages/frontend/src/store/actions/onKeyUp.ts b/packages/frontend/src/store/actions/onKeyUp.ts index 35e7d44..107fab9 100644 --- a/packages/frontend/src/store/actions/onKeyUp.ts +++ b/packages/frontend/src/store/actions/onKeyUp.ts @@ -1,11 +1,14 @@ import { State } from '../State' -import { SHIFT_KEY, SPACE_KEY } from '../utils/constants' +import { CTRL_KEY, SHIFT_KEY, SPACE_KEY } from '../utils/constants' import { updateNodePositions } from '../utils/updateNodePositions' export function onKeyUp(state: State, event: KeyboardEvent): Partial { if (event.key === SPACE_KEY) { return { pressed: { ...state.pressed, spaceKey: false } } } + if (event.key === CTRL_KEY) { + return { pressed: { ...state.pressed, ctrlKey: false } } + } if (event.key === SHIFT_KEY) { return updateNodePositions({ ...state, diff --git a/packages/frontend/src/store/actions/onWheel.ts b/packages/frontend/src/store/actions/onWheel.ts index 84c511e..c99e117 100644 --- a/packages/frontend/src/store/actions/onWheel.ts +++ b/packages/frontend/src/store/actions/onWheel.ts @@ -1,6 +1,5 @@ import { State } from '../State' import { - IS_MACOS, MAX_ZOOM, MIN_ZOOM, SCROLL_LINE_HEIGHT, @@ -21,7 +20,7 @@ export function onWheel( const rect = view.getBoundingClientRect() let desiredChange = -deltaY * ZOOM_SENSITIVITY - if (event.ctrlKey && IS_MACOS) { + if (event.ctrlKey && !state.pressed.ctrlKey) { // NOTE(radomski): This is a magic value but there is no other way to // handle this nicely in a compact way. The `onwheel` event triggers // for mouse scrolling, touchpad scrolling AND touchpad pinching. diff --git a/packages/frontend/src/store/utils/constants.ts b/packages/frontend/src/store/utils/constants.ts index 7468695..3404bae 100644 --- a/packages/frontend/src/store/utils/constants.ts +++ b/packages/frontend/src/store/utils/constants.ts @@ -5,6 +5,7 @@ export const HELD_LEFT_MOUSE_BUTTON_MASK = 1 export const HELD_MIDDLE_MOUSE_BUTTON_MASK = 4 export const SPACE_KEY = ' ' +export const CTRL_KEY = 'Control' export const SHIFT_KEY = 'Shift' export const IS_MACOS = navigator.userAgent.toLowerCase().includes('mac os') From f1a1f39f44b6ec376f7ceffd0f99d3aee35b75ca Mon Sep 17 00:00:00 2001 From: Mateusz Radomski Date: Tue, 28 May 2024 16:27:54 +0200 Subject: [PATCH 4/4] typecheck --- packages/frontend/src/store/store.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/store/store.ts b/packages/frontend/src/store/store.ts index 169dec8..1fa0e35 100644 --- a/packages/frontend/src/store/store.ts +++ b/packages/frontend/src/store/store.ts @@ -22,6 +22,7 @@ export const useStore = create()( pressed: { leftMouseButton: false, middleMouseButton: false, + ctrlKey: false, shiftKey: false, spaceKey: false, },