diff --git a/packages/graphics/src/components/stage/index.jsx b/packages/graphics/src/components/stage/index.jsx
index 84eabb0..9766e81 100644
--- a/packages/graphics/src/components/stage/index.jsx
+++ b/packages/graphics/src/components/stage/index.jsx
@@ -1,6 +1,7 @@
import Renderer from '@avocado/graphics/renderer';
import {Vector} from '@avocado/math';
import {
+ classnames,
forwardRef,
memo,
PropTypes,
@@ -13,7 +14,7 @@ import {
import K from 'kefir';
import Slider from 'rc-slider';
-import './index.css';
+import styles from './index.module.scss';
const marks = {
1: '1x',
@@ -23,6 +24,7 @@ const marks = {
};
const Stage = forwardRef(({
+ className,
centered,
renderable,
renderer,
@@ -114,9 +116,7 @@ const Stage = forwardRef(({
};
}, [renderable, renderer, ticker]);
return (
-
+
);
});
Stage.defaultProps = {
centered: true,
+ className: '',
renderer: new Renderer([0, 0]),
scalable: true,
};
@@ -147,6 +145,7 @@ Stage.displayName = 'Stage';
Stage.propTypes = {
centered: PropTypes.bool,
+ className: PropTypes.string,
renderable: PropTypes.shape({
position: PropTypes.arrayOf(PropTypes.number),
scale: PropTypes.arrayOf(PropTypes.number),
diff --git a/packages/graphics/src/components/stage/index.css b/packages/graphics/src/components/stage/index.module.scss
similarity index 65%
rename from packages/graphics/src/components/stage/index.css
rename to packages/graphics/src/components/stage/index.module.scss
index b0cdf63..cc86adc 100644
--- a/packages/graphics/src/components/stage/index.css
+++ b/packages/graphics/src/components/stage/index.module.scss
@@ -9,7 +9,7 @@
min-width: 200px;
}
-.stage > .canvas-host {
+.stage > .canvas {
align-items: center;
/* border-top: 1px solid rgba(255, 255, 255, 0.1); */
display: flex;
@@ -20,15 +20,3 @@
width: 100%;
line-height: 0;
}
-
-.stage > .rc-slider {
- margin: 0.5em 0 1.25em;
- width: calc(100% - 2em);
-}
-
-.rc-slider-handle {
- border: 1px solid rgba(0, 0, 0, 0.5);
-}
-.rc-slider-track {
- background-color: #d60000;
-}
diff --git a/packages/topdown/package.json b/packages/topdown/package.json
index b7e5f26..7a7313f 100644
--- a/packages/topdown/package.json
+++ b/packages/topdown/package.json
@@ -36,6 +36,7 @@
"@avocado/math": "^3.0.0",
"@avocado/resource": "^3.0.0",
"@avocado/s13n": "^3.0.0",
+ "@avocado/timing": "^3.0.0",
"@avocado/traits": "^3.0.0",
"@flecks/core": "^1.4.1",
"@flecks/react": "^1.4.1",
diff --git a/packages/topdown/src/components/room/index.js b/packages/topdown/src/components/room/index.js
new file mode 100644
index 0000000..0c8b9b2
--- /dev/null
+++ b/packages/topdown/src/components/room/index.js
@@ -0,0 +1,2 @@
+export {default as RoomRenderable} from './renderable';
+export {default as RoomUi} from './ui';
diff --git a/packages/topdown/src/components/room/renderable.jsx b/packages/topdown/src/components/room/renderable.jsx
new file mode 100644
index 0000000..d08502d
--- /dev/null
+++ b/packages/topdown/src/components/room/renderable.jsx
@@ -0,0 +1,117 @@
+import {
+ classnames,
+ PropTypes,
+ React,
+ useCallback,
+ useEffect,
+ useFlecks,
+ useState,
+} from '@flecks/react';
+
+import {Container, Renderer, Stage} from '@avocado/graphics';
+import {Vector} from '@avocado/math';
+import {createLoop, destroyLoop} from '@avocado/timing';
+
+import styles from './renderable.module.scss';
+
+const renderer = new Renderer();
+
+const RoomRenderable = ({camera, className, room}) => {
+ const flecks = useFlecks();
+ const [container] = useState(new Container());
+ const [roomRenderable, setRoomRenderable] = useState();
+ const [viewSize, setViewSize] = useState(flecks.get('@avocado/topdown.viewSize'));
+ useEffect(() => {
+ [container.x, container.y] = Vector.scale(viewSize, 0.5);
+ }, [container, viewSize]);
+ useEffect(() => {
+ if (!room) {
+ return undefined;
+ }
+ const roomRenderable = new (room.constructor.Renderable)(room, renderer);
+ setRoomRenderable(roomRenderable);
+ container.addChild(roomRenderable);
+ return () => {
+ container.removeChild(roomRenderable);
+ };
+ }, [container, room]);
+ useEffect(() => {
+ if (!roomRenderable || !camera) {
+ return undefined;
+ }
+ const halfViewport = Vector.scale(viewSize, 0.5);
+ // eslint-disable-next-line no-param-reassign
+ camera.realPosition = camera.position;
+ const onCameraRealOffsetChanged = () => {
+ roomRenderable.renderChunksForExtent(camera.rectangle);
+ container.pivot = Vector.sub(halfViewport, Vector.scale(camera.realOffset, -1));
+ };
+ const onCameraRotationChanged = () => {
+ container.rotation = camera.rotation;
+ };
+ const onCameraScaleChanged = () => {
+ [container.scaleX, container.scaleY] = [1, 1] || camera.scale;
+ };
+ const onCameraViewSizeChanged = () => {
+ setViewSize(camera.viewSize);
+ };
+ onCameraRealOffsetChanged();
+ onCameraRotationChanged();
+ onCameraScaleChanged();
+ onCameraViewSizeChanged();
+ camera.on('realOffsetChanged', onCameraRealOffsetChanged);
+ camera.on('rotationChanged', onCameraRotationChanged);
+ camera.on('scaleChanged', onCameraScaleChanged);
+ camera.on('viewSize', onCameraViewSizeChanged);
+ return () => {
+ camera.off('realOffsetChanged', onCameraRealOffsetChanged);
+ camera.off('rotationChanged', onCameraRotationChanged);
+ camera.off('scaleChanged', onCameraScaleChanged);
+ camera.off('viewSize', onCameraViewSizeChanged);
+ };
+ }, [container, roomRenderable, camera, viewSize]);
+ const ticker = useCallback(
+ (elapsed) => {
+ container.renderTick(elapsed);
+ },
+ [container],
+ );
+ useEffect(() => {
+ if (!room) {
+ return undefined;
+ }
+ const handle = createLoop((elapsed) => {
+ room.tick(elapsed);
+ });
+ return () => {
+ destroyLoop(handle);
+ };
+ }, [container, room]);
+ return (
+
+ );
+};
+
+RoomRenderable.defaultProps = {
+ camera: null,
+ className: '',
+ room: null,
+};
+
+RoomRenderable.propTypes = {
+ // eslint-disable-next-line react/forbid-prop-types
+ camera: PropTypes.any,
+ className: PropTypes.string,
+ // eslint-disable-next-line react/forbid-prop-types
+ room: PropTypes.any,
+};
+
+export default RoomRenderable;
diff --git a/packages/topdown/src/components/room/renderable.module.scss b/packages/topdown/src/components/room/renderable.module.scss
new file mode 100644
index 0000000..e505ede
--- /dev/null
+++ b/packages/topdown/src/components/room/renderable.module.scss
@@ -0,0 +1,7 @@
+.stage {
+ display: inline-block;
+ line-height: 0;
+ canvas {
+ image-rendering: pixelated;
+ }
+}
diff --git a/packages/topdown/src/components/room.jsx b/packages/topdown/src/components/room/ui.jsx
similarity index 92%
rename from packages/topdown/src/components/room.jsx
rename to packages/topdown/src/components/room/ui.jsx
index f6bf6e7..88c91de 100644
--- a/packages/topdown/src/components/room.jsx
+++ b/packages/topdown/src/components/room/ui.jsx
@@ -6,10 +6,10 @@ import {
useRef,
} from '@flecks/react';
-import styles from './room.module.scss';
+import styles from './ui.module.scss';
// This is the DOM side of a rendered room. Dialog text, damage numbers, (DOM node entities)...
-function Room({
+function RoomUi({
domScale,
position,
room,
@@ -52,11 +52,11 @@ function Room({
);
}
-Room.defaultProps = {
+RoomUi.defaultProps = {
room: undefined,
};
-Room.propTypes = {
+RoomUi.propTypes = {
// @todo Real prop type
domScale: PropTypes.arrayOf(PropTypes.number).isRequired,
position: PropTypes.arrayOf(PropTypes.number).isRequired,
@@ -67,4 +67,4 @@ Room.propTypes = {
}),
};
-export default Room;
+export default RoomUi;
diff --git a/packages/topdown/src/components/room.module.scss b/packages/topdown/src/components/room/ui.module.scss
similarity index 100%
rename from packages/topdown/src/components/room.module.scss
rename to packages/topdown/src/components/room/ui.module.scss
diff --git a/packages/topdown/src/index.js b/packages/topdown/src/index.js
index 4d1177d..ffd411c 100644
--- a/packages/topdown/src/index.js
+++ b/packages/topdown/src/index.js
@@ -1,6 +1,6 @@
import {Flecks, Hooks} from '@flecks/core';
-export {default as Room} from './components/room';
+export * from './components/room';
export {default as Camera} from './camera';