silphius/app/react-components/devtools/tiles.jsx
2024-07-10 14:12:48 -05:00

231 lines
6.5 KiB
JavaScript

import {useEffect, useRef, useState} from 'react';
import {useClient} from '@/context/client.js';
import {useEcs} from '@/context/ecs.js';
import useRect from '@/util/react-hooks/use-rect.js';
import styles from './tiles.module.css';
export default function Tiles({eventsChannel}) {
const client = useClient();
const wrapperRef = useRef();
const imageRef = useRef();
const imageRect = useRect(imageRef);
const [indices, setIndices] = useState([]);
const [selection, setSelection] = useState({x: 0, y: 0, w: 1, h: 1});
const [moveStart, setMoveStart] = useState();
const [layer, setLayer] = useState(0);
const [brush, setBrush] = useState(0);
const [stamp, setStamp] = useState([]);
const [ecs] = useEcs();
useEffect(() => {
if (!ecs) {
return false;
}
const master = ecs.get(1);
if (!master) {
return false;
}
const {TileLayers} = master;
const {area, tileSize} = TileLayers.layer(0);
function onClick({x, y}) {
const at = {
x: Math.floor(x / tileSize.x),
y: Math.floor(y / tileSize.y),
};
if (at.x < 0 || at.y < 0 || at.x >= area.x || at.y >= area.y) {
return;
}
client.send({
type: 'AdminAction',
payload: {
type: 'paint',
value: {
brush,
layer,
stamp: {
at,
data: stamp,
},
},
},
});
}
eventsChannel.addListener('click', onClick);
return () => {
eventsChannel.removeListener('click', onClick);
};
});
if (!ecs) {
return false;
}
const master = ecs.get(1);
if (!master) {
return false;
}
const {TileLayers} = master;
const {sourceJson, tileSize} = TileLayers.layer(0);
const {w, h} = sourceJson.meta.size;
const factor = (imageRect?.width ?? 1) / w;
const wt = w / tileSize.x;
return (
<div className={styles.tiles}>
<form>
<div className={styles.paintBar}>
<div className={styles.layer}>
<label>
<span>Layer</span>
<select
onChange={(event) => {
setLayer(event.target.value)
}}
value={layer}
>
{
Object.keys(TileLayers.layers)
.map((layer, i) => (
<option
key={i}
value={i}
>
{i}
</option>
))
}
</select>
</label>
</div>
<div className={styles.brush}>
<label>
<span>Brush</span>
<select
onChange={(event) => {
setBrush(event.target.value)
}}
value={brush}
>
<option>Paint</option>
</select>
</label>
</div>
</div>
</form>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div
onMouseDown={(event) => {
const wrapperRect = wrapperRef.current.getBoundingClientRect();
const {left, top} = wrapperRect;
const c = {
x: event.clientX,
y: event.clientY + wrapperRef.current.scrollTop,
};
if (
c.x - left >= (w * factor)
|| c.y - top >= (h * factor)
) {
return;
}
const x = Math.floor((c.x - left) / (tileSize.x * factor));
const y = Math.floor((c.y - top) / (tileSize.y * factor));
setMoveStart({x, y});
setSelection({x, y, w: 1, h: 1});
setIndices([y * wt + x]);
}}
onMouseMove={(event) => {
if (0 === event.buttons) {
setMoveStart();
return;
}
if (!moveStart) {
return;
}
const {x: sx, y: sy} = moveStart;
const wrapperRect = wrapperRef.current.getBoundingClientRect();
const {left, top} = wrapperRect;
const c = {
x: event.clientX,
y: event.clientY + wrapperRef.current.scrollTop,
};
const x = Math.floor(
Math.max(0, Math.min((w * factor) - 1, (c.x - left)) / (tileSize.x * factor)),
);
const y = Math.floor(
Math.max(0, Math.min((h * factor) - 1, (c.y - top)) / (tileSize.y * factor)),
);
const mx = Math.min(sx, x);
const my = Math.min(sy, y);
const sw = Math.abs(sx - x) + 1;
const sh = Math.abs(sy - y) + 1;
const newSelection = {
x: mx,
y: my,
w: sw,
h: sh,
};
if (
selection.x === newSelection.x
&& selection.y === newSelection.y
&& selection.w === newSelection.w
&& selection.h === newSelection.h
) {
return;
}
setSelection(newSelection);
const stamp = [];
const tw = w / tileSize.x;
for (let iy = 0; iy < sh; ++iy) {
const row = [];
for (let ix = 0; ix < sw; ++ix) {
row.push((y + iy) * tw + x + ix);
}
stamp.push(row);
}
setStamp(stamp);
const indices = [];
for (let sy = 0; sy < selection.h; ++sy) {
for (let sx = 0; sx < selection.w; ++sx) {
indices.push(((selection.y + sy) * wt) + (selection.x + sx));
}
}
setIndices(indices);
}}
className={styles.selectionWrapper}
ref={wrapperRef}
>
<div
className={styles.selection}
style={{
top: selection.y * tileSize.y * factor,
left: selection.x * tileSize.x * factor,
height: selection.h * tileSize.x * factor,
width: selection.w * tileSize.y * factor,
}}
/>
<img
alt="tileset"
ref={imageRef}
src={TileLayers.layer(0).source.replace('.json', '.png')}
/>
</div>
<div
className={styles.status}
>
<code className={styles.selectionStatus}>
Sel:
{' {'}
{selection.x}{', '}
{selection.y}{', '}
{selection.w}{', '}
{selection.h}
{'}'}
</code>
<code>
Idx:
{' ['}
{indices.join(', ')}
{']'}
</code>
</div>
</div>
);
}