relocating into library files, overlay and hover effect and scaling

This commit is contained in:
waltem01 2023-11-30 08:37:31 +01:00
parent 0e33907fe9
commit bde03f5da4
7 changed files with 215 additions and 71 deletions

View File

@ -0,0 +1,36 @@
export interface Color {
r: number;
g: number;
b: number;
}
export function getContrastColor(hexColor: string): string {
const rgb = hexToRgb(hexColor);
const luminance = getLuminance(rgb ?? ({ r: 0, g: 0, b: 0 } as Color));
return luminance > 0.5 ? '000000' : 'FFFFFF';
}
export function hexToRgb(hex: string): Color | null {
const result = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
}
: null;
}
export function getLuminance(rgb: Color): number {
const a = [rgb.r, rgb.g, rgb.b].map((v) => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
}
export function rgbToHex(color: Color): string {
return (Object.entries(color) as [string, number][])
.map((e) => e[1].toString(16).padStart(2, '0'))
.join('');
}

View File

@ -0,0 +1,21 @@
export interface Coordinates {
x: number;
y: number;
}
export function detectGamepad() {
const gps = navigator.getGamepads();
if (gps.length === 0) return null;
return gps[0];
}
export function gamepadButtonPress(gamepad: Gamepad | null, gameCoords: Coordinates) {
if (!gamepad) return;
gamepad.axes.forEach((axis, index) => {
if (Math.abs(axis) !== 1) return;
if (index === 1) gameCoords.y += axis;
if (index === 0) gameCoords.x += axis;
});
}

View File

@ -0,0 +1,14 @@
export interface MatrixCell {
xIndex: number;
yIndex: number;
color: string;
}
export function initializeMatrix(scaling: number): MatrixCell[][] {
return Array.from({ length: 192 / scaling }, (_, yIndex) =>
Array.from(
{ length: 192 / scaling },
(_, xIndex) => ({ xIndex, yIndex, color: '000000' }) as MatrixCell
)
) as MatrixCell[][];
}

View File

@ -0,0 +1,3 @@
export function toRadians(degrees: number): number {
return (degrees / 180) * Math.PI;
}

View File

@ -0,0 +1,3 @@
export interface APIResponse {
success: boolean;
}

View File

@ -1,13 +1,10 @@
<script lang="ts"> <script lang="ts">
interface MatrixResponse { import { detectGamepad, gamepadButtonPress } from '$lib/client/gamepad';
success: boolean; import { rgbToHex, type Color, getContrastColor } from '$lib/client/color';
} import { initializeMatrix } from '$lib/client/matrix';
import { toRadians } from '$lib/client/miscellaneous';
interface MatrixCell { import type { APIResponse } from '$lib/interfaces';
xIndex: number; import { onMount } from 'svelte';
yIndex: number;
color: string;
}
async function get(event: SubmitEvent) { async function get(event: SubmitEvent) {
clearMatrix(); clearMatrix();
@ -17,7 +14,7 @@
const response = await fetch(`http://localhost:8080/${form.dataset.endpoint}`, { const response = await fetch(`http://localhost:8080/${form.dataset.endpoint}`, {
mode: 'cors' mode: 'cors'
}); });
const mdata = (await response.json()) as MatrixResponse; const mdata = (await response.json()) as APIResponse;
if (!mdata.success) alert(`Error while processing '${form.dataset.endpoint}'!`); if (!mdata.success) alert(`Error while processing '${form.dataset.endpoint}'!`);
} }
@ -49,7 +46,7 @@
mode: 'cors', mode: 'cors',
body: fdata body: fdata
}); });
const mdata = (await response.json()) as MatrixResponse; const mdata = (await response.json()) as APIResponse;
if (!mdata.success) alert(`Error while processing '${form.dataset.endpoint}'!`); if (!mdata.success) alert(`Error while processing '${form.dataset.endpoint}'!`);
} }
@ -60,7 +57,7 @@
const y = parseInt((formData.get('y') as string) ?? '191'); const y = parseInt((formData.get('y') as string) ?? '191');
const r = parseInt((formData.get('r') as string) ?? '95'); const r = parseInt((formData.get('r') as string) ?? '95');
const hex = colorToHex(); const hex = rgbToHex(pixelColor);
for (let a = 0; a < 360; a++) { for (let a = 0; a < 360; a++) {
const nx = Math.round(Math.cos(toRadians(a)) * r + x); const nx = Math.round(Math.cos(toRadians(a)) * r + x);
const ny = Math.round(Math.sin(toRadians(a)) * r + y); const ny = Math.round(Math.sin(toRadians(a)) * r + y);
@ -69,17 +66,13 @@
} }
} }
function toRadians(degrees: number): number {
return (degrees / 180) * Math.PI;
}
function drawRectangle(formData: FormData) { function drawRectangle(formData: FormData) {
const x = parseInt((formData.get('x') as string) ?? '191'); const x = parseInt((formData.get('x') as string) ?? '191');
const y = parseInt((formData.get('y') as string) ?? '191'); const y = parseInt((formData.get('y') as string) ?? '191');
const w = parseInt((formData.get('w') as string) ?? '191'); const w = parseInt((formData.get('w') as string) ?? '191');
const h = parseInt((formData.get('h') as string) ?? '191'); const h = parseInt((formData.get('h') as string) ?? '191');
const hex = colorToHex(); const hex = rgbToHex(pixelColor);
for (let j = y; j < y + h; j++) for (let i = x; i < x + w; i++) matrix[j][i].color = hex; for (let j = y; j < y + h; j++) for (let i = x; i < x + w; i++) matrix[j][i].color = hex;
} }
@ -87,7 +80,7 @@
const x = parseInt((formData.get('x') as string) ?? '191'); const x = parseInt((formData.get('x') as string) ?? '191');
const y = parseInt((formData.get('y') as string) ?? '191'); const y = parseInt((formData.get('y') as string) ?? '191');
matrix[y][x].color = colorToHex(); matrix[y][x].color = rgbToHex(pixelColor);
} }
function clearMatrix() { function clearMatrix() {
@ -95,44 +88,82 @@
} }
function colorSelect(formData: FormData) { function colorSelect(formData: FormData) {
pixelColor[0] = parseInt((formData.get('r') as string) ?? '255'); pixelColor.r = parseInt((formData.get('r') as string) ?? '255');
pixelColor[1] = parseInt((formData.get('g') as string) ?? '255'); pixelColor.g = parseInt((formData.get('g') as string) ?? '255');
pixelColor[2] = parseInt((formData.get('b') as string) ?? '255'); pixelColor.b = parseInt((formData.get('b') as string) ?? '255');
} }
async function setMousePixel(event: MouseEvent) { async function setMousePixel(event: MouseEvent) {
if (!mousePressed) return; if (!mousePressed) return;
const parent = event.target as HTMLTableCellElement; const parent = event.target as HTMLTableCellElement;
const x = parseInt(parent.dataset.x ?? '0'); let x = parseInt(parent.dataset.x ?? '0');
const y = parseInt(parent.parentElement?.dataset.y ?? '0'); let y = parseInt(parent.parentElement?.dataset.y ?? '0');
matrix[y][x].color = colorToHex(); const next = rgbToHex(pixelColor);
if (matrix[y][x].color === next) return;
matrix[y][x].color = next;
const fdata = new FormData(); const fdata = new FormData();
const isScaled = scaling > 1;
if (isScaled) {
x *= scaling;
y *= scaling;
fdata.append('w', scaling.toString());
fdata.append('h', scaling.toString());
}
fdata.append('x', x.toString()); fdata.append('x', x.toString());
fdata.append('y', y.toString()); fdata.append('y', y.toString());
// TODO: Calling API via server-side // TODO: Calling API via server-side
const response = await fetch('http://localhost:8080/pixel', { const response = await fetch(`http://localhost:8080/${isScaled ? 'rectangle' : 'pixel'}`, {
method: 'POST', method: 'POST',
mode: 'cors', mode: 'cors',
body: fdata body: fdata
}); });
const mdata = (await response.json()) as MatrixResponse; const mdata = (await response.json()) as APIResponse;
if (!mdata.success) alert("Error while processing 'pixel'!"); if (!mdata.success) alert("Error while processing 'pixel'!");
} }
function colorToHex(): string { function correctSlider(event: Event) {
return pixelColor.map((e) => e.toString(16).padStart(2, '0')).join(''); const slider = event.target as HTMLInputElement;
let current = parseInt(slider.value);
while (192 % current !== 0) current--;
scaling = current;
padding = 0.1875 * scaling;
matrix = initializeMatrix(scaling);
} }
const matrix = Array.from({ length: 192 }, (_, yIndex) => let scaling = 3,
Array.from({ length: 192 }, (_, xIndex) => ({ xIndex, yIndex, color: '000000' }) as MatrixCell) padding = 0.5625;
) as MatrixCell[][];
const pixelColor = [255, 255, 255];
let mousePressed = false; let mousePressed = false;
let gamepad: Gamepad | null = null;
let matrix = initializeMatrix(scaling);
const gameCoords = { x: 0, y: 0 };
const pixelColor = { r: 255, g: 255, b: 255 } as Color;
onMount(() => {
setInterval(() => {
if (gamepad) return;
gamepad = detectGamepad();
}, 1000);
setInterval(() => gamepadButtonPress(gamepad, gameCoords), 100);
window.addEventListener('gamepadconnected', (e) => {
if (e.gamepad.index === 0) gamepad = e.gamepad;
});
window.addEventListener('gamepaddisconnected', (e) => {
if (e.gamepad.id === gamepad?.id) gamepad = null;
});
});
</script> </script>
<h1 class="text-4xl font-bold m-5 text-center">Matrix Control Panel</h1> <h1 class="text-4xl font-bold m-5 text-center">Matrix Control Panel</h1>
@ -203,7 +234,7 @@
required required
/> />
<input <input
class="border-2 border-black border-b-0 hover:bg-gray-200" class="border-2 border-black border-b-0 hover:bg-gray-200 cursor-pointer"
id="color" id="color"
name="color" name="color"
type="submit" type="submit"
@ -225,7 +256,7 @@
name="x" name="x"
type="number" type="number"
min="0" min="0"
max="191" max={192 / scaling - 1}
step="1" step="1"
value="0" value="0"
required required
@ -237,7 +268,7 @@
name="y" name="y"
type="number" type="number"
min="0" min="0"
max="191" max={192 / scaling - 1}
step="1" step="1"
value="0" value="0"
required required
@ -266,7 +297,7 @@
name="x" name="x"
type="number" type="number"
min="0" min="0"
max="191" max={192 / scaling - 1}
step="1" step="1"
value="177" value="177"
required required
@ -278,7 +309,7 @@
name="y" name="y"
type="number" type="number"
min="0" min="0"
max="191" max={192 / scaling - 1}
step="1" step="1"
value="0" value="0"
required required
@ -289,7 +320,7 @@
name="w" name="w"
type="number" type="number"
min="0" min="0"
max="191" max={192 / scaling - 1}
step="1" step="1"
value="15" value="15"
required required
@ -301,7 +332,7 @@
name="h" name="h"
type="number" type="number"
min="0" min="0"
max="191" max={192 / scaling - 1}
step="1" step="1"
value="15" value="15"
required required
@ -329,7 +360,7 @@
name="x" name="x"
type="number" type="number"
min="0" min="0"
max="191" max={192 / scaling - 1}
step="1" step="1"
value="20" value="20"
required required
@ -341,7 +372,7 @@
name="y" name="y"
type="number" type="number"
min="0" min="0"
max="191" max={192 / scaling - 1}
step="1" step="1"
value="171" value="171"
required required
@ -352,7 +383,7 @@
name="r" name="r"
type="number" type="number"
min="0" min="0"
max="95" max={96 / scaling - 1}
step="1" step="1"
value="20" value="20"
required required
@ -367,35 +398,35 @@
</form> </form>
<form class="grid grid-cols-8" method="POST" data-endpoint="text" on:submit|preventDefault={post}> <form class="grid grid-cols-8" method="POST" data-endpoint="text" on:submit|preventDefault={post}>
<label class="border-2 border-black border-r-0 pl-1" for="text">Text:</label><label <label class="border-2 border-black border-r-0 border-b-0 pl-1" for="text">Text:</label><label
class="border-2 border-black border-r-0 pl-1" class="border-2 border-black border-r-0 border-b-0 pl-1"
for="tx">X:</label for="tx">X:</label
> >
<input <input
class="border-2 border-black border-r-0 cursor-text pl-1 bg-gray-100" class="border-2 border-black border-r-0 border-b-0 cursor-text pl-1 bg-gray-100"
id="tx" id="tx"
name="x" name="x"
type="number" type="number"
min="0" min="0"
max="191" max="20"
step="1" step="1"
value="8" value="8"
required required
/> />
<label class="border-2 border-black border-r-0 pl-1" for="ty">Y:</label> <label class="border-2 border-black border-r-0 border-b-0 pl-1" for="ty">Y:</label>
<input <input
class="border-2 border-black border-r-0 cursor-text pl-1 bg-gray-100" class="border-2 border-black border-r-0 border-b-0 cursor-text pl-1 bg-gray-100"
id="ty" id="ty"
name="y" name="y"
type="number" type="number"
min="0" min="0"
max="191" max="9"
step="1" step="1"
value="9" value="9"
required required
/><label class="border-2 border-black border-r-0 pl-1" for="tin">Input:</label> /><label class="border-2 border-black border-r-0 border-b-0 pl-1" for="tin">Input:</label>
<input <input
class="border-2 border-black border-r-0 cursor-text pl-1 pr-1 bg-gray-100" class="border-2 border-black border-r-0 border-b-0 cursor-text pl-1 pr-1 bg-gray-100"
id="tin" id="tin"
name="text" name="text"
type="text" type="text"
@ -403,32 +434,72 @@
required required
/> />
<input <input
class="border-2 border-black cursor-pointer hover:bg-gray-200" class="border-2 border-black border-b-0 cursor-pointer hover:bg-gray-200"
id="text" id="text"
name="text" name="text"
type="submit" type="submit"
value="Submit" value="Submit"
/> />
</form> </form>
<form class="grid grid-cols-3" on:submit|preventDefault>
<label class="border-2 border-black border-r-0 pl-1" for="text">Scaling:</label>
<div class="flex pl-1 pr-1 border-2 border-black border-r-0 bg-gray-100">
<input
class="w-full cursor-pointer"
id="scale"
name="scale"
type="range"
min="1"
max="64"
step="1"
bind:value={scaling}
on:change={correctSlider}
required
/>
</div>
<input
class="border-2 border-black cursor-text pl-1 bg-gray-100"
type="number"
min="1"
max="64"
step="1"
bind:value={scaling}
on:change={correctSlider}
required
/>
</form>
</div> </div>
<div class="flex justify-center"> <div class="flex justify-center">
<!-- svelte-ignore a11y-no-noninteractive-element-interactions --> <table id="main" class="mt-5 border-2 border-black">
<table
class="mt-5"
on:mousedown|preventDefault={() => (mousePressed = true)}
on:mouseup|preventDefault={() => (mousePressed = false)}
>
{#each matrix as row} {#each matrix as row}
<tr data-y={row[0].yIndex}> <tr>
{#each row as cell} {#each row as cell}
<td <td style="padding: {padding}rem; background-color: #{cell.color}" />
data-x={cell.xIndex}
class="p-0.75"
style="background-color: #{cell.color};"
on:mouseenter={setMousePixel}
/>
{/each} {/each}
</tr> </tr>
{/each} {/each}
</table> </table>
<div class="absolute z-10">
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<table
id="overlay"
class="mt-5 border-2 border-transparent"
on:mousedown|preventDefault={() => (mousePressed = true)}
on:mouseup|preventDefault={() => (mousePressed = false)}
>
{#each matrix as row}
<tr data-y={row[0].yIndex}>
{#each row as cell}
<td
data-x={cell.xIndex}
style="padding: {padding}rem; background-color: #{getContrastColor(cell.color)}"
class="opacity-0 hover:opacity-50"
on:mousemove={setMousePixel}
/>
{/each}
</tr>
{/each}
</table>
</div>
</div> </div>

View File

@ -2,11 +2,7 @@
export default { export default {
content: ['./src/**/*.{html,js,svelte,ts}'], content: ['./src/**/*.{html,js,svelte,ts}'],
theme: { theme: {
extend: { extend: {}
spacing: {
0.75: '0.1875rem'
}
}
}, },
plugins: [] plugins: []
}; };