Init
This commit is contained in:
commit
0ad405c26e
78 changed files with 9127 additions and 0 deletions
98
src/lib/ink/palm.ts
Normal file
98
src/lib/ink/palm.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// Palm rejection — ported from palm-rejection-test.html.
|
||||
//
|
||||
// Pens are always accepted. A touch is rejected when a pen session is active
|
||||
// (stroke in progress or pen activity within the last 5 s), when its contact
|
||||
// ellipse is palm-sized, when it lands within 150 ms of pen activity, or while
|
||||
// a pen hovers over the screen. A touch already drawing is cut off as soon as
|
||||
// pen activity appears (callers re-run `evaluate` on every move).
|
||||
|
||||
export interface PalmOptions {
|
||||
/** Block all touches while a pen session is active (5 s window). */
|
||||
penSession: boolean;
|
||||
/** Reject touches with a wide contact ellipse. */
|
||||
geometry: boolean;
|
||||
/** Reject touches within 150 ms of pen activity. */
|
||||
timing: boolean;
|
||||
/** Arm the lockout as soon as the pen hovers. */
|
||||
hover: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_PALM: PalmOptions = { penSession: true, geometry: true, timing: true, hover: true };
|
||||
|
||||
const PEN_SESSION_MS = 5000;
|
||||
const PEN_TIMING_MS = 150;
|
||||
const PALM_CONTACT_PX = 35;
|
||||
|
||||
export interface Decision {
|
||||
accept: boolean;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export class PalmRejector {
|
||||
options: PalmOptions;
|
||||
lastPenActivityTime = 0;
|
||||
private activePens = new Set<number>();
|
||||
private penHovering = false;
|
||||
|
||||
constructor(options: PalmOptions = DEFAULT_PALM) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
get penIsDown() {
|
||||
return this.activePens.size > 0;
|
||||
}
|
||||
|
||||
/** Feed every pointerdown/move/up/leave so pen state stays current. */
|
||||
track(e: PointerEvent) {
|
||||
if (e.pointerType !== 'pen') return;
|
||||
const now = Date.now();
|
||||
switch (e.type) {
|
||||
case 'pointerdown':
|
||||
this.activePens.add(e.pointerId);
|
||||
this.lastPenActivityTime = now;
|
||||
break;
|
||||
case 'pointermove':
|
||||
case 'pointerover':
|
||||
case 'pointerenter':
|
||||
// Hover (buttons == 0) counts as "pen nearby" too.
|
||||
this.lastPenActivityTime = now;
|
||||
this.penHovering = !this.activePens.has(e.pointerId);
|
||||
break;
|
||||
case 'pointerup':
|
||||
case 'pointercancel':
|
||||
this.activePens.delete(e.pointerId);
|
||||
this.lastPenActivityTime = now;
|
||||
break;
|
||||
case 'pointerleave':
|
||||
case 'pointerout':
|
||||
this.penHovering = false;
|
||||
this.lastPenActivityTime = now;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
evaluate(e: PointerEvent): Decision {
|
||||
if (e.pointerType === 'pen') return { accept: true, reason: 'pen input' };
|
||||
if (e.pointerType !== 'touch') return { accept: true, reason: 'non-touch pointer' };
|
||||
|
||||
const o = this.options;
|
||||
const since = Date.now() - this.lastPenActivityTime;
|
||||
|
||||
if (o.penSession && (since < PEN_SESSION_MS || this.penIsDown)) {
|
||||
return { accept: false, reason: 'pen session active' };
|
||||
}
|
||||
if (o.geometry) {
|
||||
const contact = Math.max(e.width || 0, e.height || 0);
|
||||
if (contact > PALM_CONTACT_PX) {
|
||||
return { accept: false, reason: `wide contact (${contact.toFixed(0)}px)` };
|
||||
}
|
||||
}
|
||||
if (o.timing && since < PEN_TIMING_MS) {
|
||||
return { accept: false, reason: 'within 150ms of pen activity' };
|
||||
}
|
||||
if (o.hover && this.penHovering) {
|
||||
return { accept: false, reason: 'pen hovering' };
|
||||
}
|
||||
return { accept: true, reason: 'touch accepted' };
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue