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 ( -
+
{scalable && ( )} -
+
); }); 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';