Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
run: |
cp ../../wasm/* .
cp ../../vizmat-app/index.html .
cp ../../vizmat-app/mobile-gesture.js .
cp ../../vizmat-app/style.css .
npm install
npm run build
Expand Down
7 changes: 6 additions & 1 deletion vizmat-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
<head>
<meta charset="UTF-8">
<title>vizmat: a cross platform crystal visualizer</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
Expand All @@ -18,9 +21,11 @@
data-cargo-no-default-features="true" />
<link data-trunk rel="copy-dir" href="assets" />
<link data-trunk rel="copy-file" href="index.js" />
<link data-trunk rel="copy-file" href="mobile-gesture.js" />
<link data-trunk rel="copy-file" href="style.css" />
<link href="style.css" rel="stylesheet" />
<script type="module" src="./index.js"></script>
<script type="module" src="./mobile-gesture.js"></script>
<h1 class="site-title">vizmat <span>molecule / crystal</span> visual lab</h1>
<header>
<a href="https://github.com/rs4rse/vizmat" target="_blank">
Expand Down
268 changes: 268 additions & 0 deletions vizmat-app/mobile-gesture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
(function () {
const canvas = document.getElementById("bevy-canvas");

const emitTouchGesture = (gesture) => {
window.dispatchEvent(
new CustomEvent("vizmat-touch-gesture", {
detail: gesture,
}),
);
};

const toVec = (touch) => ({ x: touch.clientX, y: touch.clientY });

const touchPanState = {
one: null,
two: {
active: false,
midpoint: null,
distance: null,
},
};

const getMidpoint = (a, b) => ({
x: (a.x + b.x) * 0.5,
y: (a.y + b.y) * 0.5,
});

const getDistance = (a, b) => {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
};

const clearTouchState = () => {
touchPanState.one = null;
touchPanState.two = {
active: false,
midpoint: null,
distance: null,
};
};

const emitIf = (condition, payload) => {
if (!condition) return false;
emitTouchGesture(payload);
return true;
};

if (canvas) {
canvas.style.touchAction = "none";

if (window.PointerEvent) {
const pointerState = new Map();

const removePointer = (event) => {
if (event.pointerType !== "touch") return;
if (!event.currentTarget || !pointerState.has(event.pointerId)) return;
pointerState.delete(event.pointerId);

if (pointerState.size === 0) {
clearTouchState();
return;
}

if (pointerState.size === 1) {
touchPanState.one = [...pointerState.values()][0];
touchPanState.two = {
active: false,
midpoint: null,
distance: null,
};
return;
}

const points = [...pointerState.values()];
touchPanState.two = {
active: true,
midpoint: getMidpoint(points[0], points[1]),
distance: getDistance(points[0], points[1]),
};
};

const updateSinglePointer = (point) => {
if (!touchPanState.one) {
touchPanState.one = point;
return;
}

const dx = point.x - touchPanState.one.x;
const dy = point.y - touchPanState.one.y;
const emitted = emitIf(dx !== 0 || dy !== 0, {
gesture: "Rotate",
dx,
dy,
scale_delta: 0,
});
if (emitted) {
touchPanState.one = point;
}
};

const updateTwoPointer = (a, b) => {
if (!touchPanState.two.active) {
touchPanState.two = {
active: true,
midpoint: getMidpoint(a, b),
distance: getDistance(a, b),
};
return;
}

const midpoint = getMidpoint(a, b);
const distance = getDistance(a, b);
const panDx = midpoint.x - touchPanState.two.midpoint.x;
const panDy = midpoint.y - touchPanState.two.midpoint.y;
const scaleDelta = touchPanState.two.distance > 0
? (distance - touchPanState.two.distance) / touchPanState.two.distance
: 0;

const emitted = emitIf(panDx !== 0 || panDy !== 0 || scaleDelta !== 0, {
gesture: "TwoFinger",
dx: panDx,
dy: panDy,
scale_delta: scaleDelta,
});

if (emitted) {
touchPanState.two.midpoint = midpoint;
touchPanState.two.distance = distance;
}
};

canvas.addEventListener(
"pointerdown",
(event) => {
if (event.pointerType !== "touch") return;
if (!event.target) return;
const point = toVec(event);
pointerState.set(event.pointerId, point);
event.currentTarget.setPointerCapture(event.pointerId);
touchPanState.one = point;
touchPanState.two = {
active: false,
midpoint: null,
distance: null,
};
event.preventDefault();
},
{ passive: false },
);

canvas.addEventListener(
"pointermove",
(event) => {
if (event.pointerType !== "touch") return;
if (!pointerState.has(event.pointerId)) return;

const point = toVec(event);
pointerState.set(event.pointerId, point);

if (pointerState.size === 1) {
updateSinglePointer(point);
} else if (pointerState.size >= 2) {
const points = [...pointerState.values()];
const a = points[0];
const b = points[1];
updateTwoPointer(a, b);
}

event.preventDefault();
},
{ passive: false },
);

canvas.addEventListener(
"pointerup",
removePointer,
{ passive: false },
);
canvas.addEventListener("pointercancel", removePointer, {
passive: false,
});
canvas.addEventListener("pointerleave", removePointer, {
passive: false,
});
canvas.addEventListener("pointerout", removePointer, { passive: false });
canvas.addEventListener("pointerlostcapture", removePointer);
} else {
canvas.addEventListener(
"touchstart",
(event) => {
if (!event.target) return;
if (event.touches.length === 1) {
touchPanState.one = toVec(event.touches[0]);
touchPanState.two.active = false;
} else if (event.touches.length === 2) {
const a = toVec(event.touches[0]);
const b = toVec(event.touches[1]);
touchPanState.two = {
active: true,
midpoint: getMidpoint(a, b),
distance: getDistance(a, b),
};
touchPanState.one = null;
}
event.preventDefault();
},
{ passive: false },
);

canvas.addEventListener(
"touchmove",
(event) => {
if (event.touches.length === 1 && touchPanState.one) {
const current = toVec(event.touches[0]);
const dx = current.x - touchPanState.one.x;
const dy = current.y - touchPanState.one.y;

if (dx !== 0 || dy !== 0) {
emitTouchGesture({
gesture: "Rotate",
dx,
dy,
scale_delta: 0,
});
touchPanState.one = current;
}
event.preventDefault();
return;
}

if (event.touches.length === 2 && touchPanState.two.active) {
const a = toVec(event.touches[0]);
const b = toVec(event.touches[1]);
const midpoint = getMidpoint(a, b);
const distance = getDistance(a, b);

const panDx = midpoint.x - touchPanState.two.midpoint.x;
const panDy = midpoint.y - touchPanState.two.midpoint.y;
const scaleDelta = touchPanState.two.distance > 0
? (distance - touchPanState.two.distance) /
touchPanState.two.distance
: 0;

if (panDx !== 0 || panDy !== 0 || scaleDelta !== 0) {
emitTouchGesture({
gesture: "TwoFinger",
dx: panDx,
dy: panDy,
scale_delta: scaleDelta,
});
touchPanState.two.midpoint = midpoint;
touchPanState.two.distance = distance;
}

event.preventDefault();
}
},
{ passive: false },
);
}

const clearTouch = () => clearTouchState();
canvas.addEventListener("touchend", clearTouch, { passive: false });
canvas.addEventListener("touchcancel", clearTouch, { passive: false });
canvas.addEventListener("touchleave", clearTouch, { passive: false });
}
})();
16 changes: 16 additions & 0 deletions vizmat-app/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ html[data-theme="light"] {
html, body {
width: 100%;
height: 100%;
touch-action: none;
overscroll-behavior: none;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
body {
margin: 0;
Expand All @@ -70,6 +75,7 @@ body {
color: var(--accent);
}
header {
touch-action: none;
position: fixed;
top: 10px;
right: 10px;
Expand Down Expand Up @@ -97,6 +103,7 @@ header a img {
height: 20px;
}
.hint {
touch-action: none;
position: fixed;
left: 12px;
bottom: 12px;
Expand Down Expand Up @@ -124,6 +131,11 @@ canvas {
height: 100%;
display: block;
background: transparent;
touch-action: none;
overscroll-behavior: none;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
.canvas-shell {
position: fixed;
Expand All @@ -137,6 +149,10 @@ canvas {
border: 1px solid var(--line);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45);
background: var(--canvas-bg);
touch-action: none;
overscroll-behavior: none;
user-select: none;
-webkit-user-select: none;
}
.loader {
position: fixed;
Expand Down
Loading