sidebar
This commit is contained in:
parent
8a23a49863
commit
90162d47cf
|
|
@ -2,7 +2,7 @@ import { BrowserRouter, Link, Outlet, Route, Routes, useNavigate, useParams } fr
|
|||
import { useEffect } from 'react';
|
||||
import { reaction } from 'mobx';
|
||||
import { ThreeView } from './components/ThreeView';
|
||||
import { Toolbar } from './components/Toolbar';
|
||||
import { Panel } from './components/Panel';
|
||||
import { state } from './state';
|
||||
import './App.scss';
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ function EditorLayout() {
|
|||
<>
|
||||
<Outlet />
|
||||
<ThreeView />
|
||||
<Toolbar />
|
||||
<Panel side="left" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
.panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 30%;
|
||||
height: 100vh;
|
||||
padding: 10px;
|
||||
z-index: 10;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
&.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&>.container {
|
||||
flex: 1;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
padding: 4px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
|
||||
&>*:not(.debug):not(.gap) {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
&>.gap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&>.debug {
|
||||
border: 1px solid #ffffff20;
|
||||
padding: 2px;
|
||||
font-size: 75%;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
&>.chart {
|
||||
display: inline;
|
||||
|
||||
& div {
|
||||
display: inline;
|
||||
position: relative !important;
|
||||
z-index: unset !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&>.metrics {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25em 0.5em;
|
||||
|
||||
&>* {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import './Panel.scss';
|
||||
import { RenderInfoView } from "./RenderInfoView";
|
||||
import { state } from "../state";
|
||||
|
||||
export type PanelProps = {
|
||||
side?: 'left' | 'right';
|
||||
}
|
||||
|
||||
export const Panel = observer(function ({ side = 'left' }: PanelProps) {
|
||||
|
||||
function handleCloneTest1Object(): void {
|
||||
state.worldEditor.addObjectCloneAtRandomPosition('test1');
|
||||
}
|
||||
|
||||
function handleLoadWorld(): void {
|
||||
state.world.load();
|
||||
}
|
||||
|
||||
function handleLoadMockWorld(): void {
|
||||
state.world.loadMock();
|
||||
}
|
||||
|
||||
return <div className={`panel ${side}`}>
|
||||
<div className="container">
|
||||
<button onClick={handleLoadWorld}>Load</button>
|
||||
<button onClick={handleLoadMockWorld}>Load mock world</button>
|
||||
<button onClick={handleCloneTest1Object}>Clone test1</button>
|
||||
<div className="gap" />
|
||||
<div className="debug"><RenderInfoView /></div>
|
||||
</div>
|
||||
</div >
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { state } from "../state";
|
||||
import { chartRef } from "./chartRef";
|
||||
|
||||
export const RenderInfoView = observer(function () {
|
||||
const info = state.renderInfo;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
el.appendChild(chartRef.current);
|
||||
return () => { el.removeChild(chartRef.current); };
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
<div className="chart" ref={containerRef} />
|
||||
{info != null && (
|
||||
<div className="metrics">
|
||||
<span>draw: {info.calls}</span>
|
||||
<span>tri: {info.triangles}</span>
|
||||
<span>shaders: {info.shaders}</span>
|
||||
<span>geometries: {info.geometries}</span>
|
||||
<span>textures: {info.textures}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
});
|
||||
|
|
@ -1,19 +1,22 @@
|
|||
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
||||
import { KeyboardControls, Stats } from '@react-three/drei';
|
||||
import { useRef } from 'react';
|
||||
import { action } from 'mobx';
|
||||
import { chartRef } from './chartRef';
|
||||
|
||||
function RenderInfoUpdater({ domRef }: { domRef: React.RefObject<HTMLDivElement | null> }) {
|
||||
function RenderInfoUpdater() {
|
||||
const { gl } = useThree();
|
||||
useFrame(() => {
|
||||
if (!domRef.current)
|
||||
return;
|
||||
|
||||
useFrame(action(() => {
|
||||
const { calls, triangles } = gl.info.render;
|
||||
const shaders = gl.info.programs?.length ?? 0;
|
||||
const { geometries, textures } = gl.info.memory;
|
||||
|
||||
domRef.current.textContent = `draw: ${calls} | tri: ${triangles} | shaders: ${shaders} | geometries: ${geometries} | textures: ${textures}`;
|
||||
});
|
||||
state.setRenderInfo({
|
||||
calls,
|
||||
triangles,
|
||||
shaders,
|
||||
geometries,
|
||||
textures,
|
||||
});
|
||||
}));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -21,6 +24,7 @@ import { observer } from 'mobx-react-lite';
|
|||
import { state } from '../state';
|
||||
import { GameView } from './GameView';
|
||||
import { SceneEditorView } from './SceneEditorView';
|
||||
import type { RefObject } from 'react';
|
||||
|
||||
const IconStop = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="10" height="10" fill="currentColor" /></svg>;
|
||||
const IconPause = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="4" height="10" fill="currentColor" /><rect x="8" y="2" width="4" height="10" fill="currentColor" /></svg >;
|
||||
|
|
@ -28,7 +32,6 @@ const IconPlay = () => <svg viewBox="0 0 14 14"><polygon points="3,1 13,7 3,13"
|
|||
|
||||
export const ThreeView = observer(function () {
|
||||
const isGame = state.isGamePlaying;
|
||||
const infoRef = useRef<HTMLDivElement>(null);
|
||||
return (
|
||||
<KeyboardControls map={[
|
||||
{ name: 'forward', keys: ['ArrowUp', 'w', 'W'] },
|
||||
|
|
@ -42,16 +45,10 @@ export const ThreeView = observer(function () {
|
|||
// camera={state.world.character.camera}
|
||||
onPointerMissed={() => state.worldEditor.resetSelectedObject()}
|
||||
>
|
||||
<Stats />
|
||||
<RenderInfoUpdater domRef={infoRef} />
|
||||
<Stats parent={chartRef as RefObject<HTMLElement>} />
|
||||
<RenderInfoUpdater />
|
||||
{isGame ? <GameView /> : <SceneEditorView />}
|
||||
</Canvas>
|
||||
<div ref={infoRef} style={{
|
||||
position: 'absolute', bottom: 8, left: 8,
|
||||
color: 'white', fontSize: 11, fontFamily: 'monospace',
|
||||
background: 'rgba(0,0,0,0.5)', padding: '2px 6px', borderRadius: 3,
|
||||
pointerEvents: 'none',
|
||||
}} />
|
||||
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}>
|
||||
{
|
||||
state.game
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { state } from "../state";
|
||||
|
||||
export const Toolbar = observer(function () {
|
||||
|
||||
function handleCloneTest1Object(): void {
|
||||
state.worldEditor.addObjectCloneAtRandomPosition('test1');
|
||||
}
|
||||
|
||||
function handleLoadWorld(): void {
|
||||
state.world.load();
|
||||
}
|
||||
|
||||
function handleLoadMockWorld(): void {
|
||||
state.world.loadMock();
|
||||
}
|
||||
|
||||
return <div className="toolbar">
|
||||
<button onClick={handleLoadWorld}>Load</button>
|
||||
<button onClick={handleLoadMockWorld}>Load mock world</button>
|
||||
<button onClick={handleCloneTest1Object}>Clone test1</button>
|
||||
</div>
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const chartRef = { current: document.createElement('span') };
|
||||
|
|
@ -48,10 +48,9 @@
|
|||
}
|
||||
|
||||
#root {
|
||||
width: 1126px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
border-inline: 1px solid var(--border);
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,19 @@ import { WorldState } from "./worldState";
|
|||
import { WorldEditorState } from "./worldEditorState";
|
||||
import { GameState } from "./gameState";
|
||||
|
||||
export type RenderInfo = {
|
||||
calls: number,
|
||||
triangles: number,
|
||||
shaders: number,
|
||||
geometries: number,
|
||||
textures: number,
|
||||
}
|
||||
|
||||
export class RootState {
|
||||
public readonly world = new WorldState();
|
||||
public readonly worldEditor: WorldEditorState;
|
||||
public game: GameState | undefined;
|
||||
public renderInfo: RenderInfo | undefined;
|
||||
|
||||
constructor() {
|
||||
this.worldEditor = new WorldEditorState(this.world);
|
||||
|
|
@ -37,6 +46,10 @@ export class RootState {
|
|||
this.game.pause();
|
||||
this.game = undefined;
|
||||
}
|
||||
|
||||
public setRenderInfo(value: RenderInfo | undefined) {
|
||||
this.renderInfo = value;
|
||||
}
|
||||
}
|
||||
|
||||
export const state = new RootState();
|
||||
|
|
|
|||
Loading…
Reference in New Issue