flow: tiles editing
This commit is contained in:
parent
34ba41070d
commit
14cabec9b9
|
@ -39,9 +39,29 @@ const Stage = forwardRef(({
|
|||
'mouseout',
|
||||
'mousedown',
|
||||
'mouseup',
|
||||
'touchend',
|
||||
'touchmove',
|
||||
'touchstart',
|
||||
'wheel',
|
||||
]
|
||||
.map((event) => K.fromEvents(renderer.element, event)),
|
||||
.map((event) => (
|
||||
K.fromEvents(
|
||||
renderer.element,
|
||||
event,
|
||||
// Normalize touch events.
|
||||
(event) => {
|
||||
if (-1 !== ['touchstart', 'touchmove'].indexOf(event.type)) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
event.clientX = event.touches[0].clientX;
|
||||
event.deltaX = event.touches[0].deltaX;
|
||||
event.clientY = event.touches[0].clientY;
|
||||
event.deltaY = event.touches[0].deltaY;
|
||||
/* eslint-enable no-param-reassign */
|
||||
}
|
||||
return event;
|
||||
},
|
||||
)
|
||||
)),
|
||||
),
|
||||
}));
|
||||
useEffect(() => {
|
||||
|
|
|
@ -34,7 +34,7 @@ const makeButtonSelector = (key) => (
|
|||
);
|
||||
|
||||
const modes = [
|
||||
'move',
|
||||
'pan',
|
||||
'edit',
|
||||
'sample',
|
||||
]
|
||||
|
@ -130,6 +130,7 @@ const RoomComponent = ({
|
|||
active: true,
|
||||
events: 0 !== mode && events,
|
||||
room,
|
||||
roomRenderable,
|
||||
resource,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -14,10 +14,8 @@
|
|||
.side, .stage {
|
||||
height: 50%;
|
||||
}
|
||||
.side {
|
||||
overflow: auto;
|
||||
}
|
||||
.stage {
|
||||
margin: 1rem 0 0 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
@media (min-width: 60rem) {
|
||||
|
@ -29,7 +27,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.stage.moving {
|
||||
.stage.moving > div {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
@ -74,7 +72,7 @@
|
|||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
label.move {
|
||||
label.pan {
|
||||
font-size: 0.5rem;
|
||||
button {
|
||||
mask-image: url('./img/scan-light.svg');
|
||||
|
|
|
@ -90,18 +90,20 @@ function EntitiesPage({
|
|||
);
|
||||
};
|
||||
switch (type) {
|
||||
case 'mousedown': {
|
||||
case 'mousedown':
|
||||
case 'touchstart': {
|
||||
updatePosition();
|
||||
setIsHolding(true);
|
||||
break;
|
||||
}
|
||||
case 'touchmove':
|
||||
case 'mousemove': {
|
||||
if (isHolding) {
|
||||
updatePosition();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'mouseout':
|
||||
case 'touchend':
|
||||
case 'mouseup': {
|
||||
setIsHolding(false);
|
||||
break;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
:global(.entity-renderer) {
|
||||
flex-grow: 1;
|
||||
|
|
|
@ -1,12 +1,261 @@
|
|||
import {
|
||||
Color,
|
||||
Container,
|
||||
Primitives,
|
||||
Renderer,
|
||||
Sprite,
|
||||
Stage,
|
||||
} from '@avocado/graphics';
|
||||
import {
|
||||
Rectangle,
|
||||
Vector,
|
||||
} from '@avocado/math';
|
||||
import {
|
||||
classnames,
|
||||
hot,
|
||||
PropTypes,
|
||||
React,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from '@flecks/react';
|
||||
|
||||
function TilesPage() {
|
||||
import styles from './page.module.scss';
|
||||
|
||||
const container = new Container();
|
||||
const primitives = new Primitives();
|
||||
const renderer = new Renderer();
|
||||
|
||||
const calculateVisibility = (mute, solo) => {
|
||||
const visibility = Array(mute.length).fill(true);
|
||||
if (solo.some((is) => !!is)) {
|
||||
for (let i = 0; i < solo.length; i++) {
|
||||
visibility[i] = solo[i];
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < mute.length; i++) {
|
||||
if (mute[i]) {
|
||||
visibility[i] = false;
|
||||
}
|
||||
}
|
||||
return visibility;
|
||||
};
|
||||
|
||||
function TilesPage({
|
||||
events,
|
||||
resource,
|
||||
room,
|
||||
roomRenderable,
|
||||
}) {
|
||||
const layerCount = resource.tiles.length;
|
||||
const [currentLayer, setCurrentLayer] = useState(0);
|
||||
const [tilesetEvents, setTilesetEvents] = useState();
|
||||
const [solo, setSolo] = useState(Array(layerCount).fill(false));
|
||||
const [incident, setIncident] = useState([0, 0]);
|
||||
const [isHolding, setIsHolding] = useState(false);
|
||||
const [mute, setMute] = useState(Array(layerCount).fill(false));
|
||||
const [selection, setSelection] = useState([0, 0, 16, 16]);
|
||||
// const [sprite, setSprite] = useState(null);
|
||||
const [tilesetMode, setTilesetMode] = useState(0);
|
||||
const [viewport, setViewport] = useState([0, 0]);
|
||||
const onRef = useCallback((stage) => {
|
||||
setTilesetEvents(0 !== tilesetMode && stage?.events());
|
||||
}, [setTilesetEvents, tilesetMode]);
|
||||
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]);
|
||||
useEffect(() => {
|
||||
const {image} = room.tiles[currentLayer].atlas;
|
||||
container.removeAllChildren();
|
||||
const sprite = new Sprite(image);
|
||||
sprite.anchor = [0, 0];
|
||||
container.addChild(sprite);
|
||||
container.addChild(primitives);
|
||||
setViewport(image.size);
|
||||
}, [currentLayer, room]);
|
||||
useEffect(() => {
|
||||
primitives.anchor = [0, 0];
|
||||
primitives.clear();
|
||||
primitives.drawRectangle(
|
||||
selection,
|
||||
Primitives.lineStyle(new Color(255, 0, 255, 0.2), 0),
|
||||
Primitives.fillStyle(new Color(255, 0, 255, 0.2)),
|
||||
);
|
||||
}, [selection]);
|
||||
useEffect(() => {
|
||||
if (!events) {
|
||||
return undefined;
|
||||
}
|
||||
const onValue = (
|
||||
// {
|
||||
// clientX,
|
||||
// clientY,
|
||||
// deltaY,
|
||||
// target,
|
||||
// type,
|
||||
// },
|
||||
) => {
|
||||
};
|
||||
events.onValue(onValue);
|
||||
return () => {
|
||||
events.offValue(onValue);
|
||||
};
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!tilesetEvents) {
|
||||
return undefined;
|
||||
}
|
||||
const onValue = (
|
||||
{
|
||||
clientX,
|
||||
clientY,
|
||||
target,
|
||||
type,
|
||||
},
|
||||
) => {
|
||||
const tileSize = [16, 16];
|
||||
switch (type) {
|
||||
case 'touchstart':
|
||||
case 'mousedown': {
|
||||
const {left, top} = target.getBoundingClientRect();
|
||||
const clamped = Vector.mul(
|
||||
tileSize,
|
||||
Vector.floor(Vector.div([clientX - left, clientY - top], tileSize)),
|
||||
);
|
||||
setIncident(clamped);
|
||||
setSelection(Rectangle.compose(clamped, tileSize));
|
||||
setIsHolding(true);
|
||||
break;
|
||||
}
|
||||
case 'touchmove':
|
||||
case 'mousemove': {
|
||||
if (isHolding) {
|
||||
const {left, top} = target.getBoundingClientRect();
|
||||
const tileSize = [16, 16];
|
||||
const clamped = Vector.mul(
|
||||
tileSize,
|
||||
Vector.floor(Vector.div([clientX - left, clientY - top], tileSize)),
|
||||
);
|
||||
const min = Vector.min(clamped, incident);
|
||||
const max = Vector.max(clamped, incident);
|
||||
setSelection(Rectangle.compose(min, Vector.add(tileSize, Vector.sub(max, min))));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'touchend':
|
||||
case 'mouseup': {
|
||||
setIsHolding(false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
};
|
||||
tilesetEvents.onValue(onValue);
|
||||
return () => {
|
||||
tilesetEvents.offValue(onValue);
|
||||
};
|
||||
});
|
||||
return (
|
||||
<div />
|
||||
<div className={classnames(styles.tilesPage, {[styles.holding]: isHolding})}>
|
||||
<div className={classnames('label', styles.label)}>
|
||||
<div>Tiles</div>
|
||||
<ul>
|
||||
{
|
||||
resource.tiles.map((tiles, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={i}>
|
||||
<button
|
||||
className={styles.layerButton}
|
||||
onClick={(event) => {
|
||||
// Bug? .stopPropagation() doesn't work when clicking the child checkboxes.
|
||||
if (event.target === event.currentTarget) {
|
||||
setCurrentLayer(i);
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<div className={styles.info}>
|
||||
{i}
|
||||
{' '}
|
||||
-
|
||||
{' '}
|
||||
{tiles.tileImageUri}
|
||||
</div>
|
||||
<div className={styles.visibility}>
|
||||
<label>
|
||||
S
|
||||
<input
|
||||
checked={solo[i]}
|
||||
onChange={(event) => {
|
||||
event.stopPropagation();
|
||||
const newSolo = solo.concat();
|
||||
newSolo[i] = !newSolo[i];
|
||||
setSolo(newSolo);
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
M
|
||||
<input
|
||||
checked={mute[i]}
|
||||
onChange={(event) => {
|
||||
event.stopPropagation();
|
||||
const newMute = mute.concat();
|
||||
newMute[i] = !newMute[i];
|
||||
setMute(newMute);
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.draw}>
|
||||
<div className={styles.brushes}>
|
||||
<label>
|
||||
<span>Tileset mode</span>
|
||||
<select
|
||||
defaultValue={tilesetMode}
|
||||
onChange={(event) => {
|
||||
setTilesetMode(parseInt(event.target.value, 10));
|
||||
}}
|
||||
>
|
||||
<option value={0}>Pan</option>
|
||||
<option value={1}>Draw</option>
|
||||
<option value={2}>Fill</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className={styles.tilesetWrapper}>
|
||||
<div
|
||||
className={classnames(styles.tileset, {[styles.moving]: tilesetMode === 0})}
|
||||
>
|
||||
<Stage
|
||||
centered={false}
|
||||
ref={onRef}
|
||||
renderer={renderer}
|
||||
renderable={container}
|
||||
scalable={false}
|
||||
size={viewport}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.tilesetUri}>
|
||||
{resource.tiles[currentLayer].tileImageUri}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -15,7 +264,14 @@ TilesPage.defaultProps = {};
|
|||
TilesPage.displayName = 'TilesPage';
|
||||
|
||||
TilesPage.propTypes = {
|
||||
room: PropTypes.shape({}).isRequired,
|
||||
// eslint-disable-next-line react/forbid-prop-types
|
||||
events: PropTypes.any.isRequired,
|
||||
// eslint-disable-next-line react/forbid-prop-types
|
||||
room: PropTypes.any.isRequired,
|
||||
// eslint-disable-next-line react/forbid-prop-types
|
||||
roomRenderable: PropTypes.any.isRequired,
|
||||
// eslint-disable-next-line react/forbid-prop-types
|
||||
resource: PropTypes.any.isRequired,
|
||||
};
|
||||
|
||||
export default hot(module)(TilesPage);
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
.tilesPage {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
&.holding {
|
||||
overflow: hidden;
|
||||
}
|
||||
hr {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
margin: 0.5rem;
|
||||
}
|
||||
.label, label {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.layerButton {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-family: var(--system-font-family);
|
||||
gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
margin: 0.5rem 0 0 0;
|
||||
width: 100%;
|
||||
.info {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
label {
|
||||
background-color: transparent;
|
||||
display: inline;
|
||||
// padding: 0.5rem 0 0;
|
||||
}
|
||||
.visibility {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 1rem;
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: auto;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tilesetUri {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.draw {
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tilesetWrapper {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.tileset {
|
||||
aspect-ratio: 1;
|
||||
background-image: url('./test.png');
|
||||
display: flex;
|
||||
flex-grow: 100;
|
||||
margin: 0 auto;
|
||||
max-width: 20rem;
|
||||
overflow: hidden;
|
||||
&.moving {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.tilesetBar {
|
||||
align-items: center;
|
||||
background-color: #333;
|
||||
display: flex;
|
||||
height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.brushes label span {
|
||||
font-family: var(--system-font-family);
|
||||
font-size: 0.6em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1em;
|
||||
// text-align: center;
|
||||
// text-orientation: mixed;
|
||||
text-transform: uppercase;
|
||||
// writing-mode: vertical-rl;
|
||||
// width: 1rem;
|
||||
}
|
BIN
packages/topdown/src/persea/room-sides/tiles-side/test.png
Normal file
BIN
packages/topdown/src/persea/room-sides/tiles-side/test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
|
@ -61,6 +61,10 @@ export default (flecks) => {
|
|||
}
|
||||
}
|
||||
|
||||
get atlas() {
|
||||
return this.$$atlas;
|
||||
}
|
||||
|
||||
cleanPackets() {
|
||||
this.$$packets = [];
|
||||
this.$$updates = [];
|
||||
|
|
Loading…
Reference in New Issue
Block a user