Files
FlaxEngine/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
Wojciech Figat a7e428a21c Merge branch 'master' into 1.5
# Conflicts:
#	Content/Shaders/GI/DDGI.flax
#	Content/Shaders/GI/GlobalSurfaceAtlas.flax
#	Content/Shaders/TAA.flax
#	Content/Shaders/VolumetricFog.flax
#	Source/Editor/CustomEditors/Editors/ActorTagEditor.cs
#	Source/Engine/Core/Config/GraphicsSettings.cpp
#	Source/Engine/Engine/PostProcessEffect.cs
#	Source/Engine/Graphics/GPUResourcesCollection.cpp
#	Source/Engine/Graphics/GPUResourcesCollection.h
#	Source/Engine/Graphics/PostProcessBase.h
#	Source/FlaxEngine.Gen.cs
2023-01-10 15:37:55 +01:00

747 lines
24 KiB
C#

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Drag;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.SceneGraph.GUI
{
/// <summary>
/// Tree node GUI control used as a proxy object for actors hierarchy.
/// </summary>
/// <seealso cref="TreeNode" />
public class ActorTreeNode : TreeNode
{
private int _orderInParent;
private DragActors _dragActors;
private DragScripts _dragScripts;
private DragAssets _dragAssets;
private DragActorType _dragActorType;
private DragHandlers _dragHandlers;
private List<Rectangle> _highlights;
private bool _hasSearchFilter;
/// <summary>
/// The actor node that owns this node.
/// </summary>
protected ActorNode _actorNode;
/// <summary>
/// Gets the actor.
/// </summary>
public Actor Actor => _actorNode.Actor;
/// <summary>
/// Gets the actor node.
/// </summary>
public ActorNode ActorNode => _actorNode;
/// <summary>
/// Initializes a new instance of the <see cref="ActorTreeNode"/> class.
/// </summary>
public ActorTreeNode()
: base(true)
{
ChildrenIndent = 16.0f;
}
internal virtual void LinkNode(ActorNode node)
{
_actorNode = node;
var actor = node.Actor;
if (actor != null)
{
_orderInParent = actor.OrderInParent;
Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0;
var id = actor.ID;
if (Editor.Instance.ProjectCache.IsExpandedActor(ref id))
{
Expand(true);
}
}
else
{
_orderInParent = 0;
}
UpdateText();
}
internal void OnOrderInParentChanged()
{
if (Parent is ActorTreeNode parent)
{
for (int i = 0; i < parent.ChildrenCount; i++)
{
if (parent.Children[i] is ActorTreeNode child && child.Actor)
child._orderInParent = child.Actor.OrderInParent;
}
parent.SortChildren();
}
else if (Actor)
{
_orderInParent = Actor.OrderInParent;
}
}
internal void OnNameChanged()
{
UpdateText();
}
/// <summary>
/// Updates the tree node text.
/// </summary>
public virtual void UpdateText()
{
Text = _actorNode.Name;
}
/// <summary>
/// Updates the query search filter.
/// </summary>
/// <param name="filterText">The filter text.</param>
public void UpdateFilter(string filterText)
{
// SKip hidden actors
var actor = Actor;
if (actor != null && (actor.HideFlags & HideFlags.HideInHierarchy) != 0)
return;
bool noFilter = string.IsNullOrWhiteSpace(filterText);
_hasSearchFilter = !noFilter;
// Update itself
bool isThisVisible;
if (noFilter)
{
// Clear filter
_highlights?.Clear();
isThisVisible = true;
}
else
{
var text = Text;
if (QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
{
// Update highlights
if (_highlights == null)
_highlights = new List<Rectangle>(ranges.Length);
else
_highlights.Clear();
var font = Style.Current.FontSmall;
var textRect = TextRect;
for (int i = 0; i < ranges.Length; i++)
{
var start = font.GetCharPosition(text, ranges[i].StartIndex);
var end = font.GetCharPosition(text, ranges[i].EndIndex);
_highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height));
}
isThisVisible = true;
}
else
{
// Hide
_highlights?.Clear();
isThisVisible = false;
}
}
// Update children
bool isAnyChildVisible = false;
for (int i = 0; i < _children.Count; i++)
{
if (_children[i] is ActorTreeNode child)
{
child.UpdateFilter(filterText);
isAnyChildVisible |= child.Visible;
}
}
bool isExpanded = isAnyChildVisible;
// Restore cached state on query filter clear
if (noFilter && actor != null)
{
var id = actor.ID;
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
if (isExpanded)
{
Expand(true);
}
else
{
Collapse(true);
}
Visible = isThisVisible | isAnyChildVisible;
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
// Update hidden state
var actor = Actor;
if (actor && !_hasSearchFilter)
{
Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0;
}
base.Update(deltaTime);
}
/// <inheritdoc />
protected override bool ShowTooltip => true;
/// <inheritdoc />
public override bool OnShowTooltip(out string text, out Float2 location, out Rectangle area)
{
// Evaluate tooltip text once it's actually needed
var actor = _actorNode.Actor;
if (string.IsNullOrEmpty(TooltipText) && actor)
TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(TypeUtils.GetObjectType(actor));
return base.OnShowTooltip(out text, out location, out area);
}
/// <inheritdoc />
protected override Color CacheTextColor()
{
// Update node text color (based on ActorNode.IsActiveInHierarchy but with optimized logic a little)
if (Parent is ActorTreeNode)
{
Color color = Style.Current.Foreground;
var actor = Actor;
if (actor)
{
if (actor.HasPrefabLink)
{
// Prefab
color = Style.Current.ProgressNormal;
}
if (!actor.IsActiveInHierarchy)
{
// Inactive
return Style.Current.ForegroundGrey;
}
if (actor.Scene != null && Editor.Instance.StateMachine.IsPlayMode && actor.IsStatic)
{
// Static
return color * 0.85f;
}
}
// Default
return color;
}
return base.CacheTextColor();
}
/// <inheritdoc />
public override int Compare(Control other)
{
if (other is ActorTreeNode node)
{
return _orderInParent - node._orderInParent;
}
return base.Compare(other);
}
/// <summary>
/// Starts the actor renaming action.
/// </summary>
public void StartRenaming(EditorWindow window)
{
// Block renaming during scripts reload
if (Editor.Instance.ProgressReporting.CompileScripts.IsActive)
return;
Select();
// Disable scrolling of view
if (window is SceneTreeWindow)
(window as SceneTreeWindow).ScrollingOnSceneTreeView(false);
else if (window is PrefabWindow)
(window as PrefabWindow).ScrollingOnTreeView(false);
// Start renaming the actor
var dialog = RenamePopup.Show(this, TextRect, _actorNode.Name, false);
dialog.Renamed += OnRenamed;
dialog.Closed += popup =>
{
// Enable scrolling of view
if (window is SceneTreeWindow)
(window as SceneTreeWindow).ScrollingOnSceneTreeView(true);
else if (window is PrefabWindow)
(window as PrefabWindow).ScrollingOnTreeView(true);
};
}
private void OnRenamed(RenamePopup renamePopup)
{
using (new UndoBlock(ActorNode.Root.Undo, Actor, "Rename"))
Actor.Name = renamePopup.Text;
}
/// <inheritdoc />
protected override void OnExpandedChanged()
{
base.OnExpandedChanged();
if (!IsLayoutLocked && Actor)
{
var id = Actor.ID;
Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded);
}
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
// Draw all highlights
if (_highlights != null)
{
var style = Style.Current;
var color = style.ProgressNormal * 0.6f;
for (int i = 0; i < _highlights.Count; i++)
Render2D.FillRectangle(_highlights[i], color);
}
}
/// <inheritdoc />
protected override DragDropEffect OnDragEnterHeader(DragData data)
{
// Check if cannot edit scene or there is no scene loaded (handle case for actors in prefab editor)
if (_actorNode?.ParentScene != null)
{
if (!Editor.Instance.StateMachine.CurrentState.CanEditScene || !Level.IsAnySceneLoaded)
return DragDropEffect.None;
}
else
{
if (!Editor.Instance.StateMachine.CurrentState.CanEditContent)
return DragDropEffect.None;
}
if (_dragHandlers == null)
_dragHandlers = new DragHandlers();
// Check if drop actors
if (_dragActors == null)
{
_dragActors = new DragActors(ValidateDragActor);
_dragHandlers.Add(_dragActors);
}
if (_dragActors.OnDragEnter(data))
return _dragActors.Effect;
// Check if drop scripts
if (_dragScripts == null)
{
_dragScripts = new DragScripts(ValidateDragScript);
_dragHandlers.Add(_dragScripts);
}
if (_dragScripts.OnDragEnter(data))
return _dragScripts.Effect;
// Check if drag assets
if (_dragAssets == null)
{
_dragAssets = new DragAssets(ValidateDragAsset);
_dragHandlers.Add(_dragAssets);
}
if (_dragAssets.OnDragEnter(data))
return _dragAssets.Effect;
// Check if drag actor type
if (_dragActorType == null)
{
_dragActorType = new DragActorType(ValidateDragActorType);
_dragHandlers.Add(_dragActorType);
}
if (_dragActorType.OnDragEnter(data))
return _dragActorType.Effect;
return DragDropEffect.None;
}
/// <inheritdoc />
protected override DragDropEffect OnDragMoveHeader(DragData data)
{
return _dragHandlers.Effect;
}
/// <inheritdoc />
protected override void OnDragLeaveHeader()
{
_dragHandlers.OnDragLeave();
}
[Serializable]
private class ReparentAction : IUndoAction
{
[Serialize]
private Guid[] _ids;
[Serialize]
private int _actorsCount;
[Serialize]
private Guid[] _prefabIds;
[Serialize]
private Guid[] _prefabObjectIds;
public ReparentAction(Actor actor)
: this(new List<Actor> { actor })
{
}
public ReparentAction(List<Actor> actors)
{
var allActors = new List<Actor>(Mathf.NextPowerOfTwo(actors.Count));
for (int i = 0; i < actors.Count; i++)
{
GetAllActors(allActors, actors[i]);
}
var allScripts = new List<Script>(allActors.Capacity);
GetAllScripts(allActors, allScripts);
int allCount = allActors.Count + allScripts.Count;
_actorsCount = allActors.Count;
_ids = new Guid[allCount];
_prefabIds = new Guid[allCount];
_prefabObjectIds = new Guid[allCount];
for (int i = 0; i < allActors.Count; i++)
{
_ids[i] = allActors[i].ID;
_prefabIds[i] = allActors[i].PrefabID;
_prefabObjectIds[i] = allActors[i].PrefabObjectID;
}
for (int i = 0; i < allScripts.Count; i++)
{
int j = _actorsCount + i;
_ids[j] = allScripts[i].ID;
_prefabIds[j] = allScripts[i].PrefabID;
_prefabObjectIds[j] = allScripts[i].PrefabObjectID;
}
}
public ReparentAction(Script script)
{
_actorsCount = 0;
_ids = new Guid[] { script.ID };
_prefabIds = new Guid[] { script.PrefabID };
_prefabObjectIds = new Guid[] { script.PrefabObjectID };
}
private void GetAllActors(List<Actor> allActors, Actor actor)
{
allActors.Add(actor);
for (int i = 0; i < actor.ChildrenCount; i++)
{
var child = actor.GetChild(i);
if (!allActors.Contains(child))
{
GetAllActors(allActors, child);
}
}
}
private void GetAllScripts(List<Actor> allActors, List<Script> allScripts)
{
for (int i = 0; i < allActors.Count; i++)
{
var actor = allActors[i];
for (int j = 0; j < actor.ScriptsCount; j++)
{
allScripts.Add(actor.GetScript(j));
}
}
}
/// <inheritdoc />
public string ActionString => string.Empty;
/// <inheritdoc />
public void Do()
{
// Note: prefab links are broken by the C++ backend on actor reparenting
}
/// <inheritdoc />
public void Undo()
{
// Restore links
for (int i = 0; i < _actorsCount; i++)
{
var actor = Object.Find<Actor>(ref _ids[i]);
if (actor != null && _prefabIds[i] != Guid.Empty)
{
Actor.Internal_LinkPrefab(Object.GetUnmanagedPtr(actor), ref _prefabIds[i], ref _prefabObjectIds[i]);
}
}
for (int i = _actorsCount; i < _ids.Length; i++)
{
var script = Object.Find<Script>(ref _ids[i]);
if (script != null && _prefabIds[i] != Guid.Empty)
{
Script.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabIds[i], ref _prefabObjectIds[i]);
}
}
}
/// <inheritdoc />
public void Dispose()
{
_ids = null;
_prefabIds = null;
_prefabObjectIds = null;
}
}
/// <inheritdoc />
protected override DragDropEffect OnDragDropHeader(DragData data)
{
var result = DragDropEffect.None;
Actor myActor = Actor;
Actor newParent;
int newOrder = -1;
// Check if has no actor (only for Root Actor)
if (myActor == null)
{
// Append to the last scene
var scenes = Level.Scenes;
if (scenes == null || scenes.Length == 0)
throw new InvalidOperationException("No scene loaded.");
newParent = scenes[scenes.Length - 1];
}
else
{
newParent = myActor;
// Use drag positioning to change target parent and index
if (DragOverMode == DragItemPositioning.Above)
{
if (myActor.HasParent)
{
newParent = myActor.Parent;
newOrder = myActor.OrderInParent;
}
}
else if (DragOverMode == DragItemPositioning.Below)
{
if (myActor.HasParent)
{
newParent = myActor.Parent;
newOrder = myActor.OrderInParent + 1;
}
}
}
if (newParent == null)
throw new InvalidOperationException("Missing parent actor.");
// Drag actors
if (_dragActors != null && _dragActors.HasValidDrag)
{
bool worldPositionLock = Root.GetKey(KeyboardKeys.Control) == false;
var singleObject = _dragActors.Objects.Count == 1;
if (singleObject)
{
var targetActor = _dragActors.Objects[0].Actor;
var customAction = targetActor.HasPrefabLink ? new ReparentAction(targetActor) : null;
using (new UndoBlock(ActorNode.Root.Undo, targetActor, "Change actor parent", customAction))
{
targetActor.SetParent(newParent, worldPositionLock, true);
targetActor.OrderInParent = newOrder;
}
}
else
{
var targetActors = _dragActors.Objects.ConvertAll(x => x.Actor);
var customAction = targetActors.Any(x => x.HasPrefabLink) ? new ReparentAction(targetActors) : null;
using (new UndoMultiBlock(ActorNode.Root.Undo, targetActors, "Change actors parent", customAction))
{
for (int i = 0; i < targetActors.Count; i++)
{
var targetActor = targetActors[i];
targetActor.SetParent(newParent, worldPositionLock, true);
targetActor.OrderInParent = newOrder;
}
}
}
result = DragDropEffect.Move;
}
// Drag scripts
else if (_dragScripts != null && _dragScripts.HasValidDrag)
{
foreach(var script in _dragScripts.Objects)
{
var customAction = script.HasPrefabLink ? new ReparentAction(script) : null;
using (new UndoBlock(ActorNode.Root.Undo, script, "Change script parent", customAction))
{
script.SetParent(newParent, true);
}
}
result = DragDropEffect.Move;
}
// Drag assets
else if (_dragAssets != null && _dragAssets.HasValidDrag)
{
for (int i = 0; i < _dragAssets.Objects.Count; i++)
{
var item = _dragAssets.Objects[i];
var actor = item.OnEditorDrop(this);
actor.StaticFlags = Actor.StaticFlags;
actor.Name = item.ShortName;
actor.Transform = Actor.Transform;
ActorNode.Root.Spawn(actor, Actor);
}
result = DragDropEffect.Move;
}
// Drag actor type
else if (_dragActorType != null && _dragActorType.HasValidDrag)
{
for (int i = 0; i < _dragActorType.Objects.Count; i++)
{
var item = _dragActorType.Objects[i];
// Create actor
var actor = item.CreateInstance() as Actor;
if (actor == null)
{
Editor.LogWarning("Failed to spawn actor of type " + item.TypeName);
continue;
}
actor.StaticFlags = Actor.StaticFlags;
actor.Name = item.Name;
actor.Transform = Actor.Transform;
// Spawn
ActorNode.Root.Spawn(actor, Actor);
}
result = DragDropEffect.Move;
}
// Clear cache
_dragHandlers.OnDragDrop(null);
// Check if scene has been modified
if (result != DragDropEffect.None)
{
var node = SceneGraphFactory.FindNode(newParent.ID) as ActorNode;
node?.TreeNode.Expand();
}
return result;
}
private bool ValidateDragActor(ActorNode actorNode)
{
// Reject dragging actors not linked to scene (eg. from prefab) or in the opposite way
var thisHasScene = ActorNode.ParentScene != null;
var otherHasScene = actorNode.ParentScene != null;
if (thisHasScene != otherHasScene)
return false;
// Reject dragging parents and itself
return actorNode.Actor != null && actorNode != ActorNode && actorNode.Find(Actor) == null;
}
private bool ValidateDragScript(Script script)
{
// Reject dragging scripts not linked to scene (eg. from prefab) or in the opposite way
var thisHasScene = Actor.Scene != null;
var otherHasScene = script.Scene != null;
if (thisHasScene != otherHasScene)
return false;
// Reject dragging parents and itself
return script.Actor != null && script.Parent != Actor;
}
private bool ValidateDragAsset(AssetItem assetItem)
{
return assetItem.OnEditorDrag(this);
}
private static bool ValidateDragActorType(ScriptType actorType)
{
return true;
}
/// <inheritdoc />
protected override void DoDragDrop()
{
DragData data;
var tree = ParentTree;
// Check if this node is selected
if (tree.Selection.Contains(this))
{
// Get selected actors
var actors = new List<ActorNode>();
for (var i = 0; i < tree.Selection.Count; i++)
{
var e = tree.Selection[i];
if (e is ActorTreeNode node && node.ActorNode.CanDrag)
actors.Add(node.ActorNode);
}
data = DragActors.GetDragData(actors);
}
else
{
data = DragActors.GetDragData(ActorNode);
}
// Start drag operation
DoDragDrop(data);
}
/// <inheritdoc />
public override void OnDestroy()
{
_dragActors = null;
_dragScripts = null;
_dragAssets = null;
_dragActorType = null;
_dragHandlers?.Clear();
_dragHandlers = null;
_highlights = null;
base.OnDestroy();
}
}
}