fix: scaling and scrolling

This commit is contained in:
cha0s 2024-07-08 17:31:07 -05:00
parent 6ee9176756
commit b592ecb56b
6 changed files with 89 additions and 25 deletions

View File

@ -7,7 +7,17 @@
.devtools { .devtools {
background-color: #444444; background-color: #444444;
color: white; color: white;
max-height: 100%; height: 100%;
overflow-y: auto; overflow: hidden;
padding: 16px; padding: 16px;
width: 100%;
} }
.devtools > :global(.react-tabs) {
display: flex;
flex-direction: column;
height: 100%;
> :global(.react-tabs__tab-panel) {
overflow-y: auto;
}
}

View File

@ -1,6 +1,7 @@
import {useRef, useState} from 'react'; import {useRef, useState} from 'react';
import {useEcs} from '@/context/ecs.js'; import {useEcs} from '@/context/ecs.js';
import useRect from '@/util/react-hooks/use-rect.js';
import styles from './tiles.module.css'; import styles from './tiles.module.css';
@ -11,8 +12,10 @@ export default function Tiles({
setLayer, setLayer,
setStamp, setStamp,
}) { }) {
const offsetRef = useRef(); const wrapperRef = useRef();
const [selection, setSelection] = useState({x: 0, y: 0, w: 2, h: 2}); const imageRef = useRef();
const imageRect = useRect(imageRef);
const [selection, setSelection] = useState({x: 0, y: 0, w: 1, h: 1});
const [moveStart, setMoveStart] = useState(); const [moveStart, setMoveStart] = useState();
const [ecs] = useEcs(); const [ecs] = useEcs();
if (!ecs) { if (!ecs) {
@ -25,6 +28,7 @@ export default function Tiles({
const {TileLayers} = master; const {TileLayers} = master;
const {sourceJson, tileSize} = TileLayers.layer(0); const {sourceJson, tileSize} = TileLayers.layer(0);
const {w, h} = sourceJson.meta.size; const {w, h} = sourceJson.meta.size;
const factor = (imageRect?.width ?? 1) / w;
return ( return (
<div className={styles.tiles}> <div className={styles.tiles}>
<form> <form>
@ -57,7 +61,7 @@ export default function Tiles({
Brush: Brush:
<select <select
onChange={(event) => { onChange={(event) => {
setLayer(event.target.value) setBrush(event.target.value)
}} }}
value={brush} value={brush}
> >
@ -65,40 +69,36 @@ export default function Tiles({
</select> </select>
</label> </label>
</div> </div>
</div> </div>
</form> </form>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div <div
onMouseDown={(event) => { onMouseDown={(event) => {
if (!offsetRef.current) { const wrapperRect = wrapperRef.current.getBoundingClientRect();
return; const {left, top} = wrapperRect;
}
const {left, top} = offsetRef.current.getBoundingClientRect();
if ( if (
event.clientX - left >= w event.clientX - left >= (w * factor)
|| event.clientY - top >= h || event.clientY - top >= (h * factor)
) { ) {
return; return;
} }
const x = Math.floor((event.clientX - left) / tileSize.x); const x = Math.floor((event.clientX - left) / (tileSize.x * factor));
const y = Math.floor((event.clientY - top) / tileSize.y); const y = Math.floor((event.clientY - top) / (tileSize.y * factor));
setMoveStart({x, y}); setMoveStart({x, y});
setSelection({x, y, w: 1, h: 1}); setSelection({x, y, w: 1, h: 1});
}} }}
onMouseMove={(event) => { onMouseMove={(event) => {
if (!offsetRef.current) {
return;
}
if (!moveStart) { if (!moveStart) {
return; return;
} }
const {x: sx, y: sy} = moveStart; const {x: sx, y: sy} = moveStart;
const {left, top} = offsetRef.current.getBoundingClientRect(); const wrapperRect = wrapperRef.current.getBoundingClientRect();
const {left, top} = wrapperRect;
const x = Math.floor( const x = Math.floor(
Math.max(0, Math.min(w - 1, (event.clientX - left)) / tileSize.x), Math.max(0, Math.min((w * factor) - 1, (event.clientX - left)) / (tileSize.x * factor)),
); );
const y = Math.floor( const y = Math.floor(
Math.max(0, Math.min(h - 1, (event.clientY - top)) / tileSize.y), Math.max(0, Math.min((h * factor) - 1, (event.clientY - top)) / (tileSize.y * factor)),
); );
const mx = Math.min(sx, x); const mx = Math.min(sx, x);
const my = Math.min(sy, y); const my = Math.min(sy, y);
@ -119,19 +119,20 @@ export default function Tiles({
setStamp(stamp); setStamp(stamp);
}} }}
className={styles.selectionWrapper} className={styles.selectionWrapper}
ref={offsetRef} ref={wrapperRef}
> >
<div <div
className={styles.selection} className={styles.selection}
style={{ style={{
top: selection.y * tileSize.y, top: selection.y * tileSize.y * factor,
left: selection.x * tileSize.x, left: selection.x * tileSize.x * factor,
height: selection.h * tileSize.x, height: selection.h * tileSize.x * factor,
width: selection.w * tileSize.y, width: selection.w * tileSize.y * factor,
}} }}
/> />
<img <img
alt="tileset" alt="tileset"
ref={imageRef}
src={TileLayers.layer(0).source.replace('.json', '.png')} src={TileLayers.layer(0).source.replace('.json', '.png')}
/> />
</div> </div>

View File

@ -10,6 +10,7 @@
.selectionWrapper img { .selectionWrapper img {
user-select: none; user-select: none;
width: 100%;
} }
.selection { .selection {

16
app/util/react-hooks/use-rect.js vendored Normal file
View File

@ -0,0 +1,16 @@
import {useLayoutEffect, useState} from 'react'
import useResizeObserver from '@react-hook/resize-observer'
export default function useRect(target) {
const [rect, setRect] = useState();
useLayoutEffect(() => {
setRect(target.current.getBoundingClientRect())
}, [target]);
useResizeObserver(
target,
(entry) => {
setRect(entry.target.getBoundingClientRect());
},
);
return rect;
}

35
package-lock.json generated
View File

@ -15,6 +15,7 @@
"@pixi/react": "^7.1.2", "@pixi/react": "^7.1.2",
"@pixi/spritesheet": "^7.4.2", "@pixi/spritesheet": "^7.4.2",
"@pixi/tilemap": "^4.1.0", "@pixi/tilemap": "^4.1.0",
"@react-hook/resize-observer": "^2.0.1",
"@remix-run/express": "^2.9.2", "@remix-run/express": "^2.9.2",
"@remix-run/node": "^2.9.2", "@remix-run/node": "^2.9.2",
"@remix-run/react": "^2.9.2", "@remix-run/react": "^2.9.2",
@ -3020,6 +3021,11 @@
"integrity": "sha512-Lg3PnLp0QXpxwLIAuuJboLeRaIhrgJjeuh797QADg3xz8wGLugQOS5DpsE8A6i6Adgzf+bacllkKZG3J0tGfDw==", "integrity": "sha512-Lg3PnLp0QXpxwLIAuuJboLeRaIhrgJjeuh797QADg3xz8wGLugQOS5DpsE8A6i6Adgzf+bacllkKZG3J0tGfDw==",
"dev": true "dev": true
}, },
"node_modules/@juggle/resize-observer": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
},
"node_modules/@leodeslf/simplex-noise": { "node_modules/@leodeslf/simplex-noise": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@leodeslf/simplex-noise/-/simplex-noise-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@leodeslf/simplex-noise/-/simplex-noise-1.0.0.tgz",
@ -3971,6 +3977,35 @@
} }
} }
}, },
"node_modules/@react-hook/latest": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz",
"integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==",
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/@react-hook/passive-layout-effect": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz",
"integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==",
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/@react-hook/resize-observer": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@react-hook/resize-observer/-/resize-observer-2.0.1.tgz",
"integrity": "sha512-9PCX9grWfxdPizY8ohr+X4IkV1JhGMWr2Nm4ngbg6IcAIv0WBs7YoJcNBqYl22OqPHr5eOMItGcStZrmj2mbmQ==",
"dependencies": {
"@juggle/resize-observer": "^3.3.1",
"@react-hook/latest": "^1.0.2",
"@react-hook/passive-layout-effect": "^1.2.0"
},
"peerDependencies": {
"react": ">=18"
}
},
"node_modules/@remix-run/dev": { "node_modules/@remix-run/dev": {
"version": "2.9.2", "version": "2.9.2",
"resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.9.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.9.2.tgz",

View File

@ -22,6 +22,7 @@
"@pixi/react": "^7.1.2", "@pixi/react": "^7.1.2",
"@pixi/spritesheet": "^7.4.2", "@pixi/spritesheet": "^7.4.2",
"@pixi/tilemap": "^4.1.0", "@pixi/tilemap": "^4.1.0",
"@react-hook/resize-observer": "^2.0.1",
"@remix-run/express": "^2.9.2", "@remix-run/express": "^2.9.2",
"@remix-run/node": "^2.9.2", "@remix-run/node": "^2.9.2",
"@remix-run/react": "^2.9.2", "@remix-run/react": "^2.9.2",