editor object selection menu in panel
This commit is contained in:
parent
a5733a4f4e
commit
e08f80fc19
|
|
@ -106,7 +106,7 @@ export function JoystickView() {
|
|||
<div
|
||||
ref={moveZoneRef}
|
||||
id="move-zone"
|
||||
style={{ position: 'absolute', left: 0, bottom: 0, width: '30vw', height: '30vw', touchAction: 'none', background: 'red' }}
|
||||
style={{ position: 'absolute', left: 0, bottom: 0, width: '30vw', height: '30vw', touchAction: 'none' }}
|
||||
/>
|
||||
<div
|
||||
ref={lookZoneRef}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
.menu {
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.menu-node {
|
||||
> summary {
|
||||
& details {
|
||||
&>summary {
|
||||
list-style: none;
|
||||
&::-webkit-details-marker { display: none; }
|
||||
}
|
||||
|
||||
&::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: block;
|
||||
padding: 8px 8px;
|
||||
cursor: pointer;
|
||||
|
|
@ -18,23 +17,21 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
&.selected {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-node .menu-node > summary {
|
||||
& details {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.menu-node .menu-node .menu-leaf {
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<HTMLDetailsElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (forceOpen && ref.current)
|
||||
ref.current.open = true;
|
||||
}, [forceOpen]);
|
||||
|
||||
return (
|
||||
<nav className="menu">
|
||||
<details open className="menu-node">
|
||||
<summary className="menu-item">Editor</summary>
|
||||
<details open className="menu-node">
|
||||
<details ref={ref}>
|
||||
<summary
|
||||
className={`menu-item${selectedId == null ? ' active' : ''}`}
|
||||
onClick={() => state.worldEditor.resetSelectedObject()}
|
||||
className={node.selected?.() ? 'selected' : ''}
|
||||
onClick={() => node.onClick?.()}
|
||||
>
|
||||
Objects
|
||||
{node.title}
|
||||
</summary>
|
||||
{objectIds.map(id => (
|
||||
<div
|
||||
key={id}
|
||||
className={`menu-item menu-leaf${selectedId === id ? ' active' : ''}`}
|
||||
onClick={() => state.worldEditor.setSelectedObject(id, 'translate')}
|
||||
>
|
||||
{id}
|
||||
</div>
|
||||
))}
|
||||
</details>
|
||||
{node.children?.map((child) => <MenuNodeView key={child.id} node={child} />)}
|
||||
</details>
|
||||
);
|
||||
});
|
||||
|
||||
export const MenuView = observer(function () {
|
||||
return (
|
||||
<nav className="menu">
|
||||
{state.menu.nodes.map((node) => <MenuNodeView key={node.id} node={node} />)}
|
||||
</nav>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -30,10 +30,9 @@ export const Panel = observer(function ({ side = 'left' }: PanelProps) {
|
|||
return <div className={`panel ${side}`}>
|
||||
<div className="container">
|
||||
<MenuView />
|
||||
<div className="gap" />
|
||||
<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,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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue