This commit is contained in:
cha0s 2022-04-11 14:42:41 -05:00
parent 76703cebf3
commit 65d5430ab0
4 changed files with 172 additions and 43 deletions

View File

@ -7,6 +7,9 @@ import {
Rectangle,
Vector,
} from '@avocado/math';
import {
Vector as VectorComponent,
} from '@avocado/math/persea';
import {
Context,
redo,
@ -44,7 +47,6 @@ const RoomComponent = ({
const [side, setSide] = useState(0);
const [room, setRoom] = useState();
const [roomRenderable, setRoomRenderable] = useState();
const [viewport, setViewport] = useState([0, 0]);
const roomSides = flecks.get('$avocado/topdown/persea.room-sides');
// Stage event stream.
const onRef = useCallback((stage) => {
@ -54,11 +56,11 @@ const RoomComponent = ({
if (room) {
const load = async () => {
await Promise.all(
resource.tiles.map(async (tiles, i) => {
await room.tiles[i].load(tiles);
const {area, tileSize} = room.tiles[i];
room.tiles.map(async (tiles, i) => {
await tiles.load(resource.tiles[i]);
const {area, tileSize} = tiles;
const viewport = Vector.mul(area, tileSize);
room.tiles[i].emit('update', Rectangle.compose([0, 0], viewport));
tiles.emit('update', Rectangle.compose([0, 0], viewport));
}),
);
if (resource.entities.length !== previousResource.entities.length) {
@ -82,12 +84,9 @@ const RoomComponent = ({
uri,
});
const roomRenderable = new (room.constructor.Renderable)(room, renderer);
const {area, tileSize} = room.tiles[0];
const viewport = Vector.mul(area, tileSize);
roomRenderable.renderChunksForExtent(Rectangle.compose([0, 0], viewport));
roomRenderable.renderChunksForExtent(Rectangle.compose([0, 0], room.size));
setRoom(room);
setRoomRenderable(roomRenderable);
setViewport(viewport);
};
loadRoom();
}
@ -105,7 +104,7 @@ const RoomComponent = ({
renderer={renderer}
renderable={roomRenderable}
scale={3}
size={viewport}
size={room.size}
styles={styles}
/>
<div className={styles.side}>
@ -138,6 +137,14 @@ const RoomComponent = ({
</button>
</label>
</div>
<div className={styles.grower} />
<label className={styles.size}>
<span>Size</span>
<VectorComponent
onChange={() => {}}
value={room.size}
/>
</label>
<label className={styles.sides}>
<span>Editing</span>
<select

View File

@ -169,11 +169,31 @@ label.entities {
align-items: center;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin: 1rem;
label {
margin: 0.25rem 0;
button {
margin: 0 1rem 0 0;
}
}
}
.size {
> span {
margin-right: 0.5rem;
}
&, label {
align-items: baseline;
display: flex;
span {
font-size: 0.6em;
font-weight: bold;
text-transform: uppercase;
}
}
}
.grower {
flex-grow: 100;
}

View File

@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
/* eslint-disable jsx-a11y/control-has-associated-label */
import {
Color,
@ -14,6 +15,7 @@ import {
Rectangle,
Vector,
} from '@avocado/math';
import {Vector as VectorComponent} from '@avocado/math/persea';
import {useJsonPatcher} from '@avocado/resource/persea';
import {
classnames,
@ -31,6 +33,46 @@ const container = new Container();
const primitives = new Primitives();
const renderer = new Renderer();
function TilesPage({
events,
resource,
room,
roomRenderable,
}) {
const patch = useJsonPatcher();
const [currentLayer, setCurrentLayer] = useState(0);
const [tilesetEvents, setTilesetEvents] = useState();
const [scale, setScale] = useState(3);
const [incident, setIncident] = useState([0, 0]);
const [isHoldingPaint, setIsHoldingPaint] = useState(false);
const [isHolding, setIsHolding] = useState(false);
const [selection, setSelection] = useState([0, 0, 16, 16]);
const [tilesetMode, setTilesetMode] = useState(0);
const [fillMode, setFillMode] = useState(0);
const [viewport, setViewport] = useState([0, 0]);
const layerCount = resource.tiles?.length || 0;
const [mute, setMute] = useState(Array(layerCount).fill(false));
const [solo, setSolo] = useState(Array(layerCount).fill(false));
// Get the event stream from the tileset stage.
const onRef = useCallback((stage) => {
setTilesetEvents(stage?.events());
}, [setTilesetEvents]);
// Global 'release' handler.
// @todo useRelease()
useEffect(() => {
const onDone = () => {
setIsHolding(false);
setIsHoldingPaint(false);
};
window.addEventListener('mouseup', onDone);
window.addEventListener('touchend', onDone);
return () => {
window.removeEventListener('mouseup', onDone);
window.removeEventListener('touchend', onDone);
};
}, []);
// Calculate layer visibility.
useEffect(() => {
const calculateVisibility = (mute, solo) => {
const visibility = Array(mute.length).fill(true);
if (solo.some((is) => !!is)) {
@ -45,58 +87,26 @@ const calculateVisibility = (mute, solo) => {
}
return visibility;
};
function TilesPage({
events,
resource,
room,
roomRenderable,
}) {
const patch = useJsonPatcher();
const layerCount = resource.tiles.length;
const [currentLayer, setCurrentLayer] = useState(0);
const [tilesetEvents, setTilesetEvents] = useState();
const [scale, setScale] = useState(3);
const [solo, setSolo] = useState(Array(layerCount).fill(false));
const [incident, setIncident] = useState([0, 0]);
const [isHoldingPaint, setIsHoldingPaint] = useState(false);
const [isHolding, setIsHolding] = useState(false);
const [mute, setMute] = useState(Array(layerCount).fill(false));
const [selection, setSelection] = useState([0, 0, 16, 16]);
const [tilesetMode, setTilesetMode] = useState(0);
const [fillMode, setFillMode] = useState(0);
const [viewport, setViewport] = useState([0, 0]);
const onRef = useCallback((stage) => {
setTilesetEvents(stage?.events());
}, [setTilesetEvents]);
useEffect(() => {
const onDone = () => {
setIsHolding(false);
setIsHoldingPaint(false);
};
window.addEventListener('mouseup', onDone);
window.addEventListener('touchend', onDone);
return () => {
window.removeEventListener('mouseup', onDone);
window.removeEventListener('touchend', onDone);
};
}, []);
useEffect(() => {
const visibility = calculateVisibility(mute, solo);
for (let i = 0; i < visibility.length; i++) {
// eslint-disable-next-line no-param-reassign
roomRenderable.tilesRenderables[i].alpha = visibility[i] ? 1 : 0;
}
}, [mute, roomRenderable, solo]);
// Update tileset container.
useEffect(() => {
const {image} = room.tiles[currentLayer].atlas;
container.removeAllChildren();
if (0 === room.tiles.length) {
return;
}
const {image} = room.tiles[currentLayer].atlas;
const sprite = new Sprite(image);
sprite.anchor = [0, 0];
container.addChild(sprite);
container.addChild(primitives);
setViewport(image.size);
}, [currentLayer, room]);
// Update primitives with selection.
useEffect(() => {
primitives.anchor = [0, 0];
primitives.clear();
@ -106,6 +116,7 @@ function TilesPage({
Primitives.fillStyle(new Color(255, 0, 255, 0.2)),
);
}, [selection]);
// Map events to tiles updates.
useEffect(() => {
if (!events) {
return undefined;
@ -192,6 +203,9 @@ function TilesPage({
) => {
const origin = Vector.floor(Vector.div(position, tileSize));
const edit = () => {
if (0 === room.tiles.length) {
return;
}
const {data: originalData} = room.tiles[currentLayer].toJSON();
switch (tilesetMode) {
case 0: {
@ -248,6 +262,7 @@ function TilesPage({
events.offValue(onValue);
};
});
// Map events to selection updates.
useEffect(() => {
if (!tilesetEvents) {
return undefined;
@ -293,10 +308,10 @@ function TilesPage({
});
return (
<div className={classnames(styles.tilesPage, {[styles.holding]: isHolding})}>
<div className={styles.label}>
<div className={styles.layers}>
<ul>
{
resource.tiles.map((tiles, i) => (
room.tiles.map((tiles, i) => (
// eslint-disable-next-line react/no-array-index-key
<li key={i}>
<button
@ -310,11 +325,40 @@ function TilesPage({
type="button"
>
<div className={styles.info}>
{i}
{' '}
-
{' '}
{tiles.tileImageUri}
<label>
<span>Area</span>
<span>
[
{tiles.width}
{' x '}
{tiles.height}
]
</span>
</label>
<label>
<span>Tile size</span>
<span>
[
{tiles.tileSize[0]}
{' x '}
{tiles.tileSize[1]}
]
</span>
</label>
<label>
<span>Full size</span>
<span>
[
{tiles.tileSize[0] * tiles.width}
{' x '}
{tiles.tileSize[1] * tiles.height}
]
</span>
</label>
<label>
<span>Tileset URI</span>
<span>{tiles.atlas.image.uri}</span>
</label>
</div>
<div className={styles.visibility}>
<label>
@ -343,12 +387,22 @@ function TilesPage({
type="checkbox"
/>
</label>
<button
type="button"
>
</button>
</div>
</button>
</li>
))
}
</ul>
<button
type="button"
>
Add layer
</button>
</div>
<div>
<div className={styles.draw}>
@ -380,6 +434,20 @@ function TilesPage({
</div>
</label>
</div>
<label className={styles.area}>
<span>Area</span>
<VectorComponent
onChange={() => {}}
value={room.tiles[currentLayer].area}
/>
</label>
<label className={styles.tileSize}>
<span>Tile size</span>
<VectorComponent
onChange={() => {}}
value={room.tiles[currentLayer].tileSize}
/>
</label>
<div className={styles.grower} />
<div className={styles.scale}>
<label>
@ -418,9 +486,6 @@ function TilesPage({
</div>
</div>
</div>
<div className={styles.tilesetUri}>
{resource.tiles[currentLayer].tileImageUri}
</div>
</div>
</div>
);

View File

@ -5,7 +5,7 @@
border-color: rgba(255, 255, 255, 0.1);
margin: 0.5rem;
}
label span {
label > span {
font-family: var(--system-font-family);
font-size: 0.6em;
font-weight: bold;
@ -14,16 +14,20 @@
}
ul {
border: 1px solid rgba(255, 255, 255, 0.1);
margin: 0 1rem;
}
}
.layers {
margin: 0 1rem;
}
.layerButton {
align-items: baseline;
align-items: center;
border: none;
display: flex;
flex-wrap: wrap;
font-family: var(--system-font-family);
font-size: 0.8em;
gap: 0.5rem;
justify-content: space-between;
margin: 0;
@ -32,15 +36,36 @@
background-color: rgb(0, 108, 134);
}
.info {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding-right: 0.5rem;
> label {
display: flex;
flex-direction: column;
margin-right: -0.5rem;
> span {
font-size: 0.9em;
}
> span + span {
font-weight: normal;
}
}
}
.visibility {
align-items: center;
display: flex;
flex-wrap: nowrap;
gap: 1rem;
button {
margin: 0.5em;
}
label {
display: flex;
flex-direction: row;
input {
margin-left: 0.25rem;
}
}
}
}
@ -60,6 +85,18 @@
flex-grow: 100;
}
.area, .tileSize {
input {
width: 2.5em;
}
label + label {
margin-left: 0.5rem;
}
label {
padding: 0;
}
}
.tilesetUri {
margin: 1rem;
}