Test palm rejection for add-page button taps
Move the draw-or-navigate choice into touchIntent() so the rule the canvas uses can be tested, and cover finger taps on the plus button: accepted after writing and while the pen hovers, rejected while the pen is down, for palm-sized contacts and right after pen activity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
This commit is contained in:
parent
2760819466
commit
d2c6e617bb
3 changed files with 45 additions and 3 deletions
|
|
@ -16,7 +16,7 @@
|
||||||
import { bounds, intersects, rectBeside, type Rect } from '$lib/model/layout';
|
import { bounds, intersects, rectBeside, type Rect } from '$lib/model/layout';
|
||||||
import { canInsert, freeSlots, freeSlotsOf, neighbor, type Dir, type Slot } from '$lib/model/tree';
|
import { canInsert, freeSlots, freeSlotsOf, neighbor, type Dir, type Slot } from '$lib/model/tree';
|
||||||
import { A4, PAGE_PRESETS, type Stroke } from '$lib/model/types';
|
import { A4, PAGE_PRESETS, type Stroke } from '$lib/model/types';
|
||||||
import { PalmRejector } from '$lib/ink/palm';
|
import { PalmRejector, touchIntent } from '$lib/ink/palm';
|
||||||
import { StrokeRecorder, hitStroke } from '$lib/ink/stroke';
|
import { StrokeRecorder, hitStroke } from '$lib/ink/stroke';
|
||||||
import { pageCssVars } from '$lib/editor/pageStyle';
|
import { pageCssVars } from '$lib/editor/pageStyle';
|
||||||
import { downloadCanvas } from './download';
|
import { downloadCanvas } from './download';
|
||||||
|
|
@ -301,7 +301,7 @@
|
||||||
// pen-down, palm-sized contacts and right after pen use, but not for the 5 s pen session.
|
// pen-down, palm-sized contacts and right after pen use, but not for the 5 s pen session.
|
||||||
const plus = target.closest<HTMLElement>('.ghost');
|
const plus = target.closest<HTMLElement>('.ghost');
|
||||||
if (e.pointerType === 'touch') {
|
if (e.pointerType === 'touch') {
|
||||||
const decision = palm.evaluate(e, fingerInks && !plus ? 'draw' : 'navigate');
|
const decision = palm.evaluate(e, touchIntent(fingerInks, !!plus));
|
||||||
if (!decision.accept) return; // palm: ignore entirely
|
if (!decision.accept) return; // palm: ignore entirely
|
||||||
touches.set(e.pointerId, p);
|
touches.set(e.pointerId, p);
|
||||||
if (touches.size >= 2) {
|
if (touches.size >= 2) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { PalmRejector } from './palm';
|
import { PalmRejector, touchIntent } from './palm';
|
||||||
|
|
||||||
const ev = (type: string, pointerType: string, extra: Partial<PointerEvent> = {}) =>
|
const ev = (type: string, pointerType: string, extra: Partial<PointerEvent> = {}) =>
|
||||||
({ type, pointerType, pointerId: pointerType === 'pen' ? 1 : 2, width: 10, height: 10, ...extra }) as PointerEvent;
|
({ type, pointerType, pointerId: pointerType === 'pen' ? 1 : 2, width: 10, height: 10, ...extra }) as PointerEvent;
|
||||||
|
|
@ -53,4 +53,37 @@ describe('palm rejection', () => {
|
||||||
it('allows finger ink again once the pen session ends', () => {
|
it('allows finger ink again once the pen session ends', () => {
|
||||||
expect(afterPen(5100).evaluate(touch(), 'draw').accept).toBe(true);
|
expect(afterPen(5100).evaluate(touch(), 'draw').accept).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('add-page button taps', () => {
|
||||||
|
// With a pen in use fingers don't ink, but finger ink may still be on (before any pen).
|
||||||
|
const tap = (palm: PalmRejector, extra: Partial<PointerEvent> = {}, fingerInks = false) =>
|
||||||
|
palm.evaluate(touch(extra), touchIntent(fingerInks, true));
|
||||||
|
|
||||||
|
it('are handled as navigation even when fingers ink', () => {
|
||||||
|
expect(touchIntent(true, true)).toBe('navigate');
|
||||||
|
expect(touchIntent(false, true)).toBe('navigate');
|
||||||
|
expect(touchIntent(true, false)).toBe('draw');
|
||||||
|
expect(touchIntent(false, false)).toBe('navigate');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('are accepted a couple of seconds after writing', () => {
|
||||||
|
expect(tap(afterPen(2000)).accept).toBe(true);
|
||||||
|
expect(tap(afterPen(2000), {}, true).accept).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('are accepted while the pen hovers', () => {
|
||||||
|
expect(tap(afterPen(2000, { leave: false })).accept).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('are rejected while the pen is down', () => {
|
||||||
|
const palm = new PalmRejector();
|
||||||
|
palm.track(ev('pointerdown', 'pen'));
|
||||||
|
expect(tap(palm).accept).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('are rejected for a palm or right after the pen', () => {
|
||||||
|
expect(tap(afterPen(2000), { width: 60, height: 40 }).accept).toBe(false);
|
||||||
|
expect(tap(afterPen(50)).accept).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,15 @@ const PALM_CONTACT_PX = 35;
|
||||||
/** What an accepted touch would do: ink (draw/erase) or pan/zoom. */
|
/** What an accepted touch would do: ink (draw/erase) or pan/zoom. */
|
||||||
export type TouchIntent = 'draw' | 'navigate';
|
export type TouchIntent = 'draw' | 'navigate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which policy a touch falls under. It only draws when finger ink is on and it
|
||||||
|
* didn't land on a button; taps on buttons (e.g. add page) navigate, so they keep
|
||||||
|
* working between pen strokes.
|
||||||
|
*/
|
||||||
|
export function touchIntent(fingerInks: boolean, onButton: boolean): TouchIntent {
|
||||||
|
return fingerInks && !onButton ? 'draw' : 'navigate';
|
||||||
|
}
|
||||||
|
|
||||||
export interface Decision {
|
export interface Decision {
|
||||||
accept: boolean;
|
accept: boolean;
|
||||||
reason: string;
|
reason: string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue