// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Windows.Assets { public sealed partial class PrefabWindow { /// /// The custom implementation of the root node for the scene graph. /// public class CustomRootNode : RootNode { private readonly PrefabWindow _window; /// /// Initializes a new instance of the class. /// /// The window. public CustomRootNode(PrefabWindow window) { _window = window; } /// public override void Spawn(Actor actor, Actor parent) { _window.Spawn(actor, parent); } /// public override Undo Undo => _window.Undo; /// public override List Selection => _window.Selection; } /// /// The prefab hierarchy tree control. /// /// public class PrefabTree : Tree { /// /// Initializes a new instance of the class. /// public PrefabTree() : base(true) { } } /// /// Occurs when prefab hierarchy panel wants to show the context menu. Allows to add custom options. Applies to all prefab windows. /// public static event Action ContextMenuShow; /// /// Creates the context menu for the current objects selection. /// /// The context menu. private ContextMenu CreateContextMenu() { // Prepare bool hasSthSelected = Selection.Count > 0; bool isSingleActorSelected = Selection.Count == 1 && Selection[0] is ActorNode; bool isRootSelected = isSingleActorSelected && Selection[0] == Graph.Main; bool hasPrefabLink = isSingleActorSelected && (Selection[0] as ActorNode).HasPrefabLink; // Create popup var contextMenu = new ContextMenu { MinimumWidth = 120 }; // Basic editing options var b = contextMenu.AddButton("Rename", Rename); b.Enabled = isSingleActorSelected; b = contextMenu.AddButton("Duplicate", Duplicate); b.Enabled = hasSthSelected && !isRootSelected; b = contextMenu.AddButton("Delete", Delete); b.Enabled = hasSthSelected && !isRootSelected; contextMenu.AddSeparator(); b = contextMenu.AddButton("Copy", Copy); b.Enabled = hasSthSelected; b.Enabled = hasSthSelected; contextMenu.AddButton("Paste", Paste); b = contextMenu.AddButton("Cut", Cut); b.Enabled = hasSthSelected && !isRootSelected; b = contextMenu.AddButton("Set Root", SetRoot); b.Enabled = isSingleActorSelected && !isRootSelected && hasPrefabLink; // Prefab options contextMenu.AddSeparator(); b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab); b.Enabled = isSingleActorSelected && (Selection[0] as ActorNode).CanCreatePrefab && Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; b = contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab); b.Enabled = hasPrefabLink; // Spawning actors options contextMenu.AddSeparator(); var spawnMenu = contextMenu.AddChildMenu("New"); var newActorCm = spawnMenu.ContextMenu; for (int i = 0; i < SceneTreeWindow.SpawnActorsGroups.Length; i++) { var group = SceneTreeWindow.SpawnActorsGroups[i]; if (group.Types.Length == 1) { var type = group.Types[0].Value; newActorCm.AddButton(group.Types[0].Key, () => Spawn(type)); } else { var groupCm = newActorCm.AddChildMenu(group.Name).ContextMenu; for (int j = 0; j < group.Types.Length; j++) { var type = group.Types[j].Value; groupCm.AddButton(group.Types[j].Key, () => Spawn(type)); } } } // Custom options ContextMenuShow?.Invoke(contextMenu); return contextMenu; } /// /// Shows the context menu on a given location (in the given control coordinates). /// /// The parent control. /// The location (within a given control). private void ShowContextMenu(Control parent, ref Vector2 location) { var contextMenu = CreateContextMenu(); contextMenu.Show(parent, location); } private void Rename() { var selection = Selection; if (selection.Count != 0 && selection[0] is ActorNode actor) { if (selection.Count != 0) Select(actor); actor.TreeNode.StartRenaming(); } } /// /// Spawns the specified actor to the prefab (adds actor to root). /// /// The actor. public void Spawn(Actor actor) { // Link to parent Actor parentActor = null; if (Selection.Count > 0 && Selection[0] is ActorNode actorNode) { parentActor = actorNode.Actor; actorNode.TreeNode.Expand(); } if (parentActor == null) { parentActor = Graph.MainActor; } if (parentActor != null) { // Use the same location actor.Transform = parentActor.Transform; // Rename actor to identify it easily actor.Name = StringUtils.IncrementNameNumber(actor.GetType().Name, x => parentActor.GetChild(x) == null); } // Spawn it Spawn(actor, parentActor); } /// /// Spawns the actor of the specified type to the prefab (adds actor to root). /// /// The actor type. public void Spawn(Type type) { // Create actor Actor actor = (Actor)FlaxEngine.Object.New(type); // Spawn it Spawn(actor); } /// /// Spawns the specified actor. /// /// The actor. /// The parent. public void Spawn(Actor actor, Actor parent) { if (actor == null) throw new ArgumentNullException(nameof(actor)); // Link it actor.Parent = parent ?? throw new ArgumentNullException(nameof(parent)); // Peek spawned node var actorNode = SceneGraphFactory.FindNode(actor.ID) as ActorNode ?? SceneGraphFactory.BuildActorNode(actor); if (actorNode == null) throw new InvalidOperationException("Failed to create scene node for the spawned actor."); var parentNode = SceneGraphFactory.FindNode(parent.ID) as ActorNode; actorNode.ParentNode = parentNode ?? throw new InvalidOperationException("Missing scene graph node for the spawned parent actor."); // Call post spawn action (can possibly setup custom default values) actorNode.PostSpawn(); // Create undo action var action = new CustomDeleteActorsAction(new List(1) { actorNode }, true); Undo.AddAction(action); } private void OnTreeRightClick(TreeNode node, Vector2 location) { // Skip if it's loading data if (Graph.Main == null) return; ShowContextMenu(node, ref location); } private void Update(ActorNode actorNode) { actorNode.TreeNode.OnNameChanged(); actorNode.TreeNode.OnOrderInParentChanged(); for (int i = 0; i < actorNode.ChildNodes.Count; i++) { if (actorNode.ChildNodes[i] is ActorNode child) Update(child); } } } }