mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-06-30 11:23:33 +00:00
Warp effect (#2501)
* created starfield effect for spacedrop * fixed starfield hover effects * changed colours/styling of stars * eslint fix
This commit is contained in:
parent
96a45ee76c
commit
89645edc73
|
@ -3,18 +3,14 @@ import { NodeIdParamsSchema } from '~/app/route-schemas';
|
|||
import { Icon } from '~/components';
|
||||
import { useRouteTitle, useZodRouteParams } from '~/hooks';
|
||||
import { hardwareModelToIcon } from '~/util/hardware';
|
||||
|
||||
import { TopBarPortal } from '../TopBar/Portal';
|
||||
import StarfieldEffect from './StarfieldEffect'; // Import the StarfieldEffect component
|
||||
|
||||
export const Component = () => {
|
||||
const { id: _nodeId } = useZodRouteParams(NodeIdParamsSchema);
|
||||
// we encode/decode because nodeId has special characters and I'm not willing to change that rn
|
||||
const nodeId = decodeURIComponent(_nodeId);
|
||||
|
||||
const peers = usePeers();
|
||||
|
||||
const peer = peers.get(nodeId);
|
||||
|
||||
const title = useRouteTitle(peer?.metadata?.name || 'Peer');
|
||||
|
||||
return (
|
||||
|
@ -39,8 +35,12 @@ export const Component = () => {
|
|||
{peer?.metadata.operating_system?.toString()}
|
||||
</h3>
|
||||
<h3 className="text-sm text-ink-dull">{nodeId}</h3>
|
||||
<div className="mt-8 flex h-28 w-96 items-center justify-center rounded-lg border border-dashed border-app-line p-4 text-sm font-medium text-ink-dull">
|
||||
Drop files here to send with Spacedrop
|
||||
|
||||
<div className="relative mt-8 flex h-28 w-96 items-center justify-center rounded-lg border border-solid border-app-line p-4 text-sm font-medium text-ink-dull">
|
||||
<StarfieldEffect />
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
Drop files here to send with Spacedrop
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
254
interface/app/$libraryId/peer/StarfieldEffect.tsx
Normal file
254
interface/app/$libraryId/peer/StarfieldEffect.tsx
Normal file
|
@ -0,0 +1,254 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
const StarfieldEffect: React.FC = () => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const resizeCanvas = () => {
|
||||
const scale = window.devicePixelRatio || 1;
|
||||
const width = canvas.parentElement?.clientWidth || 800;
|
||||
const height = canvas.parentElement?.clientHeight || 300;
|
||||
canvas.width = width * scale;
|
||||
canvas.height = height * scale;
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
ctx.scale(scale, scale);
|
||||
};
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
canvas.style.position = 'absolute';
|
||||
canvas.oncontextmenu = e => e.preventDefault();
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const pix = imageData.data;
|
||||
|
||||
const center = { x: canvas.width / 2, y: canvas.height / 2 };
|
||||
|
||||
let mouseActive = false;
|
||||
let mouseDown = false;
|
||||
let mousePos = { x: center.x, y: center.y };
|
||||
|
||||
let starSpeed = 20;
|
||||
const starSpeedMin = starSpeed;
|
||||
const starSpeedMax = 100;
|
||||
const starDistance = 5000;
|
||||
|
||||
let fov = 320;
|
||||
const fovMin = 210;
|
||||
const fovMax = fov;
|
||||
|
||||
const starHolderCount = 8000; // Increased star count
|
||||
const starHolder: any[] = [];
|
||||
const starBgHolder: any[] = [];
|
||||
|
||||
const backgroundColor = { r: 28, g: 29, b: 37, a: 255 };
|
||||
|
||||
const clearImageData = () => {
|
||||
for (let i = 0, l = pix.length; i < l; i += 4) {
|
||||
pix[i] = backgroundColor.r;
|
||||
pix[i + 1] = backgroundColor.g;
|
||||
pix[i + 2] = backgroundColor.b;
|
||||
pix[i + 3] = backgroundColor.a;
|
||||
}
|
||||
};
|
||||
|
||||
const setPixel = (x: number, y: number, r: number, g: number, b: number, a: number) => {
|
||||
const i = (x + y * canvas.width) * 4;
|
||||
pix[i] = r;
|
||||
pix[i + 1] = g;
|
||||
pix[i + 2] = b;
|
||||
pix[i + 3] = a;
|
||||
};
|
||||
|
||||
const setPixelAdditive = (x: number, y: number, r: number, g: number, b: number, a: number) => {
|
||||
const i = (x + y * canvas.width) * 4;
|
||||
pix[i] += r;
|
||||
pix[i + 1] += g;
|
||||
pix[i + 2] += b;
|
||||
pix[i + 3] = a;
|
||||
};
|
||||
|
||||
const drawLine = (x1: number, y1: number, x2: number, y2: number, r: number, g: number, b: number, a: number) => {
|
||||
const dx = Math.abs(x2 - x1);
|
||||
const dy = Math.abs(y2 - y1);
|
||||
const sx = x1 < x2 ? 1 : -1;
|
||||
const sy = y1 < y2 ? 1 : -1;
|
||||
let err = dx - dy;
|
||||
let lx = x1;
|
||||
let ly = y1;
|
||||
|
||||
const continueLoop = true
|
||||
while (continueLoop) {
|
||||
if (lx > 0 && lx < canvas.width && ly > 0 && ly < canvas.height) {
|
||||
setPixel(lx, ly, r, g, b, a);
|
||||
}
|
||||
if (lx === x2 && ly === y2) break;
|
||||
const e2 = 2 * err;
|
||||
if (e2 > -dx) {
|
||||
err -= dy;
|
||||
lx += sx;
|
||||
}
|
||||
if (e2 < dy) {
|
||||
err += dx;
|
||||
ly += sy;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addParticle = (x: number, y: number, z: number, ox: number, oy: number, oz: number) => {
|
||||
const particle = { x, y, z, ox, oy, x2d: 0, y2d: 0, color: { r: 0, g: 0, b: 0, a: 0 }, oColor: { r: 0, g: 0, b: 0, a: 0 }, w: 0, distance: 0, distanceTotal: 0 };
|
||||
return particle;
|
||||
};
|
||||
|
||||
const addParticles = () => {
|
||||
let x, y, z, colorValue, particle;
|
||||
for (let i = 0; i < starHolderCount / 3; i++) {
|
||||
x = Math.random() * 24000 - 12000;
|
||||
y = Math.random() * 4500 - 2250;
|
||||
z = Math.round(Math.random() * starDistance);
|
||||
colorValue = 185; // Adjusted color
|
||||
particle = addParticle(x, y, z, x, y, z);
|
||||
particle.color = { r: 171, g: 172, b: 185, a: 255 };
|
||||
starBgHolder.push(particle);
|
||||
}
|
||||
for (let i = 0; i < starHolderCount; i++) {
|
||||
x = Math.random() * 10000 - 5000;
|
||||
y = Math.random() * 10000 - 5000;
|
||||
z = Math.round(Math.random() * starDistance);
|
||||
colorValue = 185; // Adjusted color
|
||||
particle = addParticle(x, y, z, x, y, z);
|
||||
particle.color = { r: 171, g: 172, b: 185, a: 255 };
|
||||
particle.oColor = { r: 171, g: 172, b: 185, a: 255 };
|
||||
particle.w = 1;
|
||||
particle.distance = starDistance - z;
|
||||
particle.distanceTotal = Math.round(starDistance + fov - particle.w);
|
||||
starHolder.push(particle);
|
||||
}
|
||||
};
|
||||
|
||||
const animloop = () => {
|
||||
requestAnimationFrame(animloop);
|
||||
render();
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
clearImageData();
|
||||
let star, scale;
|
||||
|
||||
if (mouseActive) {
|
||||
starSpeed += 2;
|
||||
if (starSpeed > starSpeedMax) starSpeed = starSpeedMax;
|
||||
} else {
|
||||
starSpeed -= 1;
|
||||
if (starSpeed < starSpeedMin) starSpeed = starSpeedMin;
|
||||
}
|
||||
|
||||
fov += mouseActive ? -1 : 0.5;
|
||||
fov = Math.max(fovMin, Math.min(fovMax, fov));
|
||||
|
||||
const warpSpeedValue = starSpeed * (starSpeed / (starSpeedMax / 2));
|
||||
|
||||
for (const bgStar of starBgHolder) {
|
||||
star = bgStar;
|
||||
scale = fov / (fov + star.z);
|
||||
star.x2d = star.x * scale + center.x;
|
||||
star.y2d = star.y * scale + center.y;
|
||||
if (star.x2d > 0 && star.x2d < canvas.width && star.y2d > 0 && star.y2d < canvas.height) {
|
||||
setPixel(star.x2d | 0, star.y2d | 0, star.color.r, star.color.g, star.color.b, 255);
|
||||
}
|
||||
}
|
||||
|
||||
for (const mainStar of starHolder) {
|
||||
star = mainStar;
|
||||
star.z -= starSpeed;
|
||||
star.distance += starSpeed;
|
||||
if (star.z < -fov + star.w) {
|
||||
star.z = starDistance;
|
||||
star.distance = 0;
|
||||
}
|
||||
|
||||
const distancePercent = star.distance / star.distanceTotal;
|
||||
star.color.r = Math.floor(star.oColor.r * distancePercent);
|
||||
star.color.g = Math.floor(star.oColor.g * distancePercent);
|
||||
star.color.b = Math.floor(star.oColor.b * distancePercent);
|
||||
|
||||
scale = fov / (fov + star.z);
|
||||
star.x2d = star.x * scale + center.x;
|
||||
star.y2d = star.y * scale + center.y;
|
||||
|
||||
if (star.x2d > 0 && star.x2d < canvas.width && star.y2d > 0 && star.y2d < canvas.height) {
|
||||
setPixelAdditive(star.x2d | 0, star.y2d | 0, star.color.r, star.color.g, star.color.b, 255);
|
||||
}
|
||||
|
||||
if (starSpeed !== starSpeedMin) {
|
||||
const nz = star.z + warpSpeedValue;
|
||||
scale = fov / (fov + nz);
|
||||
const x2d = star.x * scale + center.x;
|
||||
const y2d = star.y * scale + center.y;
|
||||
if (x2d > 0 && x2d < canvas.width && y2d > 0 && y2d < canvas.height) {
|
||||
drawLine(star.x2d | 0, star.y2d | 0, x2d | 0, y2d | 0, star.color.r, star.color.g, star.color.b, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
center.x += (mousePos.x - center.x) * 0.015;
|
||||
if (!mouseActive) {
|
||||
center.x += (canvas.width / 2 - center.x) * 0.015;
|
||||
}
|
||||
};
|
||||
|
||||
const getMousePos = (event: MouseEvent) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
||||
};
|
||||
|
||||
const mouseMoveHandler = (event: MouseEvent) => {
|
||||
mousePos = getMousePos(event);
|
||||
};
|
||||
|
||||
const mouseEnterHandler = () => {
|
||||
mouseActive = true;
|
||||
};
|
||||
|
||||
const mouseLeaveHandler = () => {
|
||||
mouseActive = false;
|
||||
mouseDown = false;
|
||||
};
|
||||
|
||||
canvas.addEventListener('mousemove', mouseMoveHandler);
|
||||
canvas.addEventListener('mousedown', () => { mouseDown = true; });
|
||||
canvas.addEventListener('mouseup', () => { mouseDown = false; });
|
||||
canvas.addEventListener('mouseenter', mouseEnterHandler);
|
||||
canvas.addEventListener('mouseleave', mouseLeaveHandler);
|
||||
|
||||
addParticles();
|
||||
animloop();
|
||||
|
||||
return () => {
|
||||
canvas.removeEventListener('mousemove', mouseMoveHandler);
|
||||
canvas.removeEventListener('mousedown', () => { mouseDown = true; });
|
||||
canvas.removeEventListener('mouseup', () => { mouseDown = false; });
|
||||
canvas.removeEventListener('mouseenter', mouseEnterHandler);
|
||||
canvas.removeEventListener('mouseleave', mouseLeaveHandler);
|
||||
window.removeEventListener('resize', resizeCanvas);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvasRef} className="block size-full rounded-lg border border-gray-500 transition-all hover:scale-105">
|
||||
Drop files here to send with Spacedrop
|
||||
</canvas>
|
||||
);
|
||||
};
|
||||
|
||||
export default StarfieldEffect;
|
Loading…
Reference in a new issue