Refactor new scene tree double click feature to support both prefab and scene editing

#1502
This commit is contained in:
Wojtek Figat
2025-02-23 21:23:09 +01:00
parent 618027b181
commit fc4b79239b
10 changed files with 208 additions and 103 deletions

View File

@@ -108,25 +108,28 @@ namespace FlaxEditor.Modules
/// <summary>
/// Opens a prefab editor window.
/// </summary>
///
/// <returns>Whether the prefab was successfully opened in a Prefab Editor.</returns>
public bool OpenPrefab(Guid prefabID = default)
public void OpenPrefab(ActorNode actorNode)
{
if (actorNode != null)
OpenPrefab(actorNode.Actor.PrefabID);
}
/// <summary>
/// Opens a prefab editor window.
/// </summary>
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)

View File

@@ -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<InputBinding>
{
/// <summary>
/// The key to bind.
@@ -251,6 +252,40 @@ namespace FlaxEditor.Options
}
return result;
}
/// <inheritdoc />
public bool Equals(InputBinding other)
{
return Key == other.Key && Modifier1 == other.Modifier1 && Modifier2 == other.Modifier2;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is InputBinding other && Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine((int)Key, (int)Modifier1, (int)Modifier2);
}
/// <summary>
/// Compares two values.
/// </summary>
public static bool operator ==(InputBinding left, InputBinding right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two values.
/// </summary>
public static bool operator !=(InputBinding left, InputBinding right)
{
return !left.Equals(right);
}
}
class InputBindingConverter : TypeConverter
@@ -522,5 +557,27 @@ namespace FlaxEditor.Options
return false;
}
/// <summary>
/// Invokes a specific binding.
/// </summary>
/// <param name="editor">The editor instance.</param>
/// <param name="binding">The binding to execute.</param>
/// <returns>True if event has been handled, otherwise false.</returns>
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;
}
}
}

View File

@@ -7,6 +7,32 @@ using FlaxEngine;
namespace FlaxEditor.Options
{
/// <summary>
/// Action to perform when a Scene Node receive a double mouse left click.
/// </summary>
public enum SceneNodeDoubleClick
{
/// <summary>
/// Toggles expand/state of the node.
/// </summary>
Expand,
/// <summary>
/// Rename the node.
/// </summary>
RenameActor,
/// <summary>
/// Focus the object in the viewport.
/// </summary>
FocusActor,
/// <summary>
/// If possible, open the scene node in an associated Editor (eg. Prefab Editor).
/// </summary>
OpenPrefab,
}
/// <summary>
/// Input editor options data container.
/// </summary>
@@ -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
}
}

View File

@@ -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
/// <summary>
/// Starts the actor renaming action.
/// </summary>
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
}
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
protected override DragDropEffect OnDragEnterHeader(DragData data)
{

View File

@@ -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;
}
}
}

View File

@@ -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()
/// <inheritdoc />
public void RenameSelection()
{
var selection = Selection;
if (selection.Count != 0 && selection[0] is ActorNode actor)
@@ -425,6 +427,12 @@ namespace FlaxEditor.Windows.Assets
}
}
/// <inheritdoc />
public void FocusSelection()
{
_viewport.FocusSelection();
}
/// <summary>
/// Spawns the specified actor to the prefab (adds actor to root).
/// </summary>
@@ -468,7 +476,7 @@ namespace FlaxEditor.Windows.Assets
// Spawn it
Spawn(actor);
Rename();
RenameSelection();
}
/// <summary>

View File

@@ -19,7 +19,7 @@ namespace FlaxEditor.Windows.Assets
/// </summary>
/// <seealso cref="Prefab" />
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed partial class PrefabWindow : AssetEditorWindowBase<Prefab>, IPresenterOwner
public sealed partial class PrefabWindow : AssetEditorWindowBase<Prefab>, 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);
}
/// <summary>

View File

@@ -1,14 +1,32 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.SceneGraph;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Windows
{
/// <summary>
/// Shared interface for scene editing utilities.
/// </summary>
public interface ISceneContextWindow
{
/// <summary>
/// Opends popup for renaming selected objects.
/// </summary>
void RenameSelection();
/// <summary>
/// Focuses selected objects.
/// </summary>
void FocusSelection();
}
/// <summary>
/// Base class for editor windows dedicated to scene editing.
/// </summary>
/// <seealso cref="FlaxEditor.Windows.EditorWindow" />
public abstract class SceneEditorWindow : EditorWindow
public abstract class SceneEditorWindow : EditorWindow, ISceneContextWindow
{
/// <summary>
/// Initializes a new instance of the <see cref="SceneEditorWindow"/> class.
@@ -21,5 +39,38 @@ namespace FlaxEditor.Windows
{
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
}
/// <inheritdoc />
public void FocusSelection()
{
Editor.Windows.EditWin.Viewport.FocusSelection();
}
/// <inheritdoc />
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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
/// <summary>
@@ -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();
}
/// <summary>
@@ -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;
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public override void OnLostFocus()
@@ -593,28 +545,4 @@ namespace FlaxEditor.Windows
base.OnDestroy();
}
}
/// <summary>
/// Action to perform when a Scene Node receive a double mouse click (Left)
/// </summary>
[Serializable]
public enum SceneNodeDoubleClick
{
/// <summary>
/// No action is performed
/// </summary>
None,
/// <summary>
/// Rename the Scene Node
/// </summary>
RenameActor,
/// <summary>
/// Focus the Scene Node object in the Scene View
/// </summary>
FocusActor,
/// <summary>
/// If possible, open the scene node in an associated Editor (e.g. Prefab Editor)
/// </summary>
OpenPrefab
}
}