// Copyright (c) 2012-2021 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 FlaxEngine; using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.SceneGraph.GUI { /// /// Tree node GUI control used as a proxy object for actors hierarchy. /// /// public class ActorTreeNode : TreeNode { private int _orderInParent; private DragActors _dragActors; private DragAssets _dragAssets; private DragActorType _dragActorType; private DragHandlers _dragHandlers; private List _highlights; private bool _hasSearchFilter; /// /// The actor node that owns this node. /// protected ActorNode _actorNode; /// /// Gets the actor. /// public Actor Actor => _actorNode.Actor; /// /// Gets the actor node. /// public ActorNode ActorNode => _actorNode; /// /// Initializes a new instance of the class. /// 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._orderInParent = child.Actor.OrderInParent; } parent.SortChildren(); } else if (Actor) { _orderInParent = Actor.OrderInParent; } } internal void OnNameChanged() { UpdateText(); } /// /// Updates the tree node text. /// public virtual void UpdateText() { Text = _actorNode.Name; } /// /// Updates the query search filter. /// /// The filter text. 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(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; } /// public override void Update(float deltaTime) { // Update hidden state var actor = Actor; if (actor != null && !_hasSearchFilter) { Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0; } base.Update(deltaTime); } /// 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 != null && actor.HasPrefabLink) { // Prefab color = Style.Current.ProgressNormal; } if (actor != null && !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(); } /// public override int Compare(Control other) { if (other is ActorTreeNode node) { return _orderInParent - node._orderInParent; } return base.Compare(other); } /// /// Starts the actor renaming action. /// public void StartRenaming() { // Block renaming during scripts reload if (Editor.Instance.ProgressReporting.CompileScripts.IsActive) return; Select(); // Start renaming the actor var dialog = RenamePopup.Show(this, HeaderRect, _actorNode.Name, false); dialog.Renamed += OnRenamed; } private void OnRenamed(RenamePopup renamePopup) { using (new UndoBlock(ActorNode.Root.Undo, Actor, "Rename")) Actor.Name = renamePopup.Text; } /// protected override void OnExpandedChanged() { base.OnExpandedChanged(); if (!IsLayoutLocked && Actor) { var id = Actor.ID; Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded); } } /// 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); } } /// 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 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; } /// protected override DragDropEffect OnDragMoveHeader(DragData data) { return _dragHandlers.Effect; } /// 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 }) { } public ReparentAction(List actors) { var allActors = new List(Mathf.NextPowerOfTwo(actors.Count)); for (int i = 0; i < actors.Count; i++) { GetAllActors(allActors, actors[i]); } var allScripts = new List