diff --git a/src/components/JoystickView.tsx b/src/components/JoystickView.tsx
index c0043e1..60f88c3 100644
--- a/src/components/JoystickView.tsx
+++ b/src/components/JoystickView.tsx
@@ -106,7 +106,7 @@ export function JoystickView() {
summary {
- list-style: none;
- &::-webkit-details-marker { display: none; }
+ & details {
+ &>summary {
+ list-style: none;
+
+ &::-webkit-details-marker {
+ display: none;
+ }
+
+ display: block;
+ padding: 8px 8px;
+ cursor: pointer;
+ border-radius: 4px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: rgba(255, 255, 255, 1);
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.08);
+ color: #fff;
+ }
+
+ &.selected {
+ background: rgba(255, 255, 255, 0.15);
+ color: #fff;
+ }
+ }
+
+ & details {
+ padding-left: 12px;
+ }
}
-}
-
-.menu-item {
- display: block;
- padding: 8px 8px;
- cursor: pointer;
- border-radius: 4px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- color: rgba(255, 255, 255, 0.7);
-
- &:hover {
- background: rgba(255, 255, 255, 0.08);
- color: #fff;
- }
-
- &.active {
- background: rgba(255, 255, 255, 0.15);
- color: #fff;
- }
-}
-
-.menu-node .menu-node > summary {
- padding-left: 12px;
-}
-
-.menu-node .menu-node .menu-leaf {
- padding-left: 24px;
-}
+}
\ No newline at end of file
diff --git a/src/components/MenuView.tsx b/src/components/MenuView.tsx
index b5a20dc..b1acf3c 100644
--- a/src/components/MenuView.tsx
+++ b/src/components/MenuView.tsx
@@ -1,33 +1,36 @@
import { observer } from 'mobx-react-lite';
+import { useEffect, useRef } from 'react';
import { state } from '../state';
import './MenuView.scss';
+import type { MenuNode } from '../state/menuState';
-export const MenuView = observer(function () {
- const objectIds = Object.keys(state.worldEditor.scene.objects);
- const selectedId = state.worldEditor.selectedObjectId;
+export const MenuNodeView = observer(function ({ node }: { node: MenuNode }) {
+ const forceOpen = state.menu.nodeContainsSelected(node);
+
+ const ref = useRef
(null);
+
+ useEffect(() => {
+ if (forceOpen && ref.current)
+ ref.current.open = true;
+ }, [forceOpen]);
+ return (
+
+ node.onClick?.()}
+ >
+ {node.title}
+
+ {node.children?.map((child) => )}
+
+ );
+});
+
+export const MenuView = observer(function () {
return (
);
});
diff --git a/src/components/Panel.scss b/src/components/Panel.scss
index d7593b2..96dfee6 100644
--- a/src/components/Panel.scss
+++ b/src/components/Panel.scss
@@ -24,6 +24,7 @@
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(4px);
padding: 4px;
+ border-radius: 4px;
display: flex;
flex-direction: column;
@@ -38,11 +39,16 @@
}
&>.debug {
- border: 1px solid #ffffff20;
- padding: 2px;
+ // border: 1px solid #ffffff20;
+ border-radius: 2px;
+ background: #00000040;
+
+ margin: -4px;
+ padding: 4px;
font-size: 75%;
display: flex;
gap: 0.5em;
+ align-items: start;
&>.chart {
display: inline;
diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx
index 00823be..8dd123b 100644
--- a/src/components/Panel.tsx
+++ b/src/components/Panel.tsx
@@ -30,10 +30,9 @@ export const Panel = observer(function ({ side = 'left' }: PanelProps) {
return
+
-
-
diff --git a/src/state/menuState.ts b/src/state/menuState.ts
new file mode 100644
index 0000000..fc89ece
--- /dev/null
+++ b/src/state/menuState.ts
@@ -0,0 +1,56 @@
+import { makeAutoObservable } from "mobx";
+import type { ObjectType, RuntimeObjectInstance } from "../types";
+import { state } from "./rootState";
+
+export type MenuNode = {
+ id: string;
+ title: string;
+ onClick?: () => void;
+ selected?: () => boolean;
+ children?: MenuNode[];
+}
+
+export class MenuState {
+ constructor() {
+ makeAutoObservable(this);
+ }
+
+ private get editorObjectTypesMenu(): MenuNode[] {
+ return Object.values(state.world.data.objectTypes)
+ .map((ot) => ({
+ id: `ot-${ot.id}`,
+ title: ot.name,
+ children: Object.values(state.worldEditor.scene.objects)
+ .filter((o) => o.typeId === ot.id)
+ .map((o) => ({
+ id: `o-${o.id}`,
+ title: o.id,
+ onClick: () => { state.worldEditor.setSelectedObject(o.id, 'translate') },
+ selected: () => state.worldEditor.selectedObjectId === o.id,
+ }))
+ }));
+ }
+
+ private get editorMenu(): MenuNode[] {
+ return [
+ {
+ id: 'editor-objects-menu',
+ title: 'Objects',
+ onClick: () => { state.worldEditor.resetSelectedObject() },
+ selected: () => state.worldEditor.selectedObjectId === undefined,
+ children: this.editorObjectTypesMenu,
+ }
+ ]
+ }
+
+ public get nodes(): MenuNode[] {
+ return [
+ ...this.editorMenu,
+ ]
+ }
+
+ public nodeContainsSelected(node: MenuNode): boolean {
+ return !!(node.selected?.() || node.children?.some((child) => this.nodeContainsSelected(child)));
+ }
+
+}
diff --git a/src/state/rootState.ts b/src/state/rootState.ts
index 568415c..398ae37 100644
--- a/src/state/rootState.ts
+++ b/src/state/rootState.ts
@@ -2,6 +2,7 @@ import { makeAutoObservable } from "mobx";
import { WorldState } from "./worldState";
import { WorldEditorState } from "./worldEditorState";
import { GameState } from "./gameState";
+import { MenuState } from "./menuState";
export type RenderInfo = {
calls: number,
@@ -16,6 +17,7 @@ export class RootState {
public readonly worldEditor: WorldEditorState;
public game: GameState | undefined;
public renderInfo: RenderInfo | undefined;
+ public readonly menu = new MenuState();
constructor() {
this.worldEditor = new WorldEditorState(this.world);
@@ -24,6 +26,7 @@ export class RootState {
this,
{
world: false,
+ menu: false,
},
);
}