From c496bedb0def6aafa01dab45b59226250cd82752 Mon Sep 17 00:00:00 2001 From: Sol Williams <7689429+solwllms@users.noreply.github.com> Date: Mon, 22 Dec 2025 07:15:06 +0000 Subject: [PATCH] Component context menu tidy (#3582) * Only show property-prefab options on prefab instances, simplify names a bit * Tweak these to match * Transform * Order * Guessing this meant to pick the shortest source location path * dotnet format --- .../TypeLibrary/TypeDescription.cs | 2 +- .../Editor/ControlSheet/ControlSheetRow.cs | 21 +-- .../ComponentListWidget.cs | 133 +++++++++--------- .../GameObjectTransformControl.cs | 12 +- .../Code/Scene/SceneTree/GameObjectNode.cs | 51 ++++--- 5 files changed, 107 insertions(+), 112 deletions(-) diff --git a/engine/Sandbox.Reflection/TypeLibrary/TypeDescription.cs b/engine/Sandbox.Reflection/TypeLibrary/TypeDescription.cs index a34edb89..8135da46 100644 --- a/engine/Sandbox.Reflection/TypeLibrary/TypeDescription.cs +++ b/engine/Sandbox.Reflection/TypeLibrary/TypeDescription.cs @@ -448,7 +448,7 @@ public sealed class TypeDescription : ISourceLineProvider SourceLine = 0; SourceFile = null; - if ( OwnAttributes.OfType().MinBy( x => x.Path ) is { } sourceLocation ) + if ( OwnAttributes.OfType().MinBy( x => x.Path.Length ) is { } sourceLocation ) { SourceLine = sourceLocation.Line; SourceFile = sourceLocation.Path; diff --git a/game/addons/tools/Code/Editor/ControlSheet/ControlSheetRow.cs b/game/addons/tools/Code/Editor/ControlSheet/ControlSheetRow.cs index 34b5d7a7..b00c5b87 100644 --- a/game/addons/tools/Code/Editor/ControlSheet/ControlSheetRow.cs +++ b/game/addons/tools/Code/Editor/ControlSheet/ControlSheetRow.cs @@ -248,7 +248,8 @@ class ControlSheetRow : Widget property.Parent.NoteFinishEdit( property ); } ); - if ( IsEditingComponent || IsEditingGameObject ) + bool isPrefab = property.GetContainingGameObject()?.IsPrefabInstance ?? false; + if ( isPrefab && (IsEditingComponent || IsEditingGameObject) ) { menu.AddSeparator(); @@ -258,28 +259,20 @@ class ControlSheetRow : Widget editedObject ??= EditedGameObjects.FirstOrDefault(); var prefabName = EditorUtility.Prefabs.GetOuterMostPrefabName( editedObject ) ?? ""; - var revertChangesActionName = $"Revert Property instance change"; - - menu.AddOption( revertChangesActionName, "history", () => + var revertActionName = "Revert Change"; + menu.AddOption( revertActionName, "history", () => { using var scene = SceneEditorSession.Scope(); - using ( SceneEditorSession.Active.UndoScope( revertChangesActionName ).WithComponentChanges( EditedComponents ).WithGameObjectChanges( EditedGameObjects, GameObjectUndoFlags.Properties ).Push() ) + using ( SceneEditorSession.Active.UndoScope( revertActionName ).WithComponentChanges( EditedComponents ).WithGameObjectChanges( EditedGameObjects, GameObjectUndoFlags.Properties ).Push() ) { EditorUtility.Prefabs.RevertPropertyChange( property ); } } ).Enabled = isPropertyModified; - var applyChangesActionName = $"Apply Property instance change to prefab \"{prefabName}\""; - - menu.AddOption( applyChangesActionName, "update", () => + menu.AddOption( "Apply to Prefab", "save", () => { - using var scene = SceneEditorSession.Scope(); - - using ( SceneEditorSession.Active.UndoScope( applyChangesActionName ).WithComponentChanges( EditedComponents ).WithGameObjectChanges( EditedGameObjects, GameObjectUndoFlags.Properties ).Push() ) - { - EditorUtility.Prefabs.ApplyPropertyChange( property ); - } + EditorUtility.Prefabs.ApplyPropertyChange( property ); } ).Enabled = isPropertyModified; } diff --git a/game/addons/tools/Code/Scene/GameObjectInspector/ComponentListWidget.cs b/game/addons/tools/Code/Scene/GameObjectInspector/ComponentListWidget.cs index a9f40316..a6143163 100644 --- a/game/addons/tools/Code/Scene/GameObjectInspector/ComponentListWidget.cs +++ b/game/addons/tools/Code/Scene/GameObjectInspector/ComponentListWidget.cs @@ -196,6 +196,7 @@ public class ComponentListWidget : Widget component.Reset(); } } ); + menu.AddSeparator(); var componentIndex = componentList.GetAll().ToList().IndexOf( component ); @@ -230,66 +231,6 @@ public class ComponentListWidget : Widget menu.AddSeparator(); - menu.AddOption( "Remove Component", "remove", action: () => - { - ActivateSession(); - using var scene = SceneEditorSession.Scope(); - - using ( SceneEditorSession.Active.UndoScope( $"Remove {component.GetType().Name} Component" ).WithComponentDestructions( component ).Push() ) - { - component.Destroy(); - } - } ); - - if ( component.GameObject.IsPrefabInstance ) - { - var isComponentModified = EditorUtility.Prefabs.IsComponentInstanceModified( component ); - - var prefabName = EditorUtility.Prefabs.GetOuterMostPrefabName( component ); - - var revertChangesActionName = $"Revert Component instance changes"; - menu.AddOption( revertChangesActionName, "history", action: () => - { - ActivateSession(); - using var scene = SceneEditorSession.Scope(); - - using ( SceneEditorSession.Active.UndoScope( revertChangesActionName ).WithComponentChanges( component ).Push() ) - { - EditorUtility.Prefabs.RevertComponentInstanceChanges( component ); - } - } ).Enabled = isComponentModified; - - - var applyChangesActionName = $"Apply Component instance changes to prefab \"{prefabName}\""; - menu.AddOption( applyChangesActionName, "update", action: () => - { - ActivateSession(); - using var scene = SceneEditorSession.Scope(); - - EditorUtility.Prefabs.ApplyComponentInstanceChangesToPrefab( component ); - - } ).Enabled = isComponentModified; - } - - var replace = menu.AddMenu( "Replace Component", "find_replace" ); - replace.AddWidget( new MenuComponentTypeSelectorWidget( replace ) - { - OnSelect = ( t ) => - { - ActivateSession(); - using var scene = SceneEditorSession.Scope(); - - using ( SceneEditorSession.Active.UndoScope( $"Replace {component.GetType().Name} Component" ).WithComponentDestructions( component ).WithComponentCreations().Push() ) - { - var go = component.GameObject; - var jso = component.Serialize().AsObject(); - component.Destroy(); - var newComponent = go.Components.Create( t ); - newComponent.DeserializeImmediately( jso ); - } - } - } ); - menu.AddOption( $"Cut {title}", "content_cut", action: () => { ActivateSession(); @@ -305,20 +246,84 @@ public class ComponentListWidget : Widget menu.AddOption( $"Copy {title}", "copy_all", action: () => component.CopyToClipboard() ); - if ( editable && SceneEditor.HasComponentInClipboard() ) + if ( editable ) { - menu.AddOption( "Paste Values", "content_paste", action: () => component.PasteValues() ); - menu.AddOption( "Paste As New", "content_paste_go", action: () => component.GameObject.PasteComponent() ); + bool clipboardComponent = SceneEditor.HasComponentInClipboard(); + menu.AddOption( "Paste Values", "content_paste", action: () => component.PasteValues() ).Enabled = clipboardComponent; + menu.AddOption( "Paste As New", "content_paste_go", action: () => component.GameObject.PasteComponent() ).Enabled = clipboardComponent; } menu.AddSeparator(); + if ( component.GameObject.IsPrefabInstance ) + { + var isComponentModified = EditorUtility.Prefabs.IsComponentInstanceModified( component ); + + var prefabName = EditorUtility.Prefabs.GetOuterMostPrefabName( component ); + + var revertChangesActionName = "Revert Changes"; + menu.AddOption( revertChangesActionName, "history", action: () => + { + ActivateSession(); + using var scene = SceneEditorSession.Scope(); + + using ( SceneEditorSession.Active.UndoScope( revertChangesActionName ).WithComponentChanges( component ).Push() ) + { + EditorUtility.Prefabs.RevertComponentInstanceChanges( component ); + } + } ).Enabled = isComponentModified; + + + menu.AddOption( "Apply to Prefab", "save", action: () => + { + ActivateSession(); + using var scene = SceneEditorSession.Scope(); + + EditorUtility.Prefabs.ApplyComponentInstanceChangesToPrefab( component ); + + } ).Enabled = isComponentModified; + + menu.AddSeparator(); + } + + menu.AddOption( "Remove Component", "remove", action: () => + { + ActivateSession(); + using var scene = SceneEditorSession.Scope(); + + using ( SceneEditorSession.Active.UndoScope( $"Remove {component.GetType().Name} Component" ).WithComponentDestructions( component ).Push() ) + { + component.Destroy(); + } + } ); + + var replace = menu.AddMenu( "Replace Component", "find_replace" ); + replace.AddWidget( new MenuComponentTypeSelectorWidget( replace ) + { + OnSelect = ( t ) => + { + ActivateSession(); + using var scene = SceneEditorSession.Scope(); + + using ( SceneEditorSession.Active.UndoScope( $"Replace {component.GetType().Name} Component" ).WithComponentDestructions( component ).WithComponentCreations().Push() ) + { + var go = component.GameObject; + var jso = component.Serialize().AsObject(); + component.Destroy(); + var newComponent = go.Components.Create( t ); + newComponent.DeserializeImmediately( jso ); + } + } + } ); + + menu.AddSeparator(); + var t = EditorTypeLibrary.GetType( component.GetType() ); if ( t.SourceFile is not null ) { bool isPackage = component.GetType().Assembly.IsPackage(); var filename = System.IO.Path.GetFileName( t.SourceFile ); - menu.AddOption( $"Open {filename}", "open_in_new", action: () => CodeEditor.OpenFile( t ) ).Enabled = isPackage; + menu.AddOption( $"Open {filename}", "code", action: () => CodeEditor.OpenFile( t ) ).Enabled = isPackage; } } diff --git a/game/addons/tools/Code/Scene/GameObjectInspector/GameObjectTransformControl.cs b/game/addons/tools/Code/Scene/GameObjectInspector/GameObjectTransformControl.cs index d90584d7..ddde716e 100644 --- a/game/addons/tools/Code/Scene/GameObjectInspector/GameObjectTransformControl.cs +++ b/game/addons/tools/Code/Scene/GameObjectInspector/GameObjectTransformControl.cs @@ -94,7 +94,7 @@ partial class TransformComponentWidget : ComponentEditorWidget menu.Clear(); - menu.AddOption( UseLocal ? "Display Worldspace" : "Display Localspace", null, () => + menu.AddOption( UseLocal ? "Display Worldspace" : "Display Localspace", "public", () => { UseLocal = !UseLocal; Rebuild(); @@ -102,19 +102,19 @@ partial class TransformComponentWidget : ComponentEditorWidget menu.AddSeparator(); - menu.AddOption( "Reset", action: () => + menu.AddOption( "Reset", "restart_alt", () => { SerializedObject.GetProperty( UseLocal ? "Local" : "World" ).SetValue( Transform.Zero ); } ); menu.AddSeparator(); - menu.AddOption( "Copy Local Transform", action: () => + menu.AddOption( "Copy Local Transform", "content_copy", () => { var tx = SerializedObject.GetProperty( "Local" ).GetValue(); EditorUtility.Clipboard.Copy( Json.Serialize( tx ) ); } ); - menu.AddOption( "Copy World Transform", action: () => + menu.AddOption( "Copy World Transform", "content_copy", () => { var tx = SerializedObject.GetProperty( "World" ).GetValue(); EditorUtility.Clipboard.Copy( Json.Serialize( tx ) ); @@ -126,12 +126,12 @@ partial class TransformComponentWidget : ComponentEditorWidget var tx = Json.Deserialize( clipText ); if ( tx != default ) { - menu.AddOption( "Paste as Local Transform", action: () => + menu.AddOption( "Paste as Local Transform", "content_paste", () => { SerializedObject.GetProperty( "Local" ).SetValue( tx ); } ); - menu.AddOption( "Paste as World Transform", action: () => + menu.AddOption( "Paste as World Transform", "content_paste", () => { SerializedObject.GetProperty( "World" ).SetValue( tx ); } ); diff --git a/game/addons/tools/Code/Scene/SceneTree/GameObjectNode.cs b/game/addons/tools/Code/Scene/SceneTree/GameObjectNode.cs index 92c9b121..70422635 100644 --- a/game/addons/tools/Code/Scene/SceneTree/GameObjectNode.cs +++ b/game/addons/tools/Code/Scene/SceneTree/GameObjectNode.cs @@ -600,8 +600,28 @@ partial class GameObjectNode : TreeNode var isModified = selectedGos.Any( x => (x.IsPrefabInstanceRoot && EditorUtility.Prefabs.IsInstanceModified( x )) || (x.IsPrefabInstance && EditorUtility.Prefabs.IsGameObjectInstanceModified( gameObject )) ); - var isAdded = selectedGos.All( x => x.IsPrefabInstance && EditorUtility.Prefabs.IsGameObjectAddedToInstance( x ) ); + var revertChangesActionName = "Revert Changes"; + prefabMenu.AddOption( revertChangesActionName, "history", () => + { + using var scene = SceneEditorSession.Scope(); + using ( SceneEditorSession.Active.UndoScope( revertChangesActionName ).WithGameObjectChanges( selectedGos, GameObjectUndoFlags.Properties ).Push() ) + { + foreach ( var go in selectedGos ) + { + if ( go.IsPrefabInstanceRoot ) + { + EditorUtility.Prefabs.RevertInstanceToPrefab( go ); + } + else if ( go.IsPrefabInstance ) + { + EditorUtility.Prefabs.RevertGameObjectInstanceChanges( go ); + } + } + } + } ).Enabled = isModified; + + var isAdded = selectedGos.All( x => x.IsPrefabInstance && EditorUtility.Prefabs.IsGameObjectAddedToInstance( x ) ); if ( isAdded ) { var applyAddActionName = multipleSources ? "Add Objects to Prefabs" : "Add Object to Prefab"; @@ -610,7 +630,7 @@ partial class GameObjectNode : TreeNode var parentPrefabName = EditorUtility.Prefabs.GetOuterMostPrefabName( gameObject.Parent ); applyAddActionName += $" '{parentPrefabName ?? "Invalid"}'"; } - prefabMenu.AddOption( applyAddActionName, "update", () => + prefabMenu.AddOption( applyAddActionName, "save", () => { using var scene = SceneEditorSession.Scope(); @@ -629,8 +649,8 @@ partial class GameObjectNode : TreeNode } else { - var applyChangesActionName = multipleSources ? "Apply Instance Changes to Prefabs" : "Apply Instance Changes To Prefab"; - prefabMenu.AddOption( applyChangesActionName, "update", () => + var applyChangesActionName = multipleSources ? "Apply to Prefabs" : "Apply to Prefab"; + prefabMenu.AddOption( applyChangesActionName, "save", () => { using var scene = SceneEditorSession.Scope(); @@ -651,29 +671,6 @@ partial class GameObjectNode : TreeNode } } ).Enabled = isModified; } - - - - var revertChangesActionName = "Revert Instance Changes"; - prefabMenu.AddOption( revertChangesActionName, "history", () => - { - using var scene = SceneEditorSession.Scope(); - - using ( SceneEditorSession.Active.UndoScope( revertChangesActionName ).WithGameObjectChanges( selectedGos, GameObjectUndoFlags.Properties ).Push() ) - { - foreach ( var go in selectedGos ) - { - if ( go.IsPrefabInstanceRoot ) - { - EditorUtility.Prefabs.RevertInstanceToPrefab( go ); - } - else if ( go.IsPrefabInstance ) - { - EditorUtility.Prefabs.RevertGameObjectInstanceChanges( go ); - } - } - } - } ).Enabled = isModified; } if ( !isPrefabRoot )