From 6a944e3bbddbcdb3837279c6b1e74da7987aa6a6 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 29 Jul 2026 09:17:24 +0000 Subject: [PATCH 1/3] Capture pen input via browser api --- frontend/src/utility-functions/input.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/utility-functions/input.ts b/frontend/src/utility-functions/input.ts index e0642075ff..1fd64e9436 100644 --- a/frontend/src/utility-functions/input.ts +++ b/frontend/src/utility-functions/input.ts @@ -123,7 +123,7 @@ export function onPointerMove(e: PointerEvent, editor: EditorWrapper, documentSt const modifiers = makeKeyboardModifiersBitfield(e); if (detectShake(e)) editor.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers); - editor.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers); + editor.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers, ...pointerAttributes(e)); } export function onPointerDown(e: PointerEvent, editor: EditorWrapper, dialogStore: DialogStore) { @@ -152,7 +152,7 @@ export function onPointerDown(e: PointerEvent, editor: EditorWrapper, dialogStor if (viewportPointerInteractionOngoing && isTargetingCanvas instanceof Element) { const modifiers = makeKeyboardModifiersBitfield(e); - editor.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers); + editor.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers, ...pointerAttributes(e)); } } @@ -170,7 +170,7 @@ export function onPointerUp(e: PointerEvent, editor: EditorWrapper) { if (textToolInteractiveInputElement) return; const modifiers = makeKeyboardModifiersBitfield(e); - editor.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers); + editor.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers, ...pointerAttributes(e)); } // Mouse events @@ -277,6 +277,12 @@ export function onFocusOut() { canvasFocused = false; } +function pointerAttributes(e: PointerEvent): [number, number | undefined, number | undefined, number | undefined, number | undefined, number | undefined, boolean] { + const isPen = e.pointerType === "pen"; + const eraser = isPen && (e.buttons & 0b10_0000) !== 0; // Pen eraser end is reported as the 32 button bit + return [e.timeStamp, isPen ? e.pressure : undefined, isPen ? e.tiltX : undefined, isPen ? e.tiltY : undefined, isPen ? e.twist : undefined, isPen ? e.tangentialPressure : undefined, eraser]; +} + function detectShake(e: PointerEvent | MouseEvent): boolean { const SENSITIVITY_DIRECTION_CHANGES = 3; const SENSITIVITY_DISTANCE_TO_DISPLACEMENT_RATIO = 0.1; From eb3b181fbca7687428cede85e9e7f54278408ceb Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 29 Jul 2026 09:54:13 +0000 Subject: [PATCH 2/3] Replay coalesced pointer samples for smoother pen strokes --- frontend/src/utility-functions/input.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/utility-functions/input.ts b/frontend/src/utility-functions/input.ts index 1fd64e9436..28470a4127 100644 --- a/frontend/src/utility-functions/input.ts +++ b/frontend/src/utility-functions/input.ts @@ -123,7 +123,9 @@ export function onPointerMove(e: PointerEvent, editor: EditorWrapper, documentSt const modifiers = makeKeyboardModifiersBitfield(e); if (detectShake(e)) editor.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers); - editor.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers, ...pointerAttributes(e)); + + const samples = e.pointerType === "pen" ? e.getCoalescedEvents() : [e]; + for (const sample of samples) editor.onMouseMove(sample.clientX, sample.clientY, sample.buttons, modifiers, ...pointerAttributes(sample)); } export function onPointerDown(e: PointerEvent, editor: EditorWrapper, dialogStore: DialogStore) { From da0f63928c373eae5df6765cd3f536a2f4e612d8 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 9 Aug 2026 22:58:22 +0000 Subject: [PATCH 3/3] Handle missing or empty coalesced pen events --- frontend/src/utility-functions/input.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/utility-functions/input.ts b/frontend/src/utility-functions/input.ts index 28470a4127..9c77a55a3e 100644 --- a/frontend/src/utility-functions/input.ts +++ b/frontend/src/utility-functions/input.ts @@ -124,7 +124,8 @@ export function onPointerMove(e: PointerEvent, editor: EditorWrapper, documentSt const modifiers = makeKeyboardModifiersBitfield(e); if (detectShake(e)) editor.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers); - const samples = e.pointerType === "pen" ? e.getCoalescedEvents() : [e]; + const coalesced = e.pointerType === "pen" && typeof e.getCoalescedEvents === "function" ? e.getCoalescedEvents() : []; + const samples = coalesced.length > 0 ? coalesced : [e]; for (const sample of samples) editor.onMouseMove(sample.clientX, sample.clientY, sample.buttons, modifiers, ...pointerAttributes(sample)); }