Add PixelPerfectTouch

main
Shad Amethyst 2 years ago
parent fef0f2587f
commit 6aa87d052a

@ -1,6 +1,6 @@
{ {
"name": "@shadryx/pptk", "name": "@shadryx/pptk",
"version": "0.1.2", "version": "0.1.3",
"description": "Pixel-Perfect ToolKit, a library to help make pixel-perfect applications on high-DPI devices", "description": "Pixel-Perfect ToolKit, a library to help make pixel-perfect applications on high-DPI devices",
"keywords": [ "keywords": [
"pixel-perfect", "pixel-perfect",

@ -157,6 +157,161 @@ export function getContext2D(canvas) {
return new PixelPerfectContext2D(canvas); return new PixelPerfectContext2D(canvas);
} }
/**
* @typedef {object} Touch
* @prop {number} x
* @prop {number} y
* @prop {number} click_x
* @prop {number} click_y
* @prop {number} dx
* @prop {number} dy
* @prop {PointerEvent['pointerType']} type
* @prop {number} id
**/
/**
* @typedef {object} AttachTouchOptions
* @prop {((touch: Touch, touches: Map<number, Touch>) => void)=} onDown
* @prop {((touch: Touch[], touches: Map<number, Touch>) => void)=} onMove
* @prop {((touch: Touch | undefined, touches: Map<number, Touch>) => void)=} onUp
* @prop {number=} downscale
**/
/**
* @param {HTMLElement} element
* @param {AttachTouchOptions} options
**/
export function attachTouch(element, options = {}) {
/** @type {Map<PointerEvent['pointerId'], Touch>} **/
const touches = new Map();
const downscale = options.downscale ?? 1;
let queued = null;
let queued_touches = [];
function get_dpr() {
if (options.dpr) {
return window.devicePixelRatio ?? 1;
} else {
return 1;
}
}
function onDown(event) {
if (options.preventDefault) event.preventDefault();
let dpr = get_dpr();
let bounding = element.getBoundingClientRect();
let x = (event.clientX - bounding.x) * dpr / downscale;
let y = (event.clientY - bounding.y) * dpr / downscale;
let touch = {
x,
y,
click_x: x,
click_y: y,
dx: 0,
dy: 0,
type: event.pointerType,
id: event.pointerId,
};
touches.set(event.pointerId, touch);
options.onDown?.(touch, touches);
}
function onMove(event) {
if (options.preventDefault) event.preventDefault();
let dpr = get_dpr();
let bounding = element.getBoundingClientRect();
let x = (event.clientX - bounding.x) * dpr / downscale;
let y = (event.clientY - bounding.y) * dpr / downscale;
if (touches.has(event.pointerId)) {
let touch = touches.get(event.pointerId);
touch.dx = x - touch.x;
touch.dy = y - touch.y;
touch.x = x;
touch.y = y;
if (queued === null) {
queued = setTimeout(() => {
try {
options.onMove?.(queued_touches, touches);
} finally {
queued_touches = [];
queued = null;
}
}, 0);
}
queued_touches.push(touch);
}
}
function onUp(event) {
if (options.preventDefault) event.preventDefault();
let dpr = get_dpr();
let bounding = element.getBoundingClientRect();
let x = (event.clientX - bounding.x) * dpr / downscale;
let y = (event.clientY - bounding.y) * dpr / downscale;
let touch = touches.get(event.pointerId);
if (touch) {
touch.dx = x - touch.x;
touch.dy = y - touch.y;
touch.x = x;
touch.y = y;
}
touches.delete(event.pointerId);
options.onUp?.(touch, touches);
}
function onCancel(event) {
const touch = touches.get(event.pointerId);
touches.delete(event.pointerId);
options.onUp?.(touch, touches);
}
element.addEventListener("pointerdown", onDown);
element.addEventListener("pointermove", onMove);
element.addEventListener("pointerup", onUp);
element.addEventListener("pointercancel", onCancel);
function ontouchmove(event) {
event.preventDefault();
}
// preventDefault() on pointer events does not work as expected (eg. scrolling still happens), so this is needed instead
if (options.preventDefault) {
element.addEventListener("touchmove", ontouchmove, {passive: false});
}
return function unbind() {
element.removeEventListener("pointerdown", onDown);
element.removeEventListener("pointermove", onMove);
element.removeEventListener("pointup", onUp);
element.removeEventListener("pointercancel", onCancel);
if (options.preventDefault) {
element.removeEventListener("touchmove", ontouchmove, {passive: false});
}
}
}
export const StrokeMode = Object.freeze({ export const StrokeMode = Object.freeze({
DEFAULT: 0, DEFAULT: 0,
INSIDE: 1, INSIDE: 1,

@ -0,0 +1,28 @@
import { children, Component, createEffect, JSX, onCleanup } from "solid-js";
import { attachTouch, AttachTouchOptions, Touch } from "../index.js";
export type TouchHandler<T = Touch> = (touches: Map<number, Touch>, touch: T) => void;
export type PixelPefectTouchProps = {
children: JSX.Element;
onMount?: (element: HTMLDivElement) => void;
} & AttachTouchOptions;
export const PixelPerfectTouch: Component<PixelPefectTouchProps> = (
{ children, onMount, ...options }
) => {
let element: HTMLDivElement;
createEffect(() => {
onMount?.(element);
if (!element) return;
const cleanup = attachTouch(element, options);
onCleanup(cleanup);
});
return <div ref={el => element = el}>{children}</div>;
};

@ -1 +1,2 @@
export * from "./PixelPerfectCanvas.jsx"; export * from "./PixelPerfectCanvas.jsx";
export * from "./PixelPerfectTouch.jsx";

Loading…
Cancel
Save