From 90162d47cf19f755c64e5e2c8911b9250e234865 Mon Sep 17 00:00:00 2001 From: "azykov@mail.ru" Date: Thu, 4 Jun 2026 10:50:06 +0300 Subject: [PATCH] sidebar --- src/App.tsx | 4 +- src/components/Panel.scss | 69 +++++++++++++++++++++++++++++++ src/components/Panel.tsx | 33 +++++++++++++++ src/components/RenderInfoView.tsx | 29 +++++++++++++ src/components/ThreeView.tsx | 33 +++++++-------- src/components/Toolbar.tsx | 24 ----------- src/components/chartRef.ts | 1 + src/index.scss | 3 +- src/state/rootState.ts | 13 ++++++ 9 files changed, 163 insertions(+), 46 deletions(-) create mode 100644 src/components/Panel.scss create mode 100644 src/components/Panel.tsx create mode 100644 src/components/RenderInfoView.tsx delete mode 100644 src/components/Toolbar.tsx create mode 100644 src/components/chartRef.ts diff --git a/src/App.tsx b/src/App.tsx index 280361d..2630044 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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() { <> - + ); } diff --git a/src/components/Panel.scss b/src/components/Panel.scss new file mode 100644 index 0000000..d7593b2 --- /dev/null +++ b/src/components/Panel.scss @@ -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; + } + } + } + } +} \ No newline at end of file diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx new file mode 100644 index 0000000..7b2cc18 --- /dev/null +++ b/src/components/Panel.tsx @@ -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
+
+ + + +
+
+
+
+}); diff --git a/src/components/RenderInfoView.tsx b/src/components/RenderInfoView.tsx new file mode 100644 index 0000000..505e813 --- /dev/null +++ b/src/components/RenderInfoView.tsx @@ -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(null); + + useEffect(() => { + const el = containerRef.current; + if (!el) return; + el.appendChild(chartRef.current); + return () => { el.removeChild(chartRef.current); }; + }, []); + + return <> +
+ {info != null && ( +
+ draw: {info.calls} + tri: {info.triangles} + shaders: {info.shaders} + geometries: {info.geometries} + textures: {info.textures} +
+ )} + +}); diff --git a/src/components/ThreeView.tsx b/src/components/ThreeView.tsx index b14c24a..2664a63 100644 --- a/src/components/ThreeView.tsx +++ b/src/components/ThreeView.tsx @@ -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 }) { +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 = () => ; const IconPause = () => ; @@ -28,7 +32,6 @@ const IconPlay = () => (null); return ( state.worldEditor.resetSelectedObject()} > - - + } /> + {isGame ? : } -
{ state.game diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx deleted file mode 100644 index 12557a7..0000000 --- a/src/components/Toolbar.tsx +++ /dev/null @@ -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
- - - -
-}); diff --git a/src/components/chartRef.ts b/src/components/chartRef.ts new file mode 100644 index 0000000..48919aa --- /dev/null +++ b/src/components/chartRef.ts @@ -0,0 +1 @@ +export const chartRef = { current: document.createElement('span') }; diff --git a/src/index.scss b/src/index.scss index 679a3c8..af963dc 100644 --- a/src/index.scss +++ b/src/index.scss @@ -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; diff --git a/src/state/rootState.ts b/src/state/rootState.ts index 31d6207..568415c 100644 --- a/src/state/rootState.ts +++ b/src/state/rootState.ts @@ -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();