From fc4b79239b92cdd5128448506d4102900dbef164 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 23 Feb 2025 21:23:09 +0100 Subject: [PATCH] Refactor new scene tree double click feature to support both prefab and scene editing #1502 --- Source/Editor/Modules/PrefabsModule.cs | 23 +++--- Source/Editor/Options/InputBinding.cs | 59 +++++++++++++- Source/Editor/Options/InputOptions.cs | 31 +++++++- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 28 ++++++- Source/Editor/Utilities/Utils.cs | 7 ++ .../Windows/Assets/PrefabWindow.Hierarchy.cs | 22 ++++-- Source/Editor/Windows/Assets/PrefabWindow.cs | 6 +- Source/Editor/Windows/SceneEditorWindow.cs | 53 ++++++++++++- .../Windows/SceneTreeWindow.ContextMenu.cs | 4 +- Source/Editor/Windows/SceneTreeWindow.cs | 78 +------------------ 10 files changed, 208 insertions(+), 103 deletions(-) diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs index 680d710f4..2dfb71538 100644 --- a/Source/Editor/Modules/PrefabsModule.cs +++ b/Source/Editor/Modules/PrefabsModule.cs @@ -108,25 +108,28 @@ namespace FlaxEditor.Modules /// /// Opens a prefab editor window. /// - /// - /// Whether the prefab was successfully opened in a Prefab Editor. - public bool OpenPrefab(Guid prefabID = default) + public void OpenPrefab(ActorNode actorNode) + { + if (actorNode != null) + OpenPrefab(actorNode.Actor.PrefabID); + } + + /// + /// Opens a prefab editor window. + /// + public void OpenPrefab(Guid prefabID = default) { if (prefabID == Guid.Empty) { var selection = Editor.SceneEditing.Selection.Where(x => x is ActorNode actorNode && actorNode.HasPrefabLink).ToList().BuildNodesParents(); if (selection.Count == 0 || !((ActorNode)selection[0]).Actor.HasPrefabLink) - return false; + return; prefabID = ((ActorNode)selection[0]).Actor.PrefabID; } - var item = Editor.Instance.ContentDatabase.Find(prefabID); + var item = Editor.ContentDatabase.Find(prefabID); if (item != null) - { - Editor.Instance.ContentEditing.Open(item); - return true; - } - return false; + Editor.ContentEditing.Open(item); } private void OnPrefabCreated(ContentItem contentItem, Actor actor, Windows.Assets.PrefabWindow prefabWindow) diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index 59954dec5..52341cfc7 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -8,6 +8,7 @@ using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Elements; using FlaxEngine; using FlaxEngine.GUI; +using static FlaxEditor.Viewport.EditorViewport; namespace FlaxEditor.Options { @@ -18,7 +19,7 @@ namespace FlaxEditor.Options [HideInEditor] [TypeConverter(typeof(InputBindingConverter))] [CustomEditor(typeof(InputBindingEditor))] - public struct InputBinding + public struct InputBinding : IEquatable { /// /// The key to bind. @@ -251,6 +252,40 @@ namespace FlaxEditor.Options } return result; } + + /// + public bool Equals(InputBinding other) + { + return Key == other.Key && Modifier1 == other.Modifier1 && Modifier2 == other.Modifier2; + } + + /// + public override bool Equals(object obj) + { + return obj is InputBinding other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine((int)Key, (int)Modifier1, (int)Modifier2); + } + + /// + /// Compares two values. + /// + public static bool operator ==(InputBinding left, InputBinding right) + { + return left.Equals(right); + } + + /// + /// Compares two values. + /// + public static bool operator !=(InputBinding left, InputBinding right) + { + return !left.Equals(right); + } } class InputBindingConverter : TypeConverter @@ -522,5 +557,27 @@ namespace FlaxEditor.Options return false; } + + /// + /// Invokes a specific binding. + /// + /// The editor instance. + /// The binding to execute. + /// True if event has been handled, otherwise false. + public bool Invoke(Editor editor, InputBinding binding) + { + if (binding == new InputBinding()) + return false; + var options = editor.Options.Options.Input; + for (int i = 0; i < Bindings.Count; i++) + { + if (Bindings[i].Binder(options) == binding) + { + Bindings[i].Callback(); + return true; + } + } + return false; + } } } diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 157890159..721b8d6b1 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -7,6 +7,32 @@ using FlaxEngine; namespace FlaxEditor.Options { + /// + /// Action to perform when a Scene Node receive a double mouse left click. + /// + public enum SceneNodeDoubleClick + { + /// + /// Toggles expand/state of the node. + /// + Expand, + + /// + /// Rename the node. + /// + RenameActor, + + /// + /// Focus the object in the viewport. + /// + FocusActor, + + /// + /// If possible, open the scene node in an associated Editor (eg. Prefab Editor). + /// + OpenPrefab, + } + /// /// Input editor options data container. /// @@ -344,9 +370,10 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(2020)] public InputBinding PreviousTab = new InputBinding(KeyboardKeys.Tab, KeyboardKeys.Control, KeyboardKeys.Shift); - [DefaultValue(Windows.SceneNodeDoubleClick.None)] + [DefaultValue(SceneNodeDoubleClick.None)] [EditorDisplay("Interface"), EditorOrder(2030)] - public Windows.SceneNodeDoubleClick DoubleClickSceneNode = Windows.SceneNodeDoubleClick.None; + public SceneNodeDoubleClick DoubleClickSceneNode = SceneNodeDoubleClick.None; + #endregion } } diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index b6b69950d..a1b709efd 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -8,6 +8,7 @@ using FlaxEditor.Content; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; using FlaxEditor.GUI.Tree; +using FlaxEditor.Options; using FlaxEditor.Scripting; using FlaxEditor.Utilities; using FlaxEditor.Windows; @@ -15,7 +16,6 @@ using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; -using Object = FlaxEngine.Object; namespace FlaxEditor.SceneGraph.GUI { @@ -393,7 +393,7 @@ namespace FlaxEditor.SceneGraph.GUI /// /// Starts the actor renaming action. /// - public void StartRenaming(EditorWindow window, Panel treePanel = null) + public void StartRenaming(EditorWindow window = null, Panel treePanel = null) { // Block renaming during scripts reload if (Editor.Instance.ProgressReporting.CompileScripts.IsActive) @@ -461,6 +461,30 @@ namespace FlaxEditor.SceneGraph.GUI } } + /// + protected override bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button) + { + if (button == MouseButton.Left) + { + var sceneContext = this.GetSceneContext(); + switch (Editor.Instance.Options.Options.Input.DoubleClickSceneNode) + { + case SceneNodeDoubleClick.RenameActor: + sceneContext.RenameSelection(); + return true; + case SceneNodeDoubleClick.FocusActor: + sceneContext.FocusSelection(); + return true; + case SceneNodeDoubleClick.OpenPrefab: + Editor.Instance.Prefabs.OpenPrefab(ActorNode); + return true; + case SceneNodeDoubleClick.Expand: + default: break; + } + } + return base.OnMouseDoubleClickHeader(ref location, button); + } + /// protected override DragDropEffect OnDragEnterHeader(DragData data) { diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 6197d9eb2..c9600b089 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1544,5 +1544,12 @@ namespace FlaxEditor.Utilities } return path; } + + internal static ISceneContextWindow GetSceneContext(this Control c) + { + while (c != null && !(c is ISceneContextWindow)) + c = c.Parent; + return c as ISceneContextWindow; + } } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 6a786cb3a..081573b40 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.Content; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Drag; @@ -86,7 +85,7 @@ namespace FlaxEditor.Windows.Assets { return Editor.Instance.CodeEditing.Actors.Get().Contains(actorType); } - + private static bool ValidateDragControlType(ScriptType controlType) { return Editor.Instance.CodeEditing.Controls.Get().Contains(controlType); @@ -171,7 +170,8 @@ namespace FlaxEditor.Windows.Assets var actor = item.OnEditorDrop(this); actor.Name = item.ShortName; _window.Spawn(actor); - var graphNode = _window.Graph.Root.Find(actor);; + var graphNode = _window.Graph.Root.Find(actor); + ; if (graphNode != null) graphNodes.Add(graphNode); } @@ -235,7 +235,8 @@ namespace FlaxEditor.Windows.Assets } actor.Name = actorType.Name; _window.Spawn(actor); - var graphNode = _window.Graph.Root.Find(actor);; + var graphNode = _window.Graph.Root.Find(actor); + ; if (graphNode != null) graphNodes.Add(graphNode); } @@ -290,7 +291,7 @@ namespace FlaxEditor.Windows.Assets // Basic editing options - var b = contextMenu.AddButton("Rename", Rename); + var b = contextMenu.AddButton("Rename", RenameSelection); b.Enabled = isSingleActorSelected; b = contextMenu.AddButton("Duplicate", Duplicate); @@ -414,7 +415,8 @@ namespace FlaxEditor.Windows.Assets contextMenu.Show(parent, location); } - private void Rename() + /// + public void RenameSelection() { var selection = Selection; if (selection.Count != 0 && selection[0] is ActorNode actor) @@ -425,6 +427,12 @@ namespace FlaxEditor.Windows.Assets } } + /// + public void FocusSelection() + { + _viewport.FocusSelection(); + } + /// /// Spawns the specified actor to the prefab (adds actor to root). /// @@ -468,7 +476,7 @@ namespace FlaxEditor.Windows.Assets // Spawn it Spawn(actor); - Rename(); + RenameSelection(); } /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 5bf3236ee..533735c93 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -19,7 +19,7 @@ namespace FlaxEditor.Windows.Assets /// /// /// - public sealed partial class PrefabWindow : AssetEditorWindowBase, IPresenterOwner + public sealed partial class PrefabWindow : AssetEditorWindowBase, IPresenterOwner, ISceneContextWindow { private readonly SplitPanel _split1; private readonly SplitPanel _split2; @@ -212,8 +212,8 @@ namespace FlaxEditor.Windows.Assets InputActions.Add(options => options.Paste, Paste); InputActions.Add(options => options.Duplicate, Duplicate); InputActions.Add(options => options.Delete, Delete); - InputActions.Add(options => options.Rename, Rename); - InputActions.Add(options => options.FocusSelection, _viewport.FocusSelection); + InputActions.Add(options => options.Rename, RenameSelection); + InputActions.Add(options => options.FocusSelection, FocusSelection); } /// diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index bcdc96480..62922ebed 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -1,14 +1,32 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using FlaxEditor.SceneGraph; +using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Windows { + /// + /// Shared interface for scene editing utilities. + /// + public interface ISceneContextWindow + { + /// + /// Opends popup for renaming selected objects. + /// + void RenameSelection(); + + /// + /// Focuses selected objects. + /// + void FocusSelection(); + } + /// /// Base class for editor windows dedicated to scene editing. /// /// - public abstract class SceneEditorWindow : EditorWindow + public abstract class SceneEditorWindow : EditorWindow, ISceneContextWindow { /// /// Initializes a new instance of the class. @@ -21,5 +39,38 @@ namespace FlaxEditor.Windows { FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } + + /// + public void FocusSelection() + { + Editor.Windows.EditWin.Viewport.FocusSelection(); + } + + /// + public void RenameSelection() + { + var selection = Editor.SceneEditing.Selection; + var selectionCount = selection.Count; + + // Show a window with options to rename multiple actors. + if (selectionCount > 1) + { + var selectedActors = new Actor[selectionCount]; + + for (int i = 0; i < selectionCount; i++) + if (selection[i] is ActorNode actorNode) + selectedActors[i] = actorNode.Actor; + + RenameWindow.Show(selectedActors, Editor); + return; + } + + if (selectionCount != 0 && selection[0] is ActorNode actor) + { + Editor.SceneEditing.Select(actor); + var sceneWindow = Editor.Windows.SceneWin; + actor.TreeNode.StartRenaming(sceneWindow, sceneWindow.SceneTreePanel); + } + } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index a1f8209eb..8591ecb9f 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -53,7 +53,7 @@ namespace FlaxEditor.Windows // Basic editing options var firstSelection = hasSthSelected ? Editor.SceneEditing.Selection[0] as ActorNode : null; - b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename); + b = contextMenu.AddButton("Rename", inputOptions.Rename, RenameSelection); b.Enabled = hasSthSelected; b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); b.Enabled = hasSthSelected && (firstSelection != null ? firstSelection.CanDuplicate : true); @@ -145,7 +145,7 @@ namespace FlaxEditor.Windows bool hasPrefabLink = canEditScene && isSingleActorSelected && (firstSelection != null ? firstSelection.HasPrefabLink : false); if (hasPrefabLink) { - contextMenu.AddButton("Open Prefab", () => Editor.Prefabs.OpenPrefab(actorNode.Actor.PrefabID)); + contextMenu.AddButton("Open Prefab", () => Editor.Prefabs.OpenPrefab(firstSelection)); contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab); contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks); } diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 8a500d108..7b5e84c02 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -96,7 +96,7 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.ScaleMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); InputActions.Add(options => options.FocusSelection, () => Editor.Windows.EditWin.Viewport.FocusSelection()); InputActions.Add(options => options.LockFocusSelection, () => Editor.Windows.EditWin.Viewport.LockFocusSelection()); - InputActions.Add(options => options.Rename, Rename); + InputActions.Add(options => options.Rename, RenameSelection); } /// @@ -143,31 +143,6 @@ namespace FlaxEditor.Windows PerformLayout(); } - private void Rename() - { - var selection = Editor.SceneEditing.Selection; - var selectionCount = selection.Count; - - // Show a window with options to rename multiple actors. - if (selectionCount > 1) - { - var selectedActors = new Actor[selectionCount]; - - for (int i = 0; i < selectionCount; i++) - if (selection[i] is ActorNode actorNode) - selectedActors[i] = actorNode.Actor; - - RenameWindow.Show(selectedActors, Editor); - return; - } - - if (selectionCount != 0 && selection[0] is ActorNode actor) - { - Editor.SceneEditing.Select(actor); - actor.TreeNode.StartRenaming(this, _sceneTreePanel); - } - } - private void Spawn(Type type) { // Create actor @@ -197,7 +172,7 @@ namespace FlaxEditor.Windows Editor.SceneEditing.Spawn(actor, parentActor); Editor.SceneEditing.Select(actor); - Rename(); + RenameSelection(); } /// @@ -293,7 +268,7 @@ namespace FlaxEditor.Windows { return Editor.Instance.CodeEditing.Actors.Get().Contains(actorType); } - + private static bool ValidateDragControlType(ScriptType controlType) { return Editor.Instance.CodeEditing.Controls.Get().Contains(controlType); @@ -382,29 +357,6 @@ namespace FlaxEditor.Windows return false; } - - /// - public override bool OnMouseDoubleClick(Float2 location, MouseButton button) - { - if(button == MouseButton.Left) - { - switch (Editor.Options.Options.Input.DoubleClickSceneNode) - { - case SceneNodeDoubleClick.RenameActor: - Rename(); - return true; - case SceneNodeDoubleClick.FocusActor: - Editor.Windows.EditWin.Viewport.FocusSelection(); - return true; - case SceneNodeDoubleClick.OpenPrefab: - return Editor.Prefabs.OpenPrefab(); - case SceneNodeDoubleClick.None: - default: - return base.OnMouseDoubleClick(location, button); - } - } - return base.OnMouseDoubleClick(location, button); - } /// public override void OnLostFocus() @@ -593,28 +545,4 @@ namespace FlaxEditor.Windows base.OnDestroy(); } } - - /// - /// Action to perform when a Scene Node receive a double mouse click (Left) - /// - [Serializable] - public enum SceneNodeDoubleClick - { - /// - /// No action is performed - /// - None, - /// - /// Rename the Scene Node - /// - RenameActor, - /// - /// Focus the Scene Node object in the Scene View - /// - FocusActor, - /// - /// If possible, open the scene node in an associated Editor (e.g. Prefab Editor) - /// - OpenPrefab - } }