Skip to content

Commit 2f7f693

Browse files
committed
feat: add support of "locked" mode for objects in graph
1 parent 23c636a commit 2f7f693

File tree

22 files changed

+684
-169
lines changed

22 files changed

+684
-169
lines changed

editor/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@radix-ui/react-context-menu": "^2.1.5",
5656
"@radix-ui/react-dialog": "^1.0.5",
5757
"@radix-ui/react-dropdown-menu": "^2.0.6",
58+
"@radix-ui/react-hover-card": "1.1.14",
5859
"@radix-ui/react-icons": "^1.3.0",
5960
"@radix-ui/react-label": "^2.0.2",
6061
"@radix-ui/react-menubar": "^1.0.4",

editor/src/editor/layout/graph.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ import { isSound } from "../../tools/guards/sound";
3434
import { isSceneLinkNode } from "../../tools/guards/scene";
3535
import { updateAllLights } from "../../tools/light/shadows";
3636
import { getCollisionMeshFor } from "../../tools/mesh/collision";
37+
import { isNodeVisibleInGraph } from "../../tools/node/metadata";
3738
import { isAdvancedDynamicTexture } from "../../tools/guards/texture";
3839
import { updateIblShadowsRenderPipeline } from "../../tools/light/ibl";
3940
import { UniqueNumber, waitNextAnimationFrame } from "../../tools/tools";
40-
import { isMeshMetadataNotVisibleInGraph } from "../../tools/mesh/metadata";
4141
import { isAnyParticleSystem, isGPUParticleSystem, isParticleSystem } from "../../tools/guards/particles";
4242
import {
4343
isAbstractMesh,
@@ -676,7 +676,7 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
676676
}
677677

678678
private _parseSceneNode(node: Node, noChildren?: boolean): TreeNodeInfo | null {
679-
if ((isMesh(node) && (node._masterMesh || isMeshMetadataNotVisibleInGraph(node))) || isCollisionMesh(node) || isCollisionInstancedMesh(node)) {
679+
if ((isMesh(node) && (node._masterMesh || !isNodeVisibleInGraph(node))) || isCollisionMesh(node) || isCollisionInstancedMesh(node)) {
680680
return null;
681681
}
682682

@@ -773,7 +773,19 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
773773
return (
774774
<div
775775
onClick={(ev) => {
776-
node.setEnabled(!node.isEnabled());
776+
const enabled = !node.isEnabled();
777+
778+
let selectedNodeData = this.getSelectedNodes().map((n) => n.nodeData);
779+
if (!selectedNodeData.includes(node)) {
780+
selectedNodeData = [node];
781+
}
782+
783+
selectedNodeData.forEach((node) => {
784+
if (isNode(node)) {
785+
node.setEnabled(enabled);
786+
}
787+
});
788+
777789
this.refresh();
778790
ev.stopPropagation();
779791

editor/src/editor/layout/graph/graph.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { registerUndoRedo } from "../../../tools/undoredo";
3030
import { isScene, isSceneLinkNode } from "../../../tools/guards/scene";
3131
import { UniqueNumber, waitNextAnimationFrame } from "../../../tools/tools";
3232
import { isAbstractMesh, isMesh, isNode } from "../../../tools/guards/nodes";
33+
import { isNodeLocked, isNodeSerializable, setNodeLocked, setNodeSerializable } from "../../../tools/node/metadata";
3334

3435
import { addGPUParticleSystem, addParticleSystem } from "../../../project/add/particles";
3536

@@ -113,12 +114,29 @@ export class EditorGraphContextMenu extends Component<IEditorGraphContextMenuPro
113114
<ContextMenuSeparator />
114115

115116
<ContextMenuCheckboxItem
116-
checked={this.props.object.metadata?.doNotSerialize ?? false}
117+
checked={isNodeLocked(this.props.object)}
117118
onClick={() => {
119+
const locked = !isNodeLocked(this.props.object);
120+
121+
this.props.editor.layout.graph.getSelectedNodes().forEach((node) => {
122+
if (isNode(node.nodeData)) {
123+
setNodeLocked(node.nodeData, locked);
124+
}
125+
});
126+
this.props.editor.layout.graph.refresh();
127+
}}
128+
>
129+
Locked
130+
</ContextMenuCheckboxItem>
131+
132+
<ContextMenuCheckboxItem
133+
checked={!isNodeSerializable(this.props.object)}
134+
onClick={() => {
135+
const serializable = !isNodeSerializable(this.props.object);
136+
118137
this.props.editor.layout.graph.getSelectedNodes().forEach((node) => {
119138
if (isNode(node.nodeData)) {
120-
node.nodeData.metadata ??= {};
121-
node.nodeData.metadata.doNotSerialize = node.nodeData.metadata.doNotSerialize ? false : true;
139+
setNodeSerializable(node.nodeData, serializable);
122140
}
123141
});
124142
this.props.editor.layout.graph.refresh();

editor/src/editor/layout/graph/label.tsx

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { extname } from "path/posix";
22

33
import { DragEvent, useEffect, useRef, useState } from "react";
44

5+
import { FaLock } from "react-icons/fa";
56
import { useEventListener } from "usehooks-ts";
67

78
import { Node, TransformNode, AbstractMesh } from "babylonjs";
@@ -12,6 +13,7 @@ import { isScene } from "../../../tools/guards/scene";
1213
import { isSound } from "../../../tools/guards/sound";
1314
import { registerUndoRedo } from "../../../tools/undoredo";
1415
import { isAnyParticleSystem } from "../../../tools/guards/particles";
16+
import { isNodeSerializable, isNodeLocked } from "../../../tools/node/metadata";
1517
import { isAbstractMesh, isInstancedMesh, isMesh, isNode, isTransformNode } from "../../../tools/guards/nodes";
1618

1719
import { applySoundAsset } from "../preview/import/sound";
@@ -246,14 +248,50 @@ export function EditorGraphLabel(props: IEditorGraphLabelProps) {
246248
props.editor.layout.graph.refresh();
247249
}
248250

251+
function getLabel() {
252+
if (doubleClicked) {
253+
return (
254+
<Input
255+
value={name}
256+
ref={inputRef}
257+
className="w-fit h-7"
258+
onCopy={(ev) => ev.stopPropagation()}
259+
onPaste={(ev) => ev.stopPropagation()}
260+
onChange={(ev) => setName(ev.currentTarget.value)}
261+
/>
262+
);
263+
}
264+
265+
const label = (
266+
<div
267+
className={`
268+
${!isNodeSerializable(props.object) ? "line-through" : ""}
269+
${!isNodeSerializable(props.object) || isNodeLocked(props.object) ? "text-foreground/35" : ""}
270+
transition-all duration-300 ease-in-out
271+
`}
272+
>
273+
{props.name}
274+
</div>
275+
);
276+
277+
if (isNodeLocked(props.object)) {
278+
return (
279+
<div className="flex gap-2 items-center justify-between">
280+
{label}
281+
<FaLock className="w-4 h-4 opacity-50 mr-2" />
282+
</div>
283+
);
284+
}
285+
286+
return label;
287+
}
288+
249289
return (
250290
<div
251291
draggable
252292
className={`
253293
ml-2 p-1 w-full
254294
${over ? "bg-muted" : ""}
255-
${props.object.metadata?.doNotSerialize ? "text-foreground/35 line-through" : ""}
256-
transition-all duration-300 ease-in-out
257295
`}
258296
onDragStart={(ev) => handleDragStart(ev)}
259297
onDragOver={(ev) => handleDragOver(ev)}
@@ -262,18 +300,7 @@ export function EditorGraphLabel(props: IEditorGraphLabelProps) {
262300
onDoubleClick={() => handleDoubleClick()}
263301
onBlur={() => handleInputNameBlurred()}
264302
>
265-
{doubleClicked ? (
266-
<Input
267-
value={name}
268-
ref={inputRef}
269-
className="w-fit h-7"
270-
onCopy={(ev) => ev.stopPropagation()}
271-
onPaste={(ev) => ev.stopPropagation()}
272-
onChange={(ev) => setName(ev.currentTarget.value)}
273-
/>
274-
) : (
275-
props.name
276-
)}
303+
{getLabel()}
277304
</div>
278305
);
279306
}

editor/src/editor/layout/inspector.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { Component, ReactNode } from "react";
22
import { Icon, NonIdealState } from "@blueprintjs/core";
33

4+
import { FaInfoCircle } from "react-icons/fa";
45
import { FaCube, FaSprayCanSparkles } from "react-icons/fa6";
56

67
import { Tools } from "babylonjs";
78

89
import { Editor } from "../main";
910

11+
import { Badge } from "../../ui/shadcn/ui/badge";
1012
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/shadcn/ui/tabs";
13+
import { HoverCard, HoverCardContent, HoverCardTrigger } from "../../ui/shadcn/ui/hover-card";
14+
15+
import { isNode } from "../../tools/guards/nodes";
16+
import { isNodeLocked } from "../../tools/node/metadata";
1117

1218
import { setInspectorSearch } from "./inspector/fields/field";
1319
import { IEditorInspectorImplementationProps } from "./inspector/inspector";
@@ -86,6 +92,8 @@ export class EditorInspector extends Component<IEditorInspectorProps, IEditorIns
8692
}
8793

8894
public render(): ReactNode {
95+
const disabled = (this.state.editedObject && isNode(this.state.editedObject) && isNodeLocked(this.state.editedObject)) ?? false;
96+
8997
return (
9098
<div className="flex flex-col gap-2 w-full h-full p-2 text-foreground overflow-hidden">
9199
<Tabs defaultValue="entity" className="flex flex-col gap-2 w-full h-full">
@@ -99,6 +107,18 @@ export class EditorInspector extends Component<IEditorInspectorProps, IEditorIns
99107
</TabsTrigger>
100108
</TabsList>
101109

110+
{disabled && (
111+
<HoverCard openDelay={150} closeDelay={150}>
112+
<HoverCardTrigger className="w-full">
113+
<Badge variant="secondary" className="flex items-center gap-2 w-full">
114+
<FaInfoCircle className="w-6 h-6" />
115+
Object is locked and cannot be edited.
116+
</Badge>
117+
</HoverCardTrigger>
118+
<HoverCardContent>The object is locked, meaning it cannot be modified in the inspector. You can unlock it in the scene graph.</HoverCardContent>
119+
</HoverCard>
120+
)}
121+
102122
<input
103123
type="text"
104124
placeholder="Search..."
@@ -108,7 +128,7 @@ export class EditorInspector extends Component<IEditorInspectorProps, IEditorIns
108128
/>
109129

110130
<TabsContent value="entity" className="w-full h-full overflow-auto">
111-
<div className="flex flex-col gap-2 h-full">{this._getContent()}</div>
131+
<div className={`flex flex-col gap-2 h-full ${disabled ? "pointer-events-none opacity-50 cursor-not-allowed" : ""}`}>{this._getContent()}</div>
112132
</TabsContent>
113133

114134
<TabsContent value="decals" className="w-full h-full overflow-auto">

editor/src/editor/layout/inspector/decals/decals.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Editor } from "../../../main";
1414
import { isDarwin } from "../../../../tools/os";
1515
import { registerUndoRedo } from "../../../../tools/undoredo";
1616
import { UniqueNumber, waitNextAnimationFrame } from "../../../../tools/tools";
17-
import { setMeshMetadataNotSerializable, setMeshMetadataNotVisibleInGraph } from "../../../../tools/mesh/metadata";
17+
import { setNodeSerializable, setNodeVisibleInGraph } from "../../../../tools/node/metadata";
1818

1919
import { loadImportedMaterial } from "../../preview/import/import";
2020

@@ -298,8 +298,8 @@ export class EditorDecalsInspector extends Component<IEditorDecalsInspectorProps
298298
this._decalMesh.receiveShadows = true;
299299
this._decalMesh.visibility = this.state.ctrlOrMetaKeyDown ? 1 : 0.35;
300300

301-
setMeshMetadataNotSerializable(this._decalMesh, true);
302-
setMeshMetadataNotVisibleInGraph(this._decalMesh, true);
301+
setNodeSerializable(this._decalMesh, false);
302+
setNodeVisibleInGraph(this._decalMesh, false);
303303

304304
if (this.state.material.zOffset === 0) {
305305
this.state.material.zOffset = -3;
@@ -345,8 +345,8 @@ export class EditorDecalsInspector extends Component<IEditorDecalsInspectorProps
345345
},
346346
};
347347

348-
setMeshMetadataNotSerializable(decalMesh, false);
349-
setMeshMetadataNotVisibleInGraph(decalMesh, false);
348+
setNodeSerializable(decalMesh, true);
349+
setNodeVisibleInGraph(decalMesh, true);
350350

351351
registerUndoRedo({
352352
executeRedo: false,

editor/src/editor/layout/inspector/material/pbr.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export class EditorPBRMaterialInspector extends Component<IEditorPBRMaterialInsp
5757
object={this.props.material}
5858
property="transparencyMode"
5959
items={[
60+
{ text: "None", value: null },
6061
{ text: "Opaque", value: PBRMaterial.MATERIAL_OPAQUE },
6162
{ text: "Alpha Test", value: PBRMaterial.MATERIAL_ALPHATEST },
6263
{ text: "Alpha Blend", value: PBRMaterial.MATERIAL_ALPHABLEND },

0 commit comments

Comments
 (0)