Merge branch 'Tryibion-add-content-tree-view'
This commit is contained in:
@@ -19,7 +19,7 @@ namespace FlaxEditor.Content.GUI
|
||||
/// <summary>
|
||||
/// Gets the target node.
|
||||
/// </summary>
|
||||
public ContentTreeNode TargetNode { get; }
|
||||
public ContentFolderTreeNode TargetNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentNavigationButton"/> class.
|
||||
@@ -28,7 +28,7 @@ namespace FlaxEditor.Content.GUI
|
||||
/// <param name="x">The x position.</param>
|
||||
/// <param name="y">The y position.</param>
|
||||
/// <param name="height">The height.</param>
|
||||
public ContentNavigationButton(ContentTreeNode targetNode, float x, float y, float height)
|
||||
public ContentNavigationButton(ContentFolderTreeNode targetNode, float x, float y, float height)
|
||||
: base(x, y, height)
|
||||
{
|
||||
TargetNode = targetNode;
|
||||
@@ -147,7 +147,7 @@ namespace FlaxEditor.Content.GUI
|
||||
ClearItems();
|
||||
foreach (var child in Target.TargetNode.Children)
|
||||
{
|
||||
if (child is ContentTreeNode node)
|
||||
if (child is ContentFolderTreeNode node)
|
||||
{
|
||||
if (node.Folder.VisibleInHierarchy) // Respect the filter set by ContentFilterConfig.Filter(...)
|
||||
AddItem(node.Folder.ShortName);
|
||||
@@ -180,7 +180,7 @@ namespace FlaxEditor.Content.GUI
|
||||
var item = _items[index];
|
||||
foreach (var child in Target.TargetNode.Children)
|
||||
{
|
||||
if (child is ContentTreeNode node && node.Folder.ShortName == item)
|
||||
if (child is ContentFolderTreeNode node && node.Folder.ShortName == item)
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Navigate(node);
|
||||
return;
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace FlaxEditor.Content
|
||||
/// <summary>
|
||||
/// Gets the content node.
|
||||
/// </summary>
|
||||
public ContentTreeNode Node { get; }
|
||||
public ContentFolderTreeNode Node { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The subitems of this folder.
|
||||
@@ -72,7 +72,7 @@ namespace FlaxEditor.Content
|
||||
/// <param name="type">The folder type.</param>
|
||||
/// <param name="path">The path to the item.</param>
|
||||
/// <param name="node">The folder parent node.</param>
|
||||
internal ContentFolder(ContentFolderType type, string path, ContentTreeNode node)
|
||||
internal ContentFolder(ContentFolderType type, string path, ContentFolderTreeNode node)
|
||||
: base(path)
|
||||
{
|
||||
FolderType = type;
|
||||
@@ -118,7 +118,7 @@ namespace FlaxEditor.Content
|
||||
get
|
||||
{
|
||||
var hasParentFolder = ParentFolder != null;
|
||||
var isContentFolder = Node is MainContentTreeNode;
|
||||
var isContentFolder = Node is MainContentFolderTreeNode;
|
||||
return hasParentFolder && !isContentFolder;
|
||||
}
|
||||
}
|
||||
|
||||
433
Source/Editor/Content/Tree/ContentFolderTreeNode.cs
Normal file
433
Source/Editor/Content/Tree/ContentFolderTreeNode.cs
Normal file
@@ -0,0 +1,433 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Content;
|
||||
|
||||
/// <summary>
|
||||
/// Content folder tree node.
|
||||
/// </summary>
|
||||
/// <seealso cref="TreeNode" />
|
||||
public class ContentFolderTreeNode : TreeNode
|
||||
{
|
||||
private DragItems _dragOverItems;
|
||||
private DragActors _dragActors;
|
||||
private List<Rectangle> _highlights;
|
||||
|
||||
/// <summary>
|
||||
/// The folder.
|
||||
/// </summary>
|
||||
protected ContentFolder _folder;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node can be deleted.
|
||||
/// </summary>
|
||||
public virtual bool CanDelete => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node can be duplicated.
|
||||
/// </summary>
|
||||
public virtual bool CanDuplicate => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content folder item.
|
||||
/// </summary>
|
||||
public ContentFolder Folder => _folder;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the folder.
|
||||
/// </summary>
|
||||
public ContentFolderType FolderType => _folder.FolderType;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if that folder can import/manage scripts.
|
||||
/// </summary>
|
||||
public bool CanHaveScripts => _folder.CanHaveScripts;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if that folder can import/manage assets.
|
||||
/// </summary>
|
||||
/// <returns>True if can contain assets for project, otherwise false</returns>
|
||||
public bool CanHaveAssets => _folder.CanHaveAssets;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent node.
|
||||
/// </summary>
|
||||
public ContentFolderTreeNode ParentNode => Parent as ContentFolderTreeNode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path.
|
||||
/// </summary>
|
||||
public string Path => _folder.Path;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the navigation button label.
|
||||
/// </summary>
|
||||
public virtual string NavButtonLabel => _folder.ShortName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentFolderTreeNode"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent node.</param>
|
||||
/// <param name="path">The folder path.</param>
|
||||
public ContentFolderTreeNode(ContentFolderTreeNode parent, string path)
|
||||
: this(parent, parent?.FolderType ?? ContentFolderType.Other, path)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentFolderTreeNode"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent node.</param>
|
||||
/// <param name="type">The folder type.</param>
|
||||
/// <param name="path">The folder path.</param>
|
||||
protected ContentFolderTreeNode(ContentFolderTreeNode parent, ContentFolderType type, string path)
|
||||
: base(false, Editor.Instance.Icons.FolderClosed32, Editor.Instance.Icons.FolderOpen32)
|
||||
{
|
||||
_folder = new ContentFolder(type, path, this);
|
||||
Text = _folder.ShortName;
|
||||
if (parent != null)
|
||||
{
|
||||
Folder.ParentFolder = parent.Folder;
|
||||
Parent = parent;
|
||||
}
|
||||
IconColor = Color.Transparent; // Hide default icon, we draw scaled icon manually
|
||||
UpdateCustomArrowRect();
|
||||
Editor.Instance?.Windows?.ContentWin?.TryAutoExpandContentNode(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the custom arrow rectangle so it stays aligned with the current layout.
|
||||
/// </summary>
|
||||
private void UpdateCustomArrowRect()
|
||||
{
|
||||
var contentWindow = Editor.Instance?.Windows?.ContentWin;
|
||||
var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
|
||||
var arrowSize = Mathf.Clamp(12.0f * scale, 10.0f, 20.0f);
|
||||
var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
|
||||
|
||||
// Use the current text layout, not just cached values.
|
||||
var textRect = TextRect;
|
||||
var iconLeft = textRect.Left - iconSize - 2.0f;
|
||||
var x = Mathf.Max(iconLeft - arrowSize - 2.0f, 0.0f);
|
||||
var y = Mathf.Max((HeaderHeight - arrowSize) * 0.5f, 0.0f);
|
||||
|
||||
CustomArrowRect = new Rectangle(x, y, arrowSize, arrowSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PerformLayout(bool force = false)
|
||||
{
|
||||
base.PerformLayout(force);
|
||||
UpdateCustomArrowRect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the rename popup for the item.
|
||||
/// </summary>
|
||||
public void StartRenaming()
|
||||
{
|
||||
if (!_folder.CanRename)
|
||||
return;
|
||||
|
||||
// Start renaming the folder
|
||||
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(false);
|
||||
var dialog = RenamePopup.Show(this, TextRect, _folder.ShortName, false);
|
||||
dialog.Tag = _folder;
|
||||
dialog.Renamed += popup =>
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Rename((ContentFolder)popup.Tag, popup.Text);
|
||||
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true);
|
||||
};
|
||||
dialog.Closed += popup => { Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true); };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the query search filter.
|
||||
/// </summary>
|
||||
/// <param name="filterText">The filter text.</param>
|
||||
public void UpdateFilter(string filterText)
|
||||
{
|
||||
bool noFilter = string.IsNullOrWhiteSpace(filterText);
|
||||
|
||||
// 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 style = Style.Current;
|
||||
var font = style.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 ContentFolderTreeNode child)
|
||||
{
|
||||
child.UpdateFilter(filterText);
|
||||
isAnyChildVisible |= child.Visible;
|
||||
}
|
||||
else if (_children[i] is ContentItemTreeNode itemNode)
|
||||
{
|
||||
itemNode.UpdateFilter(filterText);
|
||||
isAnyChildVisible |= itemNode.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
if (!noFilter)
|
||||
{
|
||||
bool isExpanded = isAnyChildVisible;
|
||||
if (isExpanded)
|
||||
Expand(true);
|
||||
else
|
||||
Collapse(true);
|
||||
}
|
||||
|
||||
Visible = isThisVisible | isAnyChildVisible;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Compare(Control other)
|
||||
{
|
||||
if (other is ContentItemTreeNode)
|
||||
return -1;
|
||||
if (other is ContentFolderTreeNode otherNode)
|
||||
return string.Compare(Text, otherNode.Text, StringComparison.Ordinal);
|
||||
return base.Compare(other);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
var contentWindow = Editor.Instance.Windows.ContentWin;
|
||||
var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
|
||||
var icon = IsExpanded ? Editor.Instance.Icons.FolderOpen32 : Editor.Instance.Icons.FolderClosed32;
|
||||
var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
|
||||
var iconRect = new Rectangle(TextRect.Left - iconSize - 2.0f, (HeaderHeight - iconSize) * 0.5f, iconSize, iconSize);
|
||||
Render2D.DrawSprite(icon, iconRect);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Delete folder item
|
||||
_folder.Dispose();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnExpandedChanged()
|
||||
{
|
||||
base.OnExpandedChanged();
|
||||
|
||||
Editor.Instance?.Windows?.ContentWin?.OnContentTreeNodeExpandedChanged(this, IsExpanded);
|
||||
}
|
||||
|
||||
private DragDropEffect GetDragEffect(DragData data)
|
||||
{
|
||||
if (_dragActors != null && _dragActors.HasValidDrag)
|
||||
return DragDropEffect.Move;
|
||||
if (data is DragDataFiles)
|
||||
{
|
||||
if (_folder.CanHaveAssets)
|
||||
return DragDropEffect.Copy;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_dragOverItems != null && _dragOverItems.HasValidDrag)
|
||||
return DragDropEffect.Move;
|
||||
}
|
||||
|
||||
return DragDropEffect.None;
|
||||
}
|
||||
|
||||
private bool ValidateDragItem(ContentItem item)
|
||||
{
|
||||
// Reject itself and any parent
|
||||
return item != _folder && !item.Find(_folder);
|
||||
}
|
||||
|
||||
private bool ValidateDragActors(ActorNode actor)
|
||||
{
|
||||
return actor.CanCreatePrefab && _folder.CanHaveAssets;
|
||||
}
|
||||
|
||||
private void ImportActors(DragActors actors)
|
||||
{
|
||||
Select();
|
||||
foreach (var actorNode in actors.Objects)
|
||||
{
|
||||
var actor = actorNode.Actor;
|
||||
if (actors.Objects.Contains(actorNode.ParentNode as ActorNode))
|
||||
continue;
|
||||
|
||||
Editor.Instance.Prefabs.CreatePrefab(actor, false);
|
||||
}
|
||||
Editor.Instance.Windows.ContentWin.RefreshView();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DragDropEffect OnDragEnterHeader(DragData data)
|
||||
{
|
||||
if (data is DragDataFiles)
|
||||
return _folder.CanHaveAssets ? DragDropEffect.Copy : DragDropEffect.None;
|
||||
|
||||
if (_dragActors == null)
|
||||
_dragActors = new DragActors(ValidateDragActors);
|
||||
if (_dragActors.OnDragEnter(data))
|
||||
return DragDropEffect.Move;
|
||||
|
||||
if (_dragOverItems == null)
|
||||
_dragOverItems = new DragItems(ValidateDragItem);
|
||||
|
||||
_dragOverItems.OnDragEnter(data);
|
||||
return GetDragEffect(data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DragDropEffect OnDragMoveHeader(DragData data)
|
||||
{
|
||||
if (data is DragDataFiles)
|
||||
return _folder.CanHaveAssets ? DragDropEffect.Copy : DragDropEffect.None;
|
||||
if (_dragActors != null && _dragActors.HasValidDrag)
|
||||
return DragDropEffect.Move;
|
||||
return GetDragEffect(data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDragLeaveHeader()
|
||||
{
|
||||
_dragOverItems?.OnDragLeave();
|
||||
_dragActors?.OnDragLeave();
|
||||
base.OnDragLeaveHeader();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DragDropEffect OnDragDropHeader(DragData data)
|
||||
{
|
||||
var result = DragDropEffect.None;
|
||||
|
||||
// Check if drop element or files
|
||||
if (data is DragDataFiles files)
|
||||
{
|
||||
// Import files
|
||||
Editor.Instance.ContentImporting.Import(files.Files, _folder);
|
||||
result = DragDropEffect.Copy;
|
||||
|
||||
Expand();
|
||||
}
|
||||
else if (_dragActors != null && _dragActors.HasValidDrag)
|
||||
{
|
||||
ImportActors(_dragActors);
|
||||
_dragActors.OnDragDrop();
|
||||
result = DragDropEffect.Move;
|
||||
|
||||
Expand();
|
||||
}
|
||||
else if (_dragOverItems != null && _dragOverItems.HasValidDrag)
|
||||
{
|
||||
// Move items
|
||||
Editor.Instance.ContentDatabase.Move(_dragOverItems.Objects, _folder);
|
||||
result = DragDropEffect.Move;
|
||||
|
||||
Expand();
|
||||
}
|
||||
|
||||
_dragOverItems?.OnDragDrop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DoDragDrop()
|
||||
{
|
||||
DoDragDrop(DragItems.GetDragData(_folder));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnLongPress()
|
||||
{
|
||||
Select();
|
||||
|
||||
StartRenaming();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (IsFocused)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.F2:
|
||||
StartRenaming();
|
||||
return true;
|
||||
case KeyboardKeys.Delete:
|
||||
if (Folder.Exists && CanDelete)
|
||||
Editor.Instance.Windows.ContentWin.Delete(Folder);
|
||||
return true;
|
||||
}
|
||||
if (RootWindow.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.D:
|
||||
if (Folder.Exists && CanDuplicate)
|
||||
Editor.Instance.Windows.ContentWin.Duplicate(Folder);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
}
|
||||
221
Source/Editor/Content/Tree/ContentItemTreeNode.cs
Normal file
221
Source/Editor/Content/Tree/ContentItemTreeNode.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Content.GUI;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Content;
|
||||
|
||||
/// <summary>
|
||||
/// Tree node for non-folder content items.
|
||||
/// </summary>
|
||||
public sealed class ContentItemTreeNode : TreeNode, IContentItemOwner
|
||||
{
|
||||
private List<Rectangle> _highlights;
|
||||
|
||||
/// <summary>
|
||||
/// The content item.
|
||||
/// </summary>
|
||||
public ContentItem Item { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentItemTreeNode"/> class.
|
||||
/// </summary>
|
||||
/// <param name="item">The content item.</param>
|
||||
public ContentItemTreeNode(ContentItem item)
|
||||
: base(false, Editor.Instance.Icons.Document128, Editor.Instance.Icons.Document128)
|
||||
{
|
||||
Item = item ?? throw new ArgumentNullException(nameof(item));
|
||||
UpdateDisplayedName();
|
||||
IconColor = Color.Transparent; // Reserve icon space but draw custom thumbnail.
|
||||
Item.AddReference(this);
|
||||
}
|
||||
|
||||
private static SpriteHandle GetIcon(ContentItem item)
|
||||
{
|
||||
if (item == null)
|
||||
return SpriteHandle.Invalid;
|
||||
var icon = item.Thumbnail;
|
||||
if (!icon.IsValid)
|
||||
icon = item.DefaultThumbnail;
|
||||
if (!icon.IsValid)
|
||||
icon = Editor.Instance.Icons.Document128;
|
||||
return icon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the query search filter.
|
||||
/// </summary>
|
||||
/// <param name="filterText">The filter text.</param>
|
||||
public void UpdateFilter(string filterText)
|
||||
{
|
||||
bool noFilter = string.IsNullOrWhiteSpace(filterText);
|
||||
bool isVisible;
|
||||
if (noFilter)
|
||||
{
|
||||
_highlights?.Clear();
|
||||
isVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var text = Text;
|
||||
if (QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
|
||||
{
|
||||
if (_highlights == null)
|
||||
_highlights = new List<Rectangle>(ranges.Length);
|
||||
else
|
||||
_highlights.Clear();
|
||||
var style = Style.Current;
|
||||
var font = style.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));
|
||||
}
|
||||
isVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_highlights?.Clear();
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
Visible = isVisible;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var icon = GetIcon(Item);
|
||||
if (icon.IsValid)
|
||||
{
|
||||
var contentWindow = Editor.Instance.Windows.ContentWin;
|
||||
var scale = contentWindow != null && contentWindow.IsTreeOnlyMode ? contentWindow.View.ViewScale : 1.0f;
|
||||
var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
|
||||
var textRect = TextRect;
|
||||
var iconRect = new Rectangle(textRect.Left - iconSize - 2.0f, (HeaderHeight - iconSize) * 0.5f, iconSize, iconSize);
|
||||
Render2D.DrawSprite(icon, iconRect);
|
||||
}
|
||||
|
||||
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 bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Open(Item);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDoubleClickHeader(ref location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (IsFocused)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.Return:
|
||||
Editor.Instance.Windows.ContentWin.Open(Item);
|
||||
return true;
|
||||
case KeyboardKeys.F2:
|
||||
Editor.Instance.Windows.ContentWin.Rename(Item);
|
||||
return true;
|
||||
case KeyboardKeys.Delete:
|
||||
Editor.Instance.Windows.ContentWin.Delete(Item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DoDragDrop()
|
||||
{
|
||||
DoDragDrop(DragItems.GetDragData(Item));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnShowTooltip(out string text, out Float2 location, out Rectangle area)
|
||||
{
|
||||
Item.UpdateTooltipText();
|
||||
TooltipText = Item.TooltipText;
|
||||
return base.OnShowTooltip(out text, out location, out area);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IContentItemOwner.OnItemDeleted(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IContentItemOwner.OnItemRenamed(ContentItem item)
|
||||
{
|
||||
UpdateDisplayedName();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IContentItemOwner.OnItemReimported(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IContentItemOwner.OnItemDispose(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Compare(Control other)
|
||||
{
|
||||
if (other is ContentFolderTreeNode)
|
||||
return 1;
|
||||
if (other is ContentItemTreeNode otherItem)
|
||||
return ApplySortOrder(string.Compare(Text, otherItem.Text, StringComparison.InvariantCulture));
|
||||
return base.Compare(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Item.RemoveReference(this);
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
public void UpdateDisplayedName()
|
||||
{
|
||||
var contentWindow = Editor.Instance?.Windows?.ContentWin;
|
||||
var showExtensions = contentWindow?.View?.ShowFileExtensions ?? true;
|
||||
Text = Item.ShowFileExtension || showExtensions ? Item.FileName : Item.ShortName;
|
||||
}
|
||||
|
||||
private static SortType GetSortType()
|
||||
{
|
||||
return Editor.Instance?.Windows?.ContentWin?.CurrentSortType ?? SortType.AlphabeticOrder;
|
||||
}
|
||||
|
||||
private static int ApplySortOrder(int result)
|
||||
{
|
||||
return GetSortType() == SortType.AlphabeticReverse ? -result : result;
|
||||
}
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Content
|
||||
{
|
||||
/// <summary>
|
||||
/// Content folder tree node.
|
||||
/// </summary>
|
||||
/// <seealso cref="TreeNode" />
|
||||
public class ContentTreeNode : TreeNode
|
||||
{
|
||||
private DragItems _dragOverItems;
|
||||
private List<Rectangle> _highlights;
|
||||
|
||||
/// <summary>
|
||||
/// The folder.
|
||||
/// </summary>
|
||||
protected ContentFolder _folder;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node can be deleted.
|
||||
/// </summary>
|
||||
public virtual bool CanDelete => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node can be duplicated.
|
||||
/// </summary>
|
||||
public virtual bool CanDuplicate => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content folder item.
|
||||
/// </summary>
|
||||
public ContentFolder Folder => _folder;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the folder.
|
||||
/// </summary>
|
||||
public ContentFolderType FolderType => _folder.FolderType;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if that folder can import/manage scripts.
|
||||
/// </summary>
|
||||
public bool CanHaveScripts => _folder.CanHaveScripts;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if that folder can import/manage assets.
|
||||
/// </summary>
|
||||
/// <returns>True if can contain assets for project, otherwise false</returns>
|
||||
public bool CanHaveAssets => _folder.CanHaveAssets;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent node.
|
||||
/// </summary>
|
||||
public ContentTreeNode ParentNode => Parent as ContentTreeNode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path.
|
||||
/// </summary>
|
||||
public string Path => _folder.Path;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the navigation button label.
|
||||
/// </summary>
|
||||
public virtual string NavButtonLabel => _folder.ShortName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentTreeNode"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent node.</param>
|
||||
/// <param name="path">The folder path.</param>
|
||||
public ContentTreeNode(ContentTreeNode parent, string path)
|
||||
: this(parent, parent?.FolderType ?? ContentFolderType.Other, path)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentTreeNode"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent node.</param>
|
||||
/// <param name="type">The folder type.</param>
|
||||
/// <param name="path">The folder path.</param>
|
||||
protected ContentTreeNode(ContentTreeNode parent, ContentFolderType type, string path)
|
||||
: base(false, Editor.Instance.Icons.FolderClosed32, Editor.Instance.Icons.FolderOpen32)
|
||||
{
|
||||
_folder = new ContentFolder(type, path, this);
|
||||
Text = _folder.ShortName;
|
||||
if (parent != null)
|
||||
{
|
||||
Folder.ParentFolder = parent.Folder;
|
||||
Parent = parent;
|
||||
}
|
||||
IconColor = Style.Current.Foreground;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the rename popup for the item.
|
||||
/// </summary>
|
||||
public void StartRenaming()
|
||||
{
|
||||
if (!_folder.CanRename)
|
||||
return;
|
||||
|
||||
// Start renaming the folder
|
||||
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(false);
|
||||
var dialog = RenamePopup.Show(this, TextRect, _folder.ShortName, false);
|
||||
dialog.Tag = _folder;
|
||||
dialog.Renamed += popup =>
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Rename((ContentFolder)popup.Tag, popup.Text);
|
||||
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true);
|
||||
};
|
||||
dialog.Closed += popup => { Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true); };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the query search filter.
|
||||
/// </summary>
|
||||
/// <param name="filterText">The filter text.</param>
|
||||
public void UpdateFilter(string filterText)
|
||||
{
|
||||
bool noFilter = string.IsNullOrWhiteSpace(filterText);
|
||||
|
||||
// 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 style = Style.Current;
|
||||
var font = style.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 ContentTreeNode child)
|
||||
{
|
||||
child.UpdateFilter(filterText);
|
||||
isAnyChildVisible |= child.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
if (!noFilter)
|
||||
{
|
||||
bool isExpanded = isAnyChildVisible;
|
||||
if (isExpanded)
|
||||
Expand(true);
|
||||
else
|
||||
Collapse(true);
|
||||
}
|
||||
|
||||
Visible = isThisVisible | isAnyChildVisible;
|
||||
}
|
||||
|
||||
/// <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 />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Delete folder item
|
||||
_folder.Dispose();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
private DragDropEffect GetDragEffect(DragData data)
|
||||
{
|
||||
if (data is DragDataFiles)
|
||||
{
|
||||
if (_folder.CanHaveAssets)
|
||||
return DragDropEffect.Copy;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_dragOverItems.HasValidDrag)
|
||||
return DragDropEffect.Move;
|
||||
}
|
||||
|
||||
return DragDropEffect.None;
|
||||
}
|
||||
|
||||
private bool ValidateDragItem(ContentItem item)
|
||||
{
|
||||
// Reject itself and any parent
|
||||
return item != _folder && !item.Find(_folder);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DragDropEffect OnDragEnterHeader(DragData data)
|
||||
{
|
||||
if (_dragOverItems == null)
|
||||
_dragOverItems = new DragItems(ValidateDragItem);
|
||||
|
||||
_dragOverItems.OnDragEnter(data);
|
||||
return GetDragEffect(data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DragDropEffect OnDragMoveHeader(DragData data)
|
||||
{
|
||||
return GetDragEffect(data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDragLeaveHeader()
|
||||
{
|
||||
_dragOverItems.OnDragLeave();
|
||||
base.OnDragLeaveHeader();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DragDropEffect OnDragDropHeader(DragData data)
|
||||
{
|
||||
var result = DragDropEffect.None;
|
||||
|
||||
// Check if drop element or files
|
||||
if (data is DragDataFiles files)
|
||||
{
|
||||
// Import files
|
||||
Editor.Instance.ContentImporting.Import(files.Files, _folder);
|
||||
result = DragDropEffect.Copy;
|
||||
|
||||
Expand();
|
||||
}
|
||||
else if (_dragOverItems.HasValidDrag)
|
||||
{
|
||||
// Move items
|
||||
Editor.Instance.ContentDatabase.Move(_dragOverItems.Objects, _folder);
|
||||
result = DragDropEffect.Move;
|
||||
|
||||
Expand();
|
||||
}
|
||||
|
||||
_dragOverItems.OnDragDrop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DoDragDrop()
|
||||
{
|
||||
DoDragDrop(DragItems.GetDragData(_folder));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnLongPress()
|
||||
{
|
||||
Select();
|
||||
|
||||
StartRenaming();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (IsFocused)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.F2:
|
||||
StartRenaming();
|
||||
return true;
|
||||
case KeyboardKeys.Delete:
|
||||
if (Folder.Exists && CanDelete)
|
||||
Editor.Instance.Windows.ContentWin.Delete(Folder);
|
||||
return true;
|
||||
}
|
||||
if (RootWindow.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.D:
|
||||
if (Folder.Exists && CanDuplicate)
|
||||
Editor.Instance.Windows.ContentWin.Duplicate(Folder);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ namespace FlaxEditor.Content
|
||||
/// <summary>
|
||||
/// Content tree node used for main directories.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Content.ContentTreeNode" />
|
||||
public class MainContentTreeNode : ContentTreeNode
|
||||
/// <seealso cref="ContentFolderTreeNode" />
|
||||
public class MainContentFolderTreeNode : ContentFolderTreeNode
|
||||
{
|
||||
private FileSystemWatcher _watcher;
|
||||
|
||||
@@ -19,12 +19,12 @@ namespace FlaxEditor.Content
|
||||
public override bool CanDuplicate => false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MainContentTreeNode"/> class.
|
||||
/// Initializes a new instance of the <see cref="MainContentFolderTreeNode"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent project.</param>
|
||||
/// <param name="type">The folder type.</param>
|
||||
/// <param name="path">The folder path.</param>
|
||||
public MainContentTreeNode(ProjectTreeNode parent, ContentFolderType type, string path)
|
||||
public MainContentFolderTreeNode(ProjectFolderTreeNode parent, ContentFolderType type, string path)
|
||||
: base(parent, type, path)
|
||||
{
|
||||
_watcher = new FileSystemWatcher(path)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Content
|
||||
@@ -7,8 +8,8 @@ namespace FlaxEditor.Content
|
||||
/// <summary>
|
||||
/// Root tree node for the project workspace.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Content.ContentTreeNode" />
|
||||
public sealed class ProjectTreeNode : ContentTreeNode
|
||||
/// <seealso cref="ContentFolderTreeNode" />
|
||||
public sealed class ProjectFolderTreeNode : ContentFolderTreeNode
|
||||
{
|
||||
/// <summary>
|
||||
/// The project/
|
||||
@@ -18,18 +19,18 @@ namespace FlaxEditor.Content
|
||||
/// <summary>
|
||||
/// The project content directory.
|
||||
/// </summary>
|
||||
public MainContentTreeNode Content;
|
||||
public MainContentFolderTreeNode Content;
|
||||
|
||||
/// <summary>
|
||||
/// The project source code directory.
|
||||
/// </summary>
|
||||
public MainContentTreeNode Source;
|
||||
public MainContentFolderTreeNode Source;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProjectTreeNode"/> class.
|
||||
/// Initializes a new instance of the <see cref="ProjectFolderTreeNode"/> class.
|
||||
/// </summary>
|
||||
/// <param name="project">The project.</param>
|
||||
public ProjectTreeNode(ProjectInfo project)
|
||||
public ProjectFolderTreeNode(ProjectInfo project)
|
||||
: base(null, project.ProjectFolderPath)
|
||||
{
|
||||
Project = project;
|
||||
@@ -48,9 +49,29 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override int Compare(Control other)
|
||||
{
|
||||
// Move the main game project to the top
|
||||
if (Project.Name == Editor.Instance.GameProject.Name)
|
||||
return -1;
|
||||
if (other is ProjectFolderTreeNode otherProject)
|
||||
{
|
||||
var gameProject = Editor.Instance.GameProject;
|
||||
var engineProject = Editor.Instance.EngineProject;
|
||||
bool isGame = Project == gameProject;
|
||||
bool isEngine = Project == engineProject;
|
||||
bool otherIsGame = otherProject.Project == gameProject;
|
||||
bool otherIsEngine = otherProject.Project == engineProject;
|
||||
|
||||
// Main game project at the top
|
||||
if (isGame && !otherIsGame)
|
||||
return -1;
|
||||
if (!isGame && otherIsGame)
|
||||
return 1;
|
||||
|
||||
// Engine project at the bottom (when distinct)
|
||||
if (isEngine && !otherIsEngine)
|
||||
return 1;
|
||||
if (!isEngine && otherIsEngine)
|
||||
return -1;
|
||||
|
||||
return string.CompareOrdinal(Project.Name, otherProject.Project.Name);
|
||||
}
|
||||
return base.Compare(other);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ namespace FlaxEditor.Content
|
||||
/// <summary>
|
||||
/// Root tree node for the content workspace.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Content.ContentTreeNode" />
|
||||
public sealed class RootContentTreeNode : ContentTreeNode
|
||||
/// <seealso cref="ContentFolderTreeNode" />
|
||||
public sealed class RootContentFolderTreeNode : ContentFolderTreeNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RootContentTreeNode"/> class.
|
||||
/// Initializes a new instance of the <see cref="RootContentFolderTreeNode"/> class.
|
||||
/// </summary>
|
||||
public RootContentTreeNode()
|
||||
public RootContentFolderTreeNode()
|
||||
: base(null, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -76,9 +76,23 @@ namespace FlaxEditor.GUI
|
||||
toolstrip.IsLayoutLocked = toolstripLocked;
|
||||
toolstrip.ItemsMargin = toolstripMargin;
|
||||
|
||||
var lastToolstripButton = toolstrip.LastButton;
|
||||
var margin = toolstrip.ItemsMargin;
|
||||
float xOffset = margin.Left;
|
||||
bool hadChild = false;
|
||||
for (int i = 0; i < toolstrip.ChildrenCount; i++)
|
||||
{
|
||||
var child = toolstrip.GetChild(i);
|
||||
if (child == this || !child.Visible)
|
||||
continue;
|
||||
hadChild = true;
|
||||
xOffset += child.Width + margin.Width;
|
||||
}
|
||||
|
||||
var right = hadChild ? xOffset - margin.Width : margin.Left;
|
||||
var parentSize = Parent.Size;
|
||||
Bounds = new Rectangle(lastToolstripButton.Right + 8.0f, 0, parentSize.X - X - 8.0f, toolstrip.Height);
|
||||
var x = right + 8.0f;
|
||||
var width = Mathf.Max(parentSize.X - x - 8.0f, 0.0f);
|
||||
Bounds = new Rectangle(x, 0, width, toolstrip.Height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -24,22 +24,22 @@ namespace FlaxEditor.Modules
|
||||
private bool _rebuildInitFlag;
|
||||
private int _itemsCreated;
|
||||
private int _itemsDeleted;
|
||||
private readonly HashSet<MainContentTreeNode> _dirtyNodes = new HashSet<MainContentTreeNode>();
|
||||
private readonly HashSet<MainContentFolderTreeNode> _dirtyNodes = new HashSet<MainContentFolderTreeNode>();
|
||||
|
||||
/// <summary>
|
||||
/// The project directory.
|
||||
/// </summary>
|
||||
public ProjectTreeNode Game { get; private set; }
|
||||
public ProjectFolderTreeNode Game { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The engine directory.
|
||||
/// </summary>
|
||||
public ProjectTreeNode Engine { get; private set; }
|
||||
public ProjectFolderTreeNode Engine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of all projects workspace directories (including game, engine and plugins projects).
|
||||
/// </summary>
|
||||
public readonly List<ProjectTreeNode> Projects = new List<ProjectTreeNode>();
|
||||
public readonly List<ProjectFolderTreeNode> Projects = new List<ProjectFolderTreeNode>();
|
||||
|
||||
/// <summary>
|
||||
/// The list with all content items proxy objects. Use <see cref="AddProxy"/> and <see cref="RemoveProxy"/> to modify this or <see cref="Rebuild"/> to refresh database when adding new item proxy types.
|
||||
@@ -116,7 +116,7 @@ namespace FlaxEditor.Modules
|
||||
/// </summary>
|
||||
/// <param name="project">The project.</param>
|
||||
/// <returns>The project workspace or null if not loaded into database.</returns>
|
||||
public ProjectTreeNode GetProjectWorkspace(ProjectInfo project)
|
||||
public ProjectFolderTreeNode GetProjectWorkspace(ProjectInfo project)
|
||||
{
|
||||
return Projects.FirstOrDefault(x => x.Project == project);
|
||||
}
|
||||
@@ -874,7 +874,7 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadFolder(ContentTreeNode node, bool checkSubDirs)
|
||||
private void LoadFolder(ContentFolderTreeNode node, bool checkSubDirs)
|
||||
{
|
||||
if (node == null)
|
||||
return;
|
||||
@@ -953,7 +953,7 @@ namespace FlaxEditor.Modules
|
||||
if (childFolderNode == null)
|
||||
{
|
||||
// Create node
|
||||
ContentTreeNode n = new ContentTreeNode(node, childPath);
|
||||
ContentFolderTreeNode n = new ContentFolderTreeNode(node, childPath);
|
||||
if (!_isDuringFastSetup)
|
||||
sortChildren = true;
|
||||
|
||||
@@ -978,7 +978,7 @@ namespace FlaxEditor.Modules
|
||||
node.SortChildren();
|
||||
|
||||
// Ignore some special folders
|
||||
if (node is MainContentTreeNode mainNode && mainNode.Folder.ShortName == "Source")
|
||||
if (node is MainContentFolderTreeNode mainNode && mainNode.Folder.ShortName == "Source")
|
||||
{
|
||||
var mainNodeChild = mainNode.Folder.Find(StringUtils.CombinePaths(mainNode.Path, "obj")) as ContentFolder;
|
||||
if (mainNodeChild != null)
|
||||
@@ -995,7 +995,7 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadScripts(ContentTreeNode parent, string[] files)
|
||||
private void LoadScripts(ContentFolderTreeNode parent, string[] files)
|
||||
{
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
@@ -1041,7 +1041,7 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadAssets(ContentTreeNode parent, string[] files)
|
||||
private void LoadAssets(ContentFolderTreeNode parent, string[] files)
|
||||
{
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
@@ -1093,20 +1093,20 @@ namespace FlaxEditor.Modules
|
||||
var workspace = GetProjectWorkspace(project);
|
||||
if (workspace == null)
|
||||
{
|
||||
workspace = new ProjectTreeNode(project);
|
||||
workspace = new ProjectFolderTreeNode(project);
|
||||
Projects.Add(workspace);
|
||||
|
||||
var contentFolder = StringUtils.CombinePaths(project.ProjectFolderPath, "Content");
|
||||
if (Directory.Exists(contentFolder))
|
||||
{
|
||||
workspace.Content = new MainContentTreeNode(workspace, ContentFolderType.Content, contentFolder);
|
||||
workspace.Content = new MainContentFolderTreeNode(workspace, ContentFolderType.Content, contentFolder);
|
||||
workspace.Content.Folder.ParentFolder = workspace.Folder;
|
||||
}
|
||||
|
||||
var sourceFolder = StringUtils.CombinePaths(project.ProjectFolderPath, "Source");
|
||||
if (Directory.Exists(sourceFolder))
|
||||
{
|
||||
workspace.Source = new MainContentTreeNode(workspace, ContentFolderType.Source, sourceFolder);
|
||||
workspace.Source = new MainContentFolderTreeNode(workspace, ContentFolderType.Source, sourceFolder);
|
||||
workspace.Source.Folder.ParentFolder = workspace.Folder;
|
||||
}
|
||||
}
|
||||
@@ -1213,16 +1213,16 @@ namespace FlaxEditor.Modules
|
||||
Proxy.Add(new GenericJsonAssetProxy());
|
||||
|
||||
// Create content folders nodes
|
||||
Engine = new ProjectTreeNode(Editor.EngineProject)
|
||||
Engine = new ProjectFolderTreeNode(Editor.EngineProject)
|
||||
{
|
||||
Content = new MainContentTreeNode(Engine, ContentFolderType.Content, Globals.EngineContentFolder),
|
||||
Content = new MainContentFolderTreeNode(Engine, ContentFolderType.Content, Globals.EngineContentFolder),
|
||||
};
|
||||
if (Editor.GameProject != Editor.EngineProject)
|
||||
{
|
||||
Game = new ProjectTreeNode(Editor.GameProject)
|
||||
Game = new ProjectFolderTreeNode(Editor.GameProject)
|
||||
{
|
||||
Content = new MainContentTreeNode(Game, ContentFolderType.Content, Globals.ProjectContentFolder),
|
||||
Source = new MainContentTreeNode(Game, ContentFolderType.Source, Globals.ProjectSourceFolder),
|
||||
Content = new MainContentFolderTreeNode(Game, ContentFolderType.Content, Globals.ProjectContentFolder),
|
||||
Source = new MainContentFolderTreeNode(Game, ContentFolderType.Source, Globals.ProjectSourceFolder),
|
||||
};
|
||||
// TODO: why it's required? the code above should work for linking the nodes hierarchy
|
||||
Game.Content.Folder.ParentFolder = Game.Folder;
|
||||
@@ -1302,7 +1302,7 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e)
|
||||
internal void OnDirectoryEvent(MainContentFolderTreeNode node, FileSystemEventArgs e)
|
||||
{
|
||||
// Ensure to be ready for external events
|
||||
if (_isDuringFastSetup)
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace FlaxEditor.Windows
|
||||
cm.AddSeparator();
|
||||
}
|
||||
|
||||
if (item is ContentFolder contentFolder && contentFolder.Node is ProjectTreeNode)
|
||||
if (item is ContentFolder contentFolder && contentFolder.Node is ProjectFolderTreeNode)
|
||||
{
|
||||
cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(CurrentViewFolder.Path));
|
||||
}
|
||||
@@ -76,8 +76,8 @@ namespace FlaxEditor.Windows
|
||||
|
||||
cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path)));
|
||||
|
||||
if (!String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text))
|
||||
{
|
||||
if (!_showAllContentInTree && !String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text))
|
||||
{
|
||||
cm.AddButton("Show in Content Panel", () =>
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
|
||||
@@ -129,7 +129,7 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
}
|
||||
|
||||
if (isFolder && folder.Node is MainContentTreeNode)
|
||||
if (isFolder && folder.Node is MainContentFolderTreeNode)
|
||||
{
|
||||
cm.AddSeparator();
|
||||
}
|
||||
@@ -145,7 +145,7 @@ namespace FlaxEditor.Windows
|
||||
b = cm.AddButton("Paste", _view.Paste);
|
||||
b.Enabled = _view.CanPaste();
|
||||
|
||||
if (isFolder && folder.Node is MainContentTreeNode)
|
||||
if (isFolder && folder.Node is MainContentFolderTreeNode)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
@@ -201,7 +201,7 @@ namespace FlaxEditor.Windows
|
||||
private void CreateNewModuleMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false)
|
||||
{
|
||||
// Check if is source folder to add new module
|
||||
if (folder?.ParentFolder?.Node is ProjectTreeNode parentFolderNode && folder.Node == parentFolderNode.Source)
|
||||
if (folder?.ParentFolder?.Node is ProjectFolderTreeNode parentFolderNode && folder.Node == parentFolderNode.Source)
|
||||
{
|
||||
var button = menu.AddButton("New module");
|
||||
button.CloseMenuOnClick = false;
|
||||
@@ -216,7 +216,7 @@ namespace FlaxEditor.Windows
|
||||
|
||||
private bool CanCreateFolder(ContentItem item = null)
|
||||
{
|
||||
bool canCreateFolder = CurrentViewFolder != _root.Folder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode);
|
||||
bool canCreateFolder = CurrentViewFolder != _root.Folder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectFolderTreeNode);
|
||||
return canCreateFolder;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,41 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
public partial class ContentWindow
|
||||
{
|
||||
private static readonly List<ContentTreeNode> NavUpdateCache = new List<ContentTreeNode>(8);
|
||||
private static readonly List<ContentFolderTreeNode> NavUpdateCache = new List<ContentFolderTreeNode>(8);
|
||||
|
||||
private void OnTreeSelectionChanged(List<TreeNode> from, List<TreeNode> to)
|
||||
{
|
||||
if (!_showAllContentInTree && to.Count > 1)
|
||||
{
|
||||
_tree.Select(to[^1]);
|
||||
return;
|
||||
}
|
||||
if (_showAllContentInTree && to.Count > 1)
|
||||
{
|
||||
var activeNode = GetActiveTreeSelection(to);
|
||||
if (activeNode is ContentItemTreeNode itemNode)
|
||||
SaveLastViewedFolder(itemNode.Item?.ParentFolder?.Node);
|
||||
else
|
||||
SaveLastViewedFolder(activeNode as ContentFolderTreeNode);
|
||||
UpdateUI();
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate
|
||||
var source = from.Count > 0 ? from[0] as ContentTreeNode : null;
|
||||
var target = to.Count > 0 ? to[0] as ContentTreeNode : null;
|
||||
var source = from.Count > 0 ? from[0] as ContentFolderTreeNode : null;
|
||||
var targetNode = GetActiveTreeSelection(to);
|
||||
if (targetNode is ContentItemTreeNode itemNode2)
|
||||
{
|
||||
SaveLastViewedFolder(itemNode2.Item?.ParentFolder?.Node);
|
||||
UpdateUI();
|
||||
itemNode2.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
var target = targetNode as ContentFolderTreeNode;
|
||||
Navigate(source, target);
|
||||
|
||||
SaveLastViewedFolder(target);
|
||||
target?.Focus();
|
||||
}
|
||||
|
||||
@@ -26,12 +52,12 @@ namespace FlaxEditor.Windows
|
||||
/// Navigates to the specified target content location.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
public void Navigate(ContentTreeNode target)
|
||||
public void Navigate(ContentFolderTreeNode target)
|
||||
{
|
||||
Navigate(SelectedNode, target);
|
||||
}
|
||||
|
||||
private void Navigate(ContentTreeNode source, ContentTreeNode target)
|
||||
private void Navigate(ContentFolderTreeNode source, ContentFolderTreeNode target)
|
||||
{
|
||||
if (target == null)
|
||||
target = _root;
|
||||
@@ -50,7 +76,8 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
|
||||
// Show folder contents and select tree node
|
||||
RefreshView(target);
|
||||
if (!_showAllContentInTree)
|
||||
RefreshView(target);
|
||||
_tree.Select(target);
|
||||
target.ExpandAllParents();
|
||||
|
||||
@@ -62,7 +89,8 @@ namespace FlaxEditor.Windows
|
||||
//UndoList.SetSize(32);
|
||||
|
||||
// Update search
|
||||
UpdateItemsSearch();
|
||||
if (!_showAllContentInTree)
|
||||
UpdateItemsSearch();
|
||||
|
||||
// Unlock navigation
|
||||
_navigationUnlocked = true;
|
||||
@@ -81,7 +109,7 @@ namespace FlaxEditor.Windows
|
||||
if (_navigationUnlocked && _navigationUndo.Count > 0)
|
||||
{
|
||||
// Pop node
|
||||
ContentTreeNode node = _navigationUndo.Pop();
|
||||
ContentFolderTreeNode node = _navigationUndo.Pop();
|
||||
|
||||
// Lock navigation
|
||||
_navigationUnlocked = false;
|
||||
@@ -90,7 +118,8 @@ namespace FlaxEditor.Windows
|
||||
_navigationRedo.Push(SelectedNode);
|
||||
|
||||
// Select node
|
||||
RefreshView(node);
|
||||
if (!_showAllContentInTree)
|
||||
RefreshView(node);
|
||||
_tree.Select(node);
|
||||
node.ExpandAllParents();
|
||||
|
||||
@@ -99,14 +128,16 @@ namespace FlaxEditor.Windows
|
||||
//UndoList.SetSize(32);
|
||||
|
||||
// Update search
|
||||
UpdateItemsSearch();
|
||||
if (!_showAllContentInTree)
|
||||
UpdateItemsSearch();
|
||||
|
||||
// Unlock navigation
|
||||
_navigationUnlocked = true;
|
||||
|
||||
// Update UI
|
||||
UpdateUI();
|
||||
_view.SelectFirstItem();
|
||||
if (!_showAllContentInTree)
|
||||
_view.SelectFirstItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +150,7 @@ namespace FlaxEditor.Windows
|
||||
if (_navigationUnlocked && _navigationRedo.Count > 0)
|
||||
{
|
||||
// Pop node
|
||||
ContentTreeNode node = _navigationRedo.Pop();
|
||||
ContentFolderTreeNode node = _navigationRedo.Pop();
|
||||
|
||||
// Lock navigation
|
||||
_navigationUnlocked = false;
|
||||
@@ -128,7 +159,8 @@ namespace FlaxEditor.Windows
|
||||
_navigationUndo.Push(SelectedNode);
|
||||
|
||||
// Select node
|
||||
RefreshView(node);
|
||||
if (!_showAllContentInTree)
|
||||
RefreshView(node);
|
||||
_tree.Select(node);
|
||||
node.ExpandAllParents();
|
||||
|
||||
@@ -137,14 +169,16 @@ namespace FlaxEditor.Windows
|
||||
//UndoList.SetSize(32);
|
||||
|
||||
// Update search
|
||||
UpdateItemsSearch();
|
||||
if (!_showAllContentInTree)
|
||||
UpdateItemsSearch();
|
||||
|
||||
// Unlock navigation
|
||||
_navigationUnlocked = true;
|
||||
|
||||
// Update UI
|
||||
UpdateUI();
|
||||
_view.SelectFirstItem();
|
||||
if (!_showAllContentInTree)
|
||||
_view.SelectFirstItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +187,8 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
public void NavigateUp()
|
||||
{
|
||||
ContentTreeNode target = _root;
|
||||
ContentTreeNode current = SelectedNode;
|
||||
ContentFolderTreeNode target = _root;
|
||||
ContentFolderTreeNode current = SelectedNode;
|
||||
|
||||
if (current?.Folder.ParentFolder != null)
|
||||
{
|
||||
@@ -188,7 +222,7 @@ namespace FlaxEditor.Windows
|
||||
// Spawn buttons
|
||||
var nodes = NavUpdateCache;
|
||||
nodes.Clear();
|
||||
ContentTreeNode node = SelectedNode;
|
||||
ContentFolderTreeNode node = SelectedNode;
|
||||
while (node != null)
|
||||
{
|
||||
nodes.Add(node);
|
||||
@@ -222,13 +256,31 @@ namespace FlaxEditor.Windows
|
||||
/// <summary>
|
||||
/// Gets the selected tree node.
|
||||
/// </summary>
|
||||
public ContentTreeNode SelectedNode => _tree.SelectedNode as ContentTreeNode;
|
||||
public ContentFolderTreeNode SelectedNode
|
||||
{
|
||||
get
|
||||
{
|
||||
var selected = GetActiveTreeSelection(_tree.Selection);
|
||||
if (selected is ContentItemTreeNode itemNode)
|
||||
return itemNode.Parent as ContentFolderTreeNode;
|
||||
return selected as ContentFolderTreeNode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current view folder.
|
||||
/// </summary>
|
||||
public ContentFolder CurrentViewFolder => SelectedNode?.Folder;
|
||||
|
||||
private TreeNode GetActiveTreeSelection(List<TreeNode> selection)
|
||||
{
|
||||
if (selection == null || selection.Count == 0)
|
||||
return null;
|
||||
return _showAllContentInTree && selection.Count > 1
|
||||
? selection[^1]
|
||||
: selection[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the root folder.
|
||||
/// </summary>
|
||||
@@ -236,5 +288,10 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
_tree.Select(_root);
|
||||
}
|
||||
|
||||
private void SaveLastViewedFolder(ContentFolderTreeNode node)
|
||||
{
|
||||
Editor.ProjectCache.SetCustomData(ProjectDataLastViewedFolder, node?.Path ?? string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,12 +115,14 @@ namespace FlaxEditor.Windows
|
||||
|
||||
var root = _root;
|
||||
root.LockChildrenRecursive();
|
||||
_suppressExpandedStateSave = true;
|
||||
PerformLayout();
|
||||
|
||||
// Update tree
|
||||
var query = _foldersSearchBox.Text;
|
||||
root.UpdateFilter(query);
|
||||
|
||||
_suppressExpandedStateSave = false;
|
||||
root.UnlockChildrenRecursive();
|
||||
PerformLayout();
|
||||
PerformLayout();
|
||||
@@ -161,6 +163,11 @@ namespace FlaxEditor.Windows
|
||||
// Skip events during setup or init stuff
|
||||
if (IsLayoutLocked)
|
||||
return;
|
||||
if (_showAllContentInTree)
|
||||
{
|
||||
RefreshTreeItems();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if clear filters
|
||||
if (_itemsSearchBox.TextLength == 0 && !_viewDropdown.HasSelection)
|
||||
@@ -200,7 +207,7 @@ namespace FlaxEditor.Windows
|
||||
// Special case for root folder
|
||||
for (int i = 0; i < _root.ChildrenCount; i++)
|
||||
{
|
||||
if (_root.GetChild(i) is ContentTreeNode node)
|
||||
if (_root.GetChild(i) is ContentFolderTreeNode node)
|
||||
UpdateItemsSearchFilter(node.Folder, items, filters, showAllFiles);
|
||||
}
|
||||
}
|
||||
@@ -222,7 +229,7 @@ namespace FlaxEditor.Windows
|
||||
// Special case for root folder
|
||||
for (int i = 0; i < _root.ChildrenCount; i++)
|
||||
{
|
||||
if (_root.GetChild(i) is ContentTreeNode node)
|
||||
if (_root.GetChild(i) is ContentFolderTreeNode node)
|
||||
UpdateItemsSearchFilter(node.Folder, items, filters, showAllFiles, query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,15 @@ namespace FlaxEditor.Windows
|
||||
public sealed partial class ContentWindow : EditorWindow
|
||||
{
|
||||
private const string ProjectDataLastViewedFolder = "LastViewedFolder";
|
||||
private const string ProjectDataExpandedFolders = "ExpandedFolders";
|
||||
private bool _isWorkspaceDirty;
|
||||
private string _workspaceRebuildLocation;
|
||||
private string _lastViewedFolderBeforeReload;
|
||||
private SplitPanel _split;
|
||||
private Panel _treeOnlyPanel;
|
||||
private ContainerControl _treePanelRoot;
|
||||
private ContainerControl _treeHeaderPanel;
|
||||
private Panel _contentItemsSearchPanel;
|
||||
private Panel _contentViewPanel;
|
||||
private Panel _contentTreePanel;
|
||||
private ContentView _view;
|
||||
@@ -43,18 +48,23 @@ namespace FlaxEditor.Windows
|
||||
private readonly ToolStripButton _navigateUpButton;
|
||||
|
||||
private NavigationBar _navigationBar;
|
||||
private Panel _viewDropdownPanel;
|
||||
private Tree _tree;
|
||||
private TextBox _foldersSearchBox;
|
||||
private TextBox _itemsSearchBox;
|
||||
private ViewDropdown _viewDropdown;
|
||||
private SortType _sortType;
|
||||
private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true, _showGeneratedFiles = false;
|
||||
private bool _showAllContentInTree;
|
||||
private bool _suppressExpandedStateSave;
|
||||
private readonly HashSet<string> _expandedFolderPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
private bool _renameInTree;
|
||||
|
||||
private RootContentTreeNode _root;
|
||||
private RootContentFolderTreeNode _root;
|
||||
|
||||
private bool _navigationUnlocked;
|
||||
private readonly Stack<ContentTreeNode> _navigationUndo = new Stack<ContentTreeNode>(32);
|
||||
private readonly Stack<ContentTreeNode> _navigationRedo = new Stack<ContentTreeNode>(32);
|
||||
private readonly Stack<ContentFolderTreeNode> _navigationUndo = new Stack<ContentFolderTreeNode>(32);
|
||||
private readonly Stack<ContentFolderTreeNode> _navigationRedo = new Stack<ContentFolderTreeNode>(32);
|
||||
|
||||
private NewItem _newElement;
|
||||
|
||||
@@ -134,6 +144,9 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsTreeOnlyMode => _showAllContentInTree;
|
||||
internal SortType CurrentSortType => _sortType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentWindow"/> class.
|
||||
/// </summary>
|
||||
@@ -163,14 +176,6 @@ namespace FlaxEditor.Windows
|
||||
_navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up.");
|
||||
_toolStrip.AddSeparator();
|
||||
|
||||
// Navigation bar
|
||||
_navigationBar = new NavigationBar
|
||||
{
|
||||
Parent = _toolStrip,
|
||||
ScrollbarTrackColor = style.Background,
|
||||
ScrollbarThumbColor = style.ForegroundGrey,
|
||||
};
|
||||
|
||||
// Split panel
|
||||
_split = new SplitPanel(options.Options.Interface.ContentWindowOrientation, ScrollBars.None, ScrollBars.None)
|
||||
{
|
||||
@@ -180,19 +185,38 @@ namespace FlaxEditor.Windows
|
||||
Parent = this,
|
||||
};
|
||||
|
||||
// Tree-only panel (used when showing all content in the tree)
|
||||
_treeOnlyPanel = new Panel(ScrollBars.None)
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = new Margin(0, 0, _toolStrip.Bottom, 0),
|
||||
Visible = false,
|
||||
Parent = this,
|
||||
};
|
||||
|
||||
// Tree host panel
|
||||
_treePanelRoot = new ContainerControl
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
Parent = _split.Panel1,
|
||||
};
|
||||
|
||||
// Content structure tree searching query input box
|
||||
var headerPanel = new ContainerControl
|
||||
_treeHeaderPanel = new ContainerControl
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
BackgroundColor = style.Background,
|
||||
IsScrollable = false,
|
||||
Offsets = new Margin(0, 0, 0, 18 + 6),
|
||||
Parent = _treePanelRoot,
|
||||
};
|
||||
|
||||
_foldersSearchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
Parent = headerPanel,
|
||||
Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
|
||||
Parent = _treeHeaderPanel,
|
||||
Bounds = new Rectangle(4, 4, _treeHeaderPanel.Width - 8, 18),
|
||||
};
|
||||
_foldersSearchBox.TextChanged += OnFoldersSearchBoxTextChanged;
|
||||
|
||||
@@ -200,55 +224,74 @@ namespace FlaxEditor.Windows
|
||||
_contentTreePanel = new Panel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = new Margin(0, 0, headerPanel.Bottom, 0),
|
||||
Offsets = new Margin(0, 0, _treeHeaderPanel.Bottom, 0),
|
||||
IsScrollable = true,
|
||||
ScrollBars = ScrollBars.Both,
|
||||
Parent = _split.Panel1,
|
||||
Parent = _treePanelRoot,
|
||||
};
|
||||
|
||||
// Content structure tree
|
||||
_tree = new Tree(false)
|
||||
_tree = new Tree(true)
|
||||
{
|
||||
DrawRootTreeLine = false,
|
||||
Parent = _contentTreePanel,
|
||||
};
|
||||
_tree.SelectedChanged += OnTreeSelectionChanged;
|
||||
headerPanel.Parent = _split.Panel1;
|
||||
|
||||
// Content items searching query input box and filters selector
|
||||
var contentItemsSearchPanel = new ContainerControl
|
||||
_contentItemsSearchPanel = new Panel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
IsScrollable = true,
|
||||
Offsets = new Margin(0, 0, 0, 18 + 8),
|
||||
Parent = _split.Panel2,
|
||||
};
|
||||
const float viewDropdownWidth = 50.0f;
|
||||
|
||||
_itemsSearchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
Parent = contentItemsSearchPanel,
|
||||
Bounds = new Rectangle(viewDropdownWidth + 8, 4, contentItemsSearchPanel.Width - 12 - viewDropdownWidth, 18),
|
||||
Parent = _contentItemsSearchPanel,
|
||||
Bounds = new Rectangle(4, 4, _contentItemsSearchPanel.Width - 8, 18),
|
||||
};
|
||||
_itemsSearchBox.TextChanged += UpdateItemsSearch;
|
||||
|
||||
_viewDropdownPanel = new Panel
|
||||
{
|
||||
Width = 50.0f,
|
||||
Parent = this,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
BackgroundColor = Color.Transparent,
|
||||
};
|
||||
|
||||
_viewDropdown = new ViewDropdown
|
||||
{
|
||||
AnchorPreset = AnchorPresets.MiddleLeft,
|
||||
SupportMultiSelect = true,
|
||||
TooltipText = "Change content view and filter options",
|
||||
Parent = contentItemsSearchPanel,
|
||||
Offsets = new Margin(4, viewDropdownWidth, -9, 18),
|
||||
Offsets = Margin.Zero,
|
||||
Width = 46.0f,
|
||||
Height = 18.0f,
|
||||
Parent = _viewDropdownPanel,
|
||||
};
|
||||
_viewDropdown.LocalX += 2.0f;
|
||||
_viewDropdown.LocalY += _toolStrip.ItemsHeight * 0.5f - 9.0f;
|
||||
_viewDropdown.SelectedIndexChanged += e => UpdateItemsSearch();
|
||||
for (int i = 0; i <= (int)ContentItemSearchFilter.Other; i++)
|
||||
_viewDropdown.Items.Add(((ContentItemSearchFilter)i).ToString());
|
||||
_viewDropdown.PopupCreate += OnViewDropdownPopupCreate;
|
||||
|
||||
// Navigation bar (after view dropdown so layout order stays stable)
|
||||
_navigationBar = new NavigationBar
|
||||
{
|
||||
Parent = _toolStrip,
|
||||
ScrollbarTrackColor = style.Background,
|
||||
ScrollbarThumbColor = style.ForegroundGrey,
|
||||
};
|
||||
|
||||
// Content view panel
|
||||
_contentViewPanel = new Panel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = new Margin(0, 0, contentItemsSearchPanel.Bottom + 4, 0),
|
||||
Offsets = new Margin(0, 0, _contentItemsSearchPanel.Bottom + 4, 0),
|
||||
IsScrollable = true,
|
||||
ScrollBars = ScrollBars.Vertical,
|
||||
Parent = _split.Panel2,
|
||||
@@ -268,9 +311,14 @@ namespace FlaxEditor.Windows
|
||||
_view.OnDelete += Delete;
|
||||
_view.OnDuplicate += Duplicate;
|
||||
_view.OnPaste += Paste;
|
||||
_view.ViewScaleChanged += ApplyTreeViewScale;
|
||||
|
||||
_view.InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
|
||||
InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
|
||||
|
||||
LoadExpandedFolders();
|
||||
UpdateViewDropdownBounds();
|
||||
ApplyTreeViewScale();
|
||||
}
|
||||
|
||||
private void OnCreateNewItemButtonClicked()
|
||||
@@ -325,6 +373,7 @@ namespace FlaxEditor.Windows
|
||||
var viewType = menu.AddChildMenu("View Type");
|
||||
viewType.ContextMenu.AddButton("Tiles", OnViewTypeButtonClicked).Tag = ContentViewType.Tiles;
|
||||
viewType.ContextMenu.AddButton("List", OnViewTypeButtonClicked).Tag = ContentViewType.List;
|
||||
viewType.ContextMenu.AddButton("Tree View", OnViewTypeButtonClicked).Tag = "Tree";
|
||||
viewType.ContextMenu.VisibleChanged += control =>
|
||||
{
|
||||
if (!control.Visible)
|
||||
@@ -332,13 +381,23 @@ namespace FlaxEditor.Windows
|
||||
foreach (var item in ((ContextMenu)control).Items)
|
||||
{
|
||||
if (item is ContextMenuButton button)
|
||||
button.Checked = View.ViewType == (ContentViewType)button.Tag;
|
||||
{
|
||||
if (button.Tag is ContentViewType type)
|
||||
button.Checked = View.ViewType == type && !_showAllContentInTree;
|
||||
else
|
||||
button.Checked = _showAllContentInTree;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var show = menu.AddChildMenu("Show");
|
||||
{
|
||||
var b = show.ContextMenu.AddButton("File extensions", () => View.ShowFileExtensions = !View.ShowFileExtensions);
|
||||
var b = show.ContextMenu.AddButton("File extensions", () =>
|
||||
{
|
||||
View.ShowFileExtensions = !View.ShowFileExtensions;
|
||||
if (_showAllContentInTree)
|
||||
UpdateTreeItemNames(_root);
|
||||
});
|
||||
b.TooltipText = "Shows all files with extensions";
|
||||
b.Checked = View.ShowFileExtensions;
|
||||
b.CloseMenuOnClick = false;
|
||||
@@ -419,9 +478,63 @@ namespace FlaxEditor.Windows
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
private void SetShowAllContentInTree(bool value)
|
||||
{
|
||||
if (_showAllContentInTree == value)
|
||||
return;
|
||||
|
||||
_showAllContentInTree = value;
|
||||
ApplyTreeViewMode();
|
||||
}
|
||||
|
||||
private void ApplyTreeViewMode()
|
||||
{
|
||||
if (_treeOnlyPanel == null || _split == null || _treePanelRoot == null)
|
||||
return;
|
||||
|
||||
if (_showAllContentInTree)
|
||||
{
|
||||
_split.Visible = false;
|
||||
_treeOnlyPanel.Visible = true;
|
||||
_treePanelRoot.Parent = _treeOnlyPanel;
|
||||
_treePanelRoot.Offsets = Margin.Zero;
|
||||
_contentItemsSearchPanel.Visible = false;
|
||||
_itemsSearchBox.Visible = false;
|
||||
_contentViewPanel.Visible = false;
|
||||
RefreshTreeItems();
|
||||
}
|
||||
else
|
||||
{
|
||||
_treeOnlyPanel.Visible = false;
|
||||
_split.Visible = true;
|
||||
_treePanelRoot.Parent = _split.Panel1;
|
||||
_treePanelRoot.Offsets = Margin.Zero;
|
||||
_contentItemsSearchPanel.Visible = true;
|
||||
_itemsSearchBox.Visible = true;
|
||||
_contentViewPanel.Visible = true;
|
||||
if (_tree.SelectedNode is ContentItemTreeNode itemNode && itemNode.Parent is TreeNode parentNode)
|
||||
_tree.Select(parentNode);
|
||||
if (_root != null)
|
||||
RemoveTreeAssetNodes(_root);
|
||||
RefreshView(SelectedNode);
|
||||
}
|
||||
|
||||
PerformLayout();
|
||||
ApplyTreeViewScale();
|
||||
_tree.PerformLayout();
|
||||
}
|
||||
|
||||
private void OnViewTypeButtonClicked(ContextMenuButton button)
|
||||
{
|
||||
View.ViewType = (ContentViewType)button.Tag;
|
||||
if (button.Tag is ContentViewType viewType)
|
||||
{
|
||||
SetShowAllContentInTree(false);
|
||||
View.ViewType = viewType;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetShowAllContentInTree(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFilterClicked(ContextMenuButton filterButton)
|
||||
@@ -481,15 +594,58 @@ namespace FlaxEditor.Windows
|
||||
// Show element in the view
|
||||
Select(item, true);
|
||||
|
||||
// Disable scrolling in content view
|
||||
if (_contentViewPanel.VScrollBar != null)
|
||||
_contentViewPanel.VScrollBar.ThumbEnabled = false;
|
||||
if (_contentViewPanel.HScrollBar != null)
|
||||
_contentViewPanel.HScrollBar.ThumbEnabled = false;
|
||||
ScrollingOnContentView(false);
|
||||
// Disable scrolling in proper view
|
||||
_renameInTree = _showAllContentInTree;
|
||||
if (_renameInTree)
|
||||
{
|
||||
if (_contentTreePanel.VScrollBar != null)
|
||||
_contentTreePanel.VScrollBar.ThumbEnabled = false;
|
||||
if (_contentTreePanel.HScrollBar != null)
|
||||
_contentTreePanel.HScrollBar.ThumbEnabled = false;
|
||||
ScrollingOnTreeView(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_contentViewPanel.VScrollBar != null)
|
||||
_contentViewPanel.VScrollBar.ThumbEnabled = false;
|
||||
if (_contentViewPanel.HScrollBar != null)
|
||||
_contentViewPanel.HScrollBar.ThumbEnabled = false;
|
||||
ScrollingOnContentView(false);
|
||||
}
|
||||
|
||||
// Show rename popup
|
||||
var popup = RenamePopup.Show(item, item.TextRectangle, item.ShortName, true);
|
||||
RenamePopup popup;
|
||||
if (_renameInTree)
|
||||
{
|
||||
TreeNode node = null;
|
||||
if (item is ContentFolder folder)
|
||||
node = folder.Node;
|
||||
else if (item.ParentFolder != null)
|
||||
node = FindTreeItemNode(item.ParentFolder.Node, item);
|
||||
if (node == null)
|
||||
{
|
||||
// Fallback to content view rename
|
||||
popup = RenamePopup.Show(item, item.TextRectangle, item.ShortName, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var area = node.TextRect;
|
||||
const float minRenameWidth = 220.0f;
|
||||
if (area.Width < minRenameWidth)
|
||||
{
|
||||
float expand = minRenameWidth - area.Width;
|
||||
area.X -= expand * 0.5f;
|
||||
area.Width = minRenameWidth;
|
||||
}
|
||||
area.Y -= 2;
|
||||
area.Height += 4.0f;
|
||||
popup = RenamePopup.Show(node, area, item.ShortName, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
popup = RenamePopup.Show(item, item.TextRectangle, item.ShortName, true);
|
||||
}
|
||||
popup.Tag = item;
|
||||
popup.Validate += OnRenameValidate;
|
||||
popup.Renamed += renamePopup => Rename((ContentItem)renamePopup.Tag, renamePopup.Text);
|
||||
@@ -509,12 +665,24 @@ namespace FlaxEditor.Windows
|
||||
|
||||
private void OnRenameClosed(RenamePopup popup)
|
||||
{
|
||||
// Restore scrolling in content view
|
||||
if (_contentViewPanel.VScrollBar != null)
|
||||
_contentViewPanel.VScrollBar.ThumbEnabled = true;
|
||||
if (_contentViewPanel.HScrollBar != null)
|
||||
_contentViewPanel.HScrollBar.ThumbEnabled = true;
|
||||
ScrollingOnContentView(true);
|
||||
// Restore scrolling in proper view
|
||||
if (_renameInTree)
|
||||
{
|
||||
if (_contentTreePanel.VScrollBar != null)
|
||||
_contentTreePanel.VScrollBar.ThumbEnabled = true;
|
||||
if (_contentTreePanel.HScrollBar != null)
|
||||
_contentTreePanel.HScrollBar.ThumbEnabled = true;
|
||||
ScrollingOnTreeView(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_contentViewPanel.VScrollBar != null)
|
||||
_contentViewPanel.VScrollBar.ThumbEnabled = true;
|
||||
if (_contentViewPanel.HScrollBar != null)
|
||||
_contentViewPanel.HScrollBar.ThumbEnabled = true;
|
||||
ScrollingOnContentView(true);
|
||||
}
|
||||
_renameInTree = false;
|
||||
|
||||
// Check if was creating new element
|
||||
if (_newElement != null)
|
||||
@@ -927,6 +1095,16 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
}
|
||||
|
||||
private void OnContentDatabaseItemAdded(ContentItem contentItem)
|
||||
{
|
||||
if (contentItem is ContentFolder folder && _expandedFolderPaths.Contains(StringUtils.NormalizePath(folder.Path)))
|
||||
{
|
||||
_suppressExpandedStateSave = true;
|
||||
folder.Node?.Expand(true);
|
||||
_suppressExpandedStateSave = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the specified content item.
|
||||
/// </summary>
|
||||
@@ -943,7 +1121,8 @@ namespace FlaxEditor.Windows
|
||||
var folder = (ContentFolder)item;
|
||||
folder.Node.Expand();
|
||||
_tree.Select(folder.Node);
|
||||
_view.SelectFirstItem();
|
||||
if (!_showAllContentInTree)
|
||||
_view.SelectFirstItem();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -984,6 +1163,36 @@ namespace FlaxEditor.Windows
|
||||
// Ensure that window is visible
|
||||
FocusOrShow();
|
||||
|
||||
if (_showAllContentInTree)
|
||||
{
|
||||
var targetNode = item is ContentFolder folder ? folder.Node : parent.Node;
|
||||
if (targetNode != null)
|
||||
{
|
||||
targetNode.ExpandAllParents();
|
||||
if (item is ContentFolder)
|
||||
{
|
||||
_tree.Select(targetNode);
|
||||
_contentTreePanel.ScrollViewTo(targetNode, fastScroll);
|
||||
targetNode.Focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
var itemNode = FindTreeItemNode(targetNode, item);
|
||||
if (itemNode != null)
|
||||
{
|
||||
_tree.Select(itemNode);
|
||||
_contentTreePanel.ScrollViewTo(itemNode, fastScroll);
|
||||
itemNode.Focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
_tree.Select(targetNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate to the parent directory
|
||||
Navigate(parent.Node);
|
||||
|
||||
@@ -995,23 +1204,45 @@ namespace FlaxEditor.Windows
|
||||
_view.Focus();
|
||||
}
|
||||
|
||||
private ContentItemTreeNode FindTreeItemNode(ContentFolderTreeNode parentNode, ContentItem item)
|
||||
{
|
||||
if (parentNode == null || item == null)
|
||||
return null;
|
||||
for (int i = 0; i < parentNode.ChildrenCount; i++)
|
||||
{
|
||||
if (parentNode.GetChild(i) is ContentItemTreeNode itemNode && itemNode.Item == item)
|
||||
return itemNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the current view items collection.
|
||||
/// </summary>
|
||||
public void RefreshView()
|
||||
{
|
||||
if (_view.IsSearching)
|
||||
if (_showAllContentInTree)
|
||||
RefreshTreeItems();
|
||||
else if (_view.IsSearching)
|
||||
UpdateItemsSearch();
|
||||
else
|
||||
RefreshView(SelectedNode);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the view.
|
||||
/// </summary>
|
||||
/// <param name="target">The target location.</param>
|
||||
public void RefreshView(ContentTreeNode target)
|
||||
public void RefreshView(ContentFolderTreeNode target)
|
||||
{
|
||||
if (_showAllContentInTree)
|
||||
{
|
||||
RefreshTreeItems();
|
||||
return;
|
||||
}
|
||||
|
||||
_view.IsSearching = false;
|
||||
if (target == _root)
|
||||
{
|
||||
@@ -1019,7 +1250,7 @@ namespace FlaxEditor.Windows
|
||||
var items = new List<ContentItem>(8);
|
||||
for (int i = 0; i < _root.ChildrenCount; i++)
|
||||
{
|
||||
if (_root.GetChild(i) is ContentTreeNode node)
|
||||
if (_root.GetChild(i) is ContentFolderTreeNode node)
|
||||
{
|
||||
items.Add(node.Folder);
|
||||
}
|
||||
@@ -1038,12 +1269,263 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshTreeItems()
|
||||
{
|
||||
if (!_showAllContentInTree || _root == null)
|
||||
return;
|
||||
|
||||
_root.LockChildrenRecursive();
|
||||
RemoveTreeAssetNodes(_root);
|
||||
AddTreeAssetNodes(_root);
|
||||
_root.UnlockChildrenRecursive();
|
||||
_tree.PerformLayout();
|
||||
}
|
||||
|
||||
private void UpdateTreeItemNames(ContentFolderTreeNode node)
|
||||
{
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < node.ChildrenCount; i++)
|
||||
{
|
||||
if (node.GetChild(i) is ContentFolderTreeNode childFolder)
|
||||
{
|
||||
UpdateTreeItemNames(childFolder);
|
||||
}
|
||||
else if (node.GetChild(i) is ContentItemTreeNode itemNode)
|
||||
{
|
||||
itemNode.UpdateDisplayedName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnContentTreeNodeExpandedChanged(ContentFolderTreeNode node, bool isExpanded)
|
||||
{
|
||||
if (_suppressExpandedStateSave || node == null || node == _root)
|
||||
return;
|
||||
|
||||
var path = node.Path;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return;
|
||||
path = StringUtils.NormalizePath(path);
|
||||
|
||||
if (isExpanded)
|
||||
_expandedFolderPaths.Add(path);
|
||||
else
|
||||
// Remove all sub paths if parent folder is closed.
|
||||
_expandedFolderPaths.RemoveWhere(x => x.Contains(path));
|
||||
|
||||
SaveExpandedFolders();
|
||||
}
|
||||
|
||||
internal void TryAutoExpandContentNode(ContentFolderTreeNode node)
|
||||
{
|
||||
if (node == null || node == _root)
|
||||
return;
|
||||
|
||||
var path = node.Path;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return;
|
||||
path = StringUtils.NormalizePath(path);
|
||||
|
||||
if (!_expandedFolderPaths.Contains(path))
|
||||
return;
|
||||
|
||||
_suppressExpandedStateSave = true;
|
||||
node.Expand(true);
|
||||
_suppressExpandedStateSave = false;
|
||||
}
|
||||
|
||||
private void LoadExpandedFolders()
|
||||
{
|
||||
_expandedFolderPaths.Clear();
|
||||
if (Editor.ProjectCache.TryGetCustomData(ProjectDataExpandedFolders, out string data) && !string.IsNullOrWhiteSpace(data))
|
||||
{
|
||||
var entries = data.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
var path = entries[i].Trim();
|
||||
if (path.Length == 0)
|
||||
continue;
|
||||
_expandedFolderPaths.Add(StringUtils.NormalizePath(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveExpandedFolders()
|
||||
{
|
||||
if (_expandedFolderPaths.Count == 0)
|
||||
{
|
||||
Editor.ProjectCache.RemoveCustomData(ProjectDataExpandedFolders);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = string.Join("\n", _expandedFolderPaths);
|
||||
Editor.ProjectCache.SetCustomData(ProjectDataExpandedFolders, data);
|
||||
}
|
||||
|
||||
private void ApplyExpandedFolders()
|
||||
{
|
||||
if (_root == null || _expandedFolderPaths.Count == 0)
|
||||
return;
|
||||
|
||||
_suppressExpandedStateSave = true;
|
||||
foreach (var path in _expandedFolderPaths)
|
||||
{
|
||||
if (Editor.ContentDatabase.Find(path) is ContentFolder folder)
|
||||
{
|
||||
folder.Node.ExpandAllParents(true);
|
||||
folder.Node.Expand(true);
|
||||
}
|
||||
}
|
||||
_suppressExpandedStateSave = false;
|
||||
}
|
||||
|
||||
private void RemoveTreeAssetNodes(ContentFolderTreeNode node)
|
||||
{
|
||||
for (int i = node.ChildrenCount - 1; i >= 0; i--)
|
||||
{
|
||||
if (node.GetChild(i) is ContentItemTreeNode itemNode)
|
||||
{
|
||||
node.RemoveChild(itemNode);
|
||||
itemNode.Dispose();
|
||||
}
|
||||
else if (node.GetChild(i) is ContentFolderTreeNode childFolder)
|
||||
{
|
||||
RemoveTreeAssetNodes(childFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTreeAssetNodes(ContentFolderTreeNode node)
|
||||
{
|
||||
if (node.Folder != null)
|
||||
{
|
||||
var children = node.Folder.Children;
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (child is ContentFolder)
|
||||
continue;
|
||||
if (!ShouldShowTreeItem(child))
|
||||
continue;
|
||||
|
||||
var itemNode = new ContentItemTreeNode(child)
|
||||
{
|
||||
Parent = node,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < node.ChildrenCount; i++)
|
||||
{
|
||||
if (node.GetChild(i) is ContentFolderTreeNode childFolder)
|
||||
{
|
||||
AddTreeAssetNodes(childFolder);
|
||||
}
|
||||
}
|
||||
|
||||
node.SortChildren();
|
||||
}
|
||||
|
||||
private bool ShouldShowTreeItem(ContentItem item)
|
||||
{
|
||||
if (item == null || !item.Visible)
|
||||
return false;
|
||||
if (_viewDropdown != null && _viewDropdown.HasSelection)
|
||||
{
|
||||
var filterIndex = (int)item.SearchFilter;
|
||||
if (!_viewDropdown.Selection.Contains(filterIndex))
|
||||
return false;
|
||||
}
|
||||
if (!_showAllFiles && item is FileItem)
|
||||
return false;
|
||||
if (!_showGeneratedFiles && IsGeneratedFile(item.Path))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsGeneratedFile(string path)
|
||||
{
|
||||
return path.EndsWith(".Gen.cs", StringComparison.Ordinal) ||
|
||||
path.EndsWith(".Gen.h", StringComparison.Ordinal) ||
|
||||
path.EndsWith(".Gen.cpp", StringComparison.Ordinal) ||
|
||||
path.EndsWith(".csproj", StringComparison.Ordinal) ||
|
||||
path.Contains(".CSharp");
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
UpdateToolstrip();
|
||||
UpdateNavigationBar();
|
||||
}
|
||||
|
||||
private void ApplyTreeViewScale()
|
||||
{
|
||||
if (_tree == null)
|
||||
return;
|
||||
|
||||
var scale = _showAllContentInTree ? View.ViewScale : 1.0f;
|
||||
var headerHeight = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
|
||||
var style = Style.Current;
|
||||
var fontSize = Mathf.Clamp(style.FontSmall.Size * scale, 8.0f, 28.0f);
|
||||
var fontRef = new FontReference(style.FontSmall.Asset, fontSize);
|
||||
var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
|
||||
var textMarginLeft = 2.0f + Mathf.Max(0.0f, iconSize - 16.0f);
|
||||
ApplyTreeNodeScale(_root, headerHeight, fontRef, textMarginLeft);
|
||||
_root?.PerformLayout(true);
|
||||
_tree.PerformLayout();
|
||||
}
|
||||
|
||||
private void ApplyTreeNodeScale(ContentFolderTreeNode node, float headerHeight, FontReference fontRef, float textMarginLeft)
|
||||
{
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
var margin = node.TextMargin;
|
||||
margin.Left = textMarginLeft;
|
||||
margin.Top = 2.0f;
|
||||
margin.Right = 2.0f;
|
||||
margin.Bottom = 2.0f;
|
||||
node.TextMargin = margin;
|
||||
node.CustomArrowRect = GetTreeArrowRect(node, headerHeight);
|
||||
node.HeaderHeight = headerHeight;
|
||||
node.TextFont = fontRef;
|
||||
for (int i = 0; i < node.ChildrenCount; i++)
|
||||
{
|
||||
if (node.GetChild(i) is ContentFolderTreeNode child)
|
||||
ApplyTreeNodeScale(child, headerHeight, fontRef, textMarginLeft);
|
||||
else if (node.GetChild(i) is ContentItemTreeNode itemNode)
|
||||
{
|
||||
var itemMargin = itemNode.TextMargin;
|
||||
itemMargin.Left = textMarginLeft;
|
||||
itemMargin.Top = 2.0f;
|
||||
itemMargin.Right = 2.0f;
|
||||
itemMargin.Bottom = 2.0f;
|
||||
itemNode.TextMargin = itemMargin;
|
||||
itemNode.HeaderHeight = headerHeight;
|
||||
itemNode.TextFont = fontRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Rectangle GetTreeArrowRect(ContentFolderTreeNode node, float headerHeight)
|
||||
{
|
||||
if (node == null)
|
||||
return Rectangle.Empty;
|
||||
|
||||
var scale = Editor.Instance?.Windows?.ContentWin?.IsTreeOnlyMode == true
|
||||
? Editor.Instance.Windows.ContentWin.View.ViewScale
|
||||
: 1.0f;
|
||||
var arrowSize = Mathf.Clamp(12.0f * scale, 10.0f, 20.0f);
|
||||
var iconSize = Mathf.Clamp(16.0f * scale, 12.0f, 28.0f);
|
||||
var textRect = node.TextRect;
|
||||
var iconLeft = textRect.Left - iconSize - 2.0f;
|
||||
var x = iconLeft - arrowSize - 2.0f;
|
||||
var y = (headerHeight - arrowSize) * 0.5f;
|
||||
return new Rectangle(Mathf.Max(x, 0.0f), Mathf.Max(y, 0.0f), arrowSize, arrowSize);
|
||||
}
|
||||
|
||||
private void UpdateToolstrip()
|
||||
{
|
||||
if (_toolStrip == null)
|
||||
@@ -1063,20 +1545,42 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
var bottomPrev = _toolStrip.Bottom;
|
||||
_navigationBar.UpdateBounds(_toolStrip);
|
||||
if (_viewDropdownPanel != null && _viewDropdownPanel.Visible)
|
||||
{
|
||||
var reserved = _viewDropdownPanel.Width + 8.0f;
|
||||
_navigationBar.Width = Mathf.Max(_navigationBar.Width - reserved, 0.0f);
|
||||
}
|
||||
if (bottomPrev != _toolStrip.Bottom)
|
||||
{
|
||||
// Navigation bar changed toolstrip height
|
||||
_split.Offsets = new Margin(0, 0, _toolStrip.Bottom, 0);
|
||||
if (_treeOnlyPanel != null)
|
||||
_treeOnlyPanel.Offsets = new Margin(0, 0, _toolStrip.Bottom, 0);
|
||||
PerformLayout();
|
||||
}
|
||||
UpdateViewDropdownBounds();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateViewDropdownBounds()
|
||||
{
|
||||
if (_viewDropdownPanel == null || _toolStrip == null)
|
||||
return;
|
||||
|
||||
var margin = _toolStrip.ItemsMargin;
|
||||
var height = _toolStrip.ItemsHeight;
|
||||
var y = _toolStrip.Y + (_toolStrip.Height - height) * 0.5f;
|
||||
var width = _viewDropdownPanel.Width;
|
||||
var x = _toolStrip.Right - width - margin.Right;
|
||||
_viewDropdownPanel.Bounds = new Rectangle(x, y, width, height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInit()
|
||||
{
|
||||
// Content database events
|
||||
Editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true;
|
||||
Editor.ContentDatabase.ItemAdded += OnContentDatabaseItemAdded;
|
||||
Editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved;
|
||||
Editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; };
|
||||
Editor.ContentDatabase.WorkspaceRebuilt += () =>
|
||||
@@ -1095,6 +1599,7 @@ namespace FlaxEditor.Windows
|
||||
ShowRoot();
|
||||
};
|
||||
|
||||
LoadExpandedFolders();
|
||||
Refresh();
|
||||
|
||||
// Load last viewed folder
|
||||
@@ -1110,7 +1615,7 @@ namespace FlaxEditor.Windows
|
||||
|
||||
private void OnScriptsReloadBegin()
|
||||
{
|
||||
var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentTreeNode : null;
|
||||
var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentFolderTreeNode : null;
|
||||
_lastViewedFolderBeforeReload = lastViewedFolder?.Path ?? string.Empty;
|
||||
|
||||
_tree.RemoveChild(_root);
|
||||
@@ -1133,7 +1638,7 @@ namespace FlaxEditor.Windows
|
||||
private void Refresh()
|
||||
{
|
||||
// Setup content root node
|
||||
_root = new RootContentTreeNode
|
||||
_root = new RootContentFolderTreeNode
|
||||
{
|
||||
ChildrenIndent = 0
|
||||
};
|
||||
@@ -1156,7 +1661,7 @@ namespace FlaxEditor.Windows
|
||||
_root.AddChild(Editor.ContentDatabase.Engine);
|
||||
|
||||
Editor.ContentDatabase.Game?.Expand(true);
|
||||
_tree.Margin = new Margin(0.0f, 0.0f, -16.0f, 2.0f); // Hide root node
|
||||
_tree.Margin = new Margin(0.0f, 0.0f, -16.0f, ScrollBar.DefaultSize + 2); // Hide root node
|
||||
_tree.AddChild(_root);
|
||||
|
||||
// Setup navigation
|
||||
@@ -1167,6 +1672,8 @@ namespace FlaxEditor.Windows
|
||||
// Update UI layout
|
||||
_isLayoutLocked = false;
|
||||
PerformLayout();
|
||||
ApplyExpandedFolders();
|
||||
ApplyTreeViewMode();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1176,7 +1683,10 @@ namespace FlaxEditor.Windows
|
||||
if (_isWorkspaceDirty)
|
||||
{
|
||||
_isWorkspaceDirty = false;
|
||||
RefreshView();
|
||||
if (_showAllContentInTree)
|
||||
RefreshTreeItems();
|
||||
else
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
base.Update(deltaTime);
|
||||
@@ -1186,7 +1696,15 @@ namespace FlaxEditor.Windows
|
||||
public override void OnExit()
|
||||
{
|
||||
// Save last viewed folder
|
||||
var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentTreeNode : null;
|
||||
ContentFolderTreeNode lastViewedFolder = null;
|
||||
if (_tree.Selection.Count == 1)
|
||||
{
|
||||
var selectedNode = _tree.SelectedNode;
|
||||
if (selectedNode is ContentItemTreeNode itemNode)
|
||||
lastViewedFolder = itemNode.Item?.ParentFolder?.Node;
|
||||
else
|
||||
lastViewedFolder = selectedNode as ContentFolderTreeNode;
|
||||
}
|
||||
Editor.ProjectCache.SetCustomData(ProjectDataLastViewedFolder, lastViewedFolder?.Path ?? string.Empty);
|
||||
|
||||
// Clear view
|
||||
@@ -1197,7 +1715,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
while (_root.HasChildren)
|
||||
{
|
||||
_root.RemoveChild((ContentTreeNode)_root.GetChild(0));
|
||||
_root.RemoveChild((ContentFolderTreeNode)_root.GetChild(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1232,7 +1750,12 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
ShowContextMenuForItem(null, ref location, false);
|
||||
}
|
||||
else if (c is ContentTreeNode node)
|
||||
else if (c is ContentItemTreeNode itemNode)
|
||||
{
|
||||
_tree.Select(itemNode);
|
||||
ShowContextMenuForItem(itemNode.Item, ref location, false);
|
||||
}
|
||||
else if (c is ContentFolderTreeNode node)
|
||||
{
|
||||
_tree.Select(node);
|
||||
ShowContextMenuForItem(node.Folder, ref location, true);
|
||||
@@ -1258,11 +1781,16 @@ namespace FlaxEditor.Windows
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
UpdateNavigationBarBounds();
|
||||
|
||||
base.PerformLayoutBeforeChildren();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutAfterChildren()
|
||||
{
|
||||
base.PerformLayoutAfterChildren();
|
||||
UpdateNavigationBarBounds();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool UseLayoutData => true;
|
||||
|
||||
@@ -1277,6 +1805,7 @@ namespace FlaxEditor.Windows
|
||||
writer.WriteAttributeString("ShowAllFiles", ShowAllFiles.ToString());
|
||||
writer.WriteAttributeString("ShowGeneratedFiles", ShowGeneratedFiles.ToString());
|
||||
writer.WriteAttributeString("ViewType", _view.ViewType.ToString());
|
||||
writer.WriteAttributeString("TreeViewAllContent", _showAllContentInTree.ToString());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1297,6 +1826,9 @@ namespace FlaxEditor.Windows
|
||||
ShowGeneratedFiles = value2;
|
||||
if (Enum.TryParse(node.GetAttribute("ViewType"), out ContentViewType viewType))
|
||||
_view.ViewType = viewType;
|
||||
if (bool.TryParse(node.GetAttribute("TreeViewAllContent"), out value2))
|
||||
_showAllContentInTree = value2;
|
||||
ApplyTreeViewMode();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1304,6 +1836,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
_split.SplitterValue = 0.2f;
|
||||
_view.ViewScale = 1.0f;
|
||||
_showAllContentInTree = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1312,10 +1845,17 @@ namespace FlaxEditor.Windows
|
||||
_foldersSearchBox = null;
|
||||
_itemsSearchBox = null;
|
||||
_viewDropdown = null;
|
||||
_viewDropdownPanel = null;
|
||||
_treePanelRoot = null;
|
||||
_treeHeaderPanel = null;
|
||||
_treeOnlyPanel = null;
|
||||
_contentItemsSearchPanel = null;
|
||||
|
||||
Editor.Options.OptionsChanged -= OnOptionsChanged;
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
|
||||
if (Editor?.ContentDatabase != null)
|
||||
Editor.ContentDatabase.ItemAdded -= OnContentDatabaseItemAdded;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user