Skip to content

Commit 868d657

Browse files
authored
Inspector v2: Add support for toggle commands in scene explorer (#16772)
This change allows for "toggle" commands in addition to "action" commands in scene explorer and implements one visibility toggle command. ![image](https://github.com/user-attachments/assets/94701303-b201-4180-9f38-2cc4d94f3877)
1 parent a8d86ef commit 868d657

File tree

5 files changed

+93
-19
lines changed

5 files changed

+93
-19
lines changed

packages/dev/inspector-v2/src/components/scene/sceneExplorer.tsx

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { IDisposable, Nullable, Scene } from "core/index";
44
import type { TreeItemValue, TreeOpenChangeData, TreeOpenChangeEvent } from "@fluentui/react-components";
55
import type { ComponentType, FunctionComponent } from "react";
66

7-
import { Body1, Body1Strong, Button, FlatTree, FlatTreeItem, makeStyles, tokens, Tooltip, TreeItemLayout } from "@fluentui/react-components";
7+
import { Body1, Body1Strong, Button, FlatTree, FlatTreeItem, makeStyles, tokens, ToggleButton, Tooltip, TreeItemLayout } from "@fluentui/react-components";
88
import { VirtualizerScrollView } from "@fluentui/react-components/unstable";
99

1010
import { useCallback, useEffect, useMemo, useState } from "react";
@@ -52,7 +52,7 @@ export type SceneExplorerSection<T extends EntityBase> = Readonly<{
5252
watch: (scene: Scene, onAdded: (entity: T) => void, onRemoved: (entity: T) => void) => IDisposable;
5353
}>;
5454

55-
export type SceneExplorerEntityCommand<T extends EntityBase> = Readonly<{
55+
type EntityCommandBase<T extends EntityBase> = Readonly<{
5656
/**
5757
* An optional order for the section, relative to other commands.
5858
* Defaults to 0.
@@ -64,11 +64,6 @@ export type SceneExplorerEntityCommand<T extends EntityBase> = Readonly<{
6464
*/
6565
predicate: (entity: unknown) => entity is T;
6666

67-
/**
68-
* The function that executes the command on the given entity.
69-
*/
70-
execute: (scene: Scene, entity: T) => void;
71-
7267
/**
7368
* The display name of the command (e.g. "Delete", "Rename", etc.).
7469
*/
@@ -80,6 +75,36 @@ export type SceneExplorerEntityCommand<T extends EntityBase> = Readonly<{
8075
icon: ComponentType<{ entity: T }>;
8176
}>;
8277

78+
type ActionCommand<T extends EntityBase> = EntityCommandBase<T> &
79+
Readonly<{
80+
type: "action";
81+
/**
82+
* The function that executes the command on the given entity.
83+
*/
84+
execute: (scene: Scene, entity: T) => void;
85+
}>;
86+
87+
type ToggleCommand<T extends EntityBase> = EntityCommandBase<T> &
88+
Readonly<{
89+
type: "toggle";
90+
/**
91+
* A boolean indicating if the command is enabled.
92+
*/
93+
isEnabled: (scene: Scene, entity: T) => boolean;
94+
95+
/**
96+
* The function that sets the enabled state of the command on the given entity.
97+
*/
98+
setEnabled: (scene: Scene, entity: T, enabled: boolean) => void;
99+
100+
/**
101+
* An optional icon component to render when the command is disabled.
102+
*/
103+
disabledIcon?: ComponentType<{ entity: T }>;
104+
}>;
105+
106+
export type SceneExplorerEntityCommand<T extends EntityBase> = ActionCommand<T> | ToggleCommand<T>;
107+
83108
type TreeItemData =
84109
| {
85110
type: "section";
@@ -112,6 +137,39 @@ const useStyles = makeStyles({
112137
},
113138
});
114139

140+
const ActionCommand: FunctionComponent<{ command: ActionCommand<EntityBase>; entity: EntityBase; scene: Scene }> = (props) => {
141+
const { command, entity, scene } = props;
142+
143+
return (
144+
<Tooltip key={command.displayName} content={command.displayName} relationship="label">
145+
<Button icon={<command.icon entity={entity} />} appearance="subtle" onClick={() => command.execute(scene, entity)} />
146+
</Tooltip>
147+
);
148+
};
149+
150+
const ToggleCommand: FunctionComponent<{ command: ToggleCommand<EntityBase>; entity: EntityBase; scene: Scene }> = (props) => {
151+
const { command, entity, scene } = props;
152+
const [checked, setChecked] = useState(command.isEnabled(scene, entity));
153+
const toggle = useCallback(() => {
154+
setChecked((prev) => {
155+
const enabled = !prev;
156+
command.setEnabled(scene, entity, enabled);
157+
return enabled;
158+
});
159+
}, [setChecked]);
160+
161+
return (
162+
<Tooltip content={command.displayName} relationship="label">
163+
<ToggleButton
164+
icon={!checked && command.disabledIcon ? <command.disabledIcon entity={entity} /> : <command.icon entity={entity} />}
165+
appearance="transparent"
166+
checked={checked}
167+
onClick={toggle}
168+
/>
169+
</Tooltip>
170+
);
171+
};
172+
115173
export const SceneExplorer: FunctionComponent<{
116174
sections: readonly SceneExplorerSection<EntityBase>[];
117175
commands: readonly SceneExplorerEntityCommand<EntityBase>[];
@@ -260,11 +318,13 @@ export const SceneExplorer: FunctionComponent<{
260318
style={item.entity === selectedEntity ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined}
261319
actions={commands
262320
.filter((command) => command.predicate(item.entity))
263-
.map((command) => (
264-
<Tooltip key={command.displayName} content={command.displayName} relationship="label">
265-
<Button icon={<command.icon entity={item.entity} />} appearance="subtle" onClick={() => command.execute(scene, item.entity)} />
266-
</Tooltip>
267-
))}
321+
.map((command) =>
322+
command.type === "action" ? (
323+
<ActionCommand key={command.displayName} command={command} entity={item.entity} scene={scene} />
324+
) : (
325+
<ToggleCommand key={command.displayName} command={command} entity={item.entity} scene={scene} />
326+
)
327+
)}
268328
>
269329
<Body1 wrap={false} truncate>
270330
{item.title.substring(0, 100)}

packages/dev/inspector-v2/src/services/panes/properties/propertiesService.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type PropertiesSectionContent<EntityT> = {
2424
} & AccordionSectionContent<EntityT>;
2525

2626
/**
27-
* Provides a properties pane that enables displaying and editing properties of an entity such as a mesh or a texture.
27+
* Allows new sections or content to be added to the properties pane.
2828
*/
2929
export interface IPropertiesService extends IService<typeof PropertiesServiceIdentity> {
3030
/**
@@ -40,6 +40,9 @@ export interface IPropertiesService extends IService<typeof PropertiesServiceIde
4040
addSectionContent<EntityT>(content: PropertiesSectionContent<EntityT>): IDisposable;
4141
}
4242

43+
/**
44+
* Provides a properties pane that enables displaying and editing properties of an entity such as a mesh or a texture.
45+
*/
4346
export const PropertiesServiceDefinition: ServiceDefinition<[IPropertiesService], [IShellService, ISelectionService]> = {
4447
friendlyName: "Properties Editor",
4548
produces: [PropertiesServiceIdentity],

packages/dev/inspector-v2/src/services/panes/scene/nodeExplorerService.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { IReadonlyObservable, Node, Scene } from "core/index";
44
import type { ServiceDefinition } from "../../../modularity/serviceDefinition";
55
import type { ISceneExplorerService } from "./sceneExplorerService";
66

7-
import { BoxRegular, BranchRegular, CameraRegular, EyeRegular, LightbulbRegular } from "@fluentui/react-icons";
7+
import { BoxRegular, BranchRegular, CameraRegular, EyeRegular, EyeOffRegular, LightbulbRegular } from "@fluentui/react-icons";
88

99
import { Camera } from "core/Cameras/camera";
1010
import { Light } from "core/Lights/light";
@@ -65,13 +65,18 @@ export const NodeHierarchyServiceDefinition: ServiceDefinition<[], [ISceneExplor
6565
});
6666

6767
const visibilityCommandRegistration = sceneExplorerService.addCommand({
68+
type: "toggle",
6869
order: 0,
69-
predicate: (entity: unknown) => entity instanceof AbstractMesh,
70-
execute: (scene: Scene, mesh: AbstractMesh) => {
71-
// TODO
70+
predicate: (entity: unknown): entity is AbstractMesh => entity instanceof AbstractMesh && entity.getTotalVertices() > 0,
71+
isEnabled: (scene: Scene, mesh: AbstractMesh) => {
72+
return mesh.isVisible;
73+
},
74+
setEnabled: (scene: Scene, mesh: AbstractMesh, enabled: boolean) => {
75+
mesh.isVisible = enabled;
7276
},
7377
displayName: "Show/Hide Mesh",
7478
icon: () => <EyeRegular />,
79+
disabledIcon: () => <EyeOffRegular />,
7580
});
7681

7782
return {

packages/dev/inspector-v2/src/services/panes/scene/sceneExplorerService.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { ShellServiceIdentity } from "../../shellService";
1919
export const SceneExplorerServiceIdentity = Symbol("SceneExplorer");
2020

2121
/**
22-
* Provides a scene explorer pane that enables browsing the scene graph and executing commands on entities.
22+
* Allows new sections or commands to be added to the scene explorer pane.
2323
*/
2424
export interface ISceneExplorerService extends IService<typeof SceneExplorerServiceIdentity> {
2525
/**
@@ -35,6 +35,9 @@ export interface ISceneExplorerService extends IService<typeof SceneExplorerServ
3535
addCommand<T extends EntityBase>(command: SceneExplorerEntityCommand<T>): IDisposable;
3636
}
3737

38+
/**
39+
* Provides a scene explorer pane that enables browsing the scene graph and executing commands on entities.
40+
*/
3841
export const SceneExplorerServiceDefinition: ServiceDefinition<[ISceneExplorerService], [ISceneContext, IShellService, ISelectionService]> = {
3942
friendlyName: "Scene Explorer",
4043
produces: [SceneExplorerServiceIdentity],

packages/dev/inspector-v2/src/services/panes/statsService.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const StatsFrameStepsSectionIdentity = Symbol("Frame Steps Duration");
2323
export const StatsSystemInfoSectionIdentity = Symbol("System Info");
2424

2525
/**
26-
* Provides a scene stats pane.
26+
* Allows new sections or content to be added to the stats pane.
2727
*/
2828
export interface IStatsService extends IService<typeof StatsServiceIdentity> {
2929
/**
@@ -39,6 +39,9 @@ export interface IStatsService extends IService<typeof StatsServiceIdentity> {
3939
addSectionContent(content: AccordionSectionContent<Scene>): IDisposable;
4040
}
4141

42+
/**
43+
* Provides a scene stats pane.
44+
*/
4245
export const StatsServiceDefinition: ServiceDefinition<[IStatsService], [IShellService, ISceneContext]> = {
4346
friendlyName: "Stats",
4447
produces: [StatsServiceIdentity],

0 commit comments

Comments
 (0)