mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
1630 lines
37 KiB
Beef
1630 lines
37 KiB
Beef
using ImGui;
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using GlitchyEngine.Collections;
|
|
using System.Collections;
|
|
using GlitchyEngine.Renderer;
|
|
using GlitchyEngine.Math;
|
|
using GlitchyEngine.Content;
|
|
using GlitchyEngine;
|
|
using GlitchyEditor.Assets;
|
|
using System.Diagnostics;
|
|
using GlitchyEditor.CodeEditors;
|
|
using GlitchyEditor.Multithreading;
|
|
|
|
namespace GlitchyEditor.EditWindows
|
|
{
|
|
using internal GlitchyEditor.EditorContentManager;
|
|
|
|
class AssetCreator
|
|
{
|
|
private String _name ~ delete _;
|
|
private String _defaultFileName ~ delete _;
|
|
private String _fileExtension ~ delete _;
|
|
private CreateAssetFunc _doCreateAsset ~ delete _;
|
|
|
|
private SubTexture2D _icon ~ _?.ReleaseRef();
|
|
|
|
public delegate void CreateAssetFunc(StringView outputPath);
|
|
|
|
public StringView Name => _name;
|
|
public StringView DefaultFileName => _defaultFileName;
|
|
public StringView FileExtension => _fileExtension;
|
|
|
|
public CreateAssetFunc CreateAsset => _doCreateAsset;
|
|
|
|
public SubTexture2D Icon => _icon;
|
|
|
|
public this(StringView name, StringView defaultFileName, StringView fileExtension, CreateAssetFunc createAsset, SubTexture2D icon = null)
|
|
{
|
|
_name = new String(name);
|
|
_defaultFileName = new String(defaultFileName);
|
|
_fileExtension = new String(fileExtension);
|
|
_doCreateAsset = createAsset;
|
|
|
|
_icon = icon;
|
|
_icon?.AddRef();
|
|
}
|
|
}
|
|
|
|
class ContentBrowserWindow : EditorWindow
|
|
{
|
|
private append List<AssetCreator> _assetCreators = .() ~ ClearAndDeleteItems!(_);
|
|
|
|
private typealias AssetCreatorTree = TreeNode<(StringView Name, AssetCreator Creator)>;
|
|
|
|
private AssetCreatorTree _creatorTree = new AssetCreatorTree(("Create new", null)) ~ delete _;
|
|
/// The last Asset Creator that was inserted.
|
|
private AssetCreatorTree _lastInsertedAssetCreator = null;
|
|
|
|
public const String s_WindowTitle = "Content Browser";
|
|
|
|
class History
|
|
{
|
|
AssetHierarchy _hierarchy;
|
|
|
|
private append List<String> _history = .() ~ ClearAndDeleteItems!(_);
|
|
private int _currentIndex = -1;
|
|
|
|
private int _nextIndex = -1;
|
|
private String _pathToInsert = null;
|
|
private bool replace = false;
|
|
private bool trim = false;
|
|
|
|
public AssetHierarchy AssetHierarchy
|
|
{
|
|
get => _hierarchy;
|
|
set => _hierarchy = value;
|
|
}
|
|
|
|
public int CurrentIndex => _currentIndex;
|
|
public int HistoryLength => _history.Count;
|
|
|
|
public Span<String> History => _history;
|
|
|
|
public StringView CurrentDirectoryPath => (_history.Count == 0 || _currentIndex < 0) ? "" : _history[_currentIndex];
|
|
|
|
public bool CanGoBack => _currentIndex > 0;
|
|
public bool CanGoForward => (_currentIndex + 1) < _history.Count;
|
|
|
|
public void Update()
|
|
{
|
|
// We always defer the actual navigation to the next frame, so that the UI-rendering and updates are more robust and don't flicker for one frame
|
|
_currentIndex = _nextIndex;
|
|
if (String.IsNullOrWhiteSpace(_pathToInsert))
|
|
{
|
|
delete _pathToInsert;
|
|
}
|
|
else
|
|
{
|
|
if (replace)
|
|
{
|
|
_history[_currentIndex].Set(_pathToInsert);
|
|
delete _pathToInsert;
|
|
}
|
|
else
|
|
{
|
|
_history.Insert(_currentIndex, _pathToInsert);
|
|
}
|
|
|
|
if (trim)
|
|
{
|
|
TrimHistory();
|
|
}
|
|
|
|
_pathToInsert = null;
|
|
replace = false;
|
|
trim = false;
|
|
}
|
|
}
|
|
|
|
public void Navigate(StringView nextPath)
|
|
{
|
|
_nextIndex++;
|
|
_pathToInsert = new String(nextPath);
|
|
|
|
TrimHistory();
|
|
}
|
|
|
|
private void TrimHistory()
|
|
{
|
|
while (_history.Count > (_currentIndex + 1))
|
|
{
|
|
delete _history.PopBack();
|
|
}
|
|
}
|
|
|
|
public void Replace(StringView nextPath)
|
|
{
|
|
_pathToInsert = new String(nextPath);
|
|
if (_currentIndex == -1)
|
|
{
|
|
_nextIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
replace = true;
|
|
}
|
|
|
|
trim = true;
|
|
}
|
|
|
|
public bool Back()
|
|
{
|
|
if (CanGoBack)
|
|
{
|
|
_nextIndex--;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool Forward()
|
|
{
|
|
if (CanGoForward)
|
|
{
|
|
_nextIndex++;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool NavigateToIndex(int index)
|
|
{
|
|
if (index < 0)
|
|
return false;
|
|
|
|
if (index >= _history.Count)
|
|
return false;
|
|
|
|
_nextIndex = index;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private append History _directoryHistory = .();
|
|
|
|
public StringView CurrentDirectory => _directoryHistory.CurrentDirectoryPath;
|
|
|
|
TreeNode<AssetNode> _currentDirectoryNode => {
|
|
Result<TreeNode<AssetNode>> currentDirectoryNode = _manager.AssetHierarchy.GetNodeFromPath(_directoryHistory.CurrentDirectoryPath);
|
|
|
|
if (currentDirectoryNode case .Ok(TreeNode<AssetNode> currentDirectory))
|
|
{
|
|
return currentDirectory;
|
|
}
|
|
|
|
null
|
|
};
|
|
|
|
private append List<String> _selectedFiles = .() ~ ClearAndDeleteItems!(_);
|
|
|
|
private append List<String> _filesToCopy = .() ~ ClearAndDeleteItems!(_);
|
|
private bool _cutFiles = false;
|
|
|
|
private append String _assetToRename = .();
|
|
private char8[128] _renameFileNameBuffer;
|
|
|
|
private AssetThumbnailManager _thumbnailManager;
|
|
|
|
public static SubTexture2D s_FolderTexture;
|
|
public static SubTexture2D s_FileTexture;
|
|
|
|
public EditorContentManager _manager;
|
|
|
|
// TODO: Update
|
|
public StringView SelectedFile => _selectedFiles.First();
|
|
|
|
char8[256] _newFileName = .();
|
|
bool _showNewFile = false;
|
|
AssetCreator _newFileCreator = null;
|
|
|
|
char8[256] _filesFilter = .();
|
|
bool _searchEverywhere;
|
|
|
|
public Event<EventHandler<StringView>> OnFileSelected ~ _.Dispose();
|
|
|
|
public this(Editor editor, EditorContentManager contentManager, AssetThumbnailManager thumbnailManager)
|
|
{
|
|
_editor = editor;
|
|
_manager = contentManager;
|
|
_thumbnailManager = thumbnailManager;
|
|
}
|
|
|
|
/// Registers an asset creator that will show up in the "Create new" drop down menu
|
|
public void RegisterAssetCreator(AssetCreator assetCreator)
|
|
{
|
|
_assetCreators.Add(assetCreator);
|
|
|
|
AssetCreatorTree tree = _creatorTree;
|
|
|
|
for (StringView entryName in assetCreator.Name.Split('/'))
|
|
{
|
|
AssetCreatorTree nextNode = tree.Children.Where((node) => node->Name == entryName).FirstOrDefault();
|
|
|
|
// Create new node if we didn't find it
|
|
nextNode ??= tree.AddChild((entryName, null));
|
|
|
|
tree = nextNode;
|
|
}
|
|
|
|
if (tree->Creator == null)
|
|
{
|
|
tree->Creator = assetCreator;
|
|
}
|
|
|
|
_lastInsertedAssetCreator = tree;
|
|
}
|
|
|
|
/// Adds a seperator line after the asset creator that was last added.
|
|
public void InsertAssetCreatorSeparator()
|
|
{
|
|
AssetCreatorTree parent = _lastInsertedAssetCreator.Parent;
|
|
|
|
// Separators have an empty name and no creator!
|
|
parent.AddChild(("", null));
|
|
}
|
|
|
|
private SelectionMode _selectionMode;
|
|
private bool _addToSelection;
|
|
|
|
bool _wantsDelete = false;
|
|
|
|
private void NavigateForward()
|
|
{
|
|
if (_directoryHistory.Forward())
|
|
{
|
|
_selectedFiles.ClearAndDeleteItems();
|
|
}
|
|
}
|
|
|
|
private void NavigateBack()
|
|
{
|
|
if (_directoryHistory.Back())
|
|
{
|
|
_selectedFiles.ClearAndDeleteItems();
|
|
}
|
|
}
|
|
|
|
private void Navigate(TreeNode<AssetNode> node)
|
|
{
|
|
if (node == _currentDirectoryNode)
|
|
return;
|
|
|
|
_directoryHistory.Navigate(node->Path);
|
|
_selectedFiles.ClearAndDeleteItems();
|
|
}
|
|
|
|
private void NavigateToIndex(int index)
|
|
{
|
|
if (_directoryHistory.NavigateToIndex(index))
|
|
{
|
|
_selectedFiles.ClearAndDeleteItems();
|
|
}
|
|
}
|
|
|
|
private bool CanNavigateUp => _currentDirectoryNode?.Parent != _manager.AssetHierarchy.RootNode;
|
|
|
|
private void NavigateUp()
|
|
{
|
|
if (CanNavigateUp)
|
|
{
|
|
Navigate(_currentDirectoryNode.Parent);
|
|
}
|
|
}
|
|
|
|
protected override void InternalShow()
|
|
{
|
|
_manager.Update();
|
|
|
|
/*if (ImGui.Begin("Debug History"))
|
|
{
|
|
ImGui.CollapsingHeader("History");
|
|
for (String path in _directoryHistory.[Friend]_history)
|
|
{
|
|
if (_directoryHistory.CurrentDirectoryPath === StringView(path))
|
|
{
|
|
ImGui.Text("==>");
|
|
ImGui.SameLine();
|
|
}
|
|
ImGui.Text(path);
|
|
}
|
|
|
|
ImGui.CollapsingHeader("Selected Files");
|
|
|
|
for (String path in _selectedFiles)
|
|
{
|
|
ImGui.Text(path);
|
|
}
|
|
|
|
ImGui.End();
|
|
}*/
|
|
|
|
// Make sure we are in an existing directory.
|
|
if (!_manager.AssetHierarchy.FileExists(CurrentDirectory))
|
|
{
|
|
if (Directory.Exists(_manager.AssetDirectory))
|
|
{
|
|
_directoryHistory.Replace(_manager.AssetDirectory);
|
|
}
|
|
else
|
|
{
|
|
// If the assets directory doesn't exist (probably, because we have no project open)
|
|
// we navigate to the resources directory.
|
|
// This MUST exist, otherwise we wouldn't have managed to launch the Editor.
|
|
_directoryHistory.Replace(_manager.ResourcesDirectory);
|
|
}
|
|
}
|
|
|
|
_directoryHistory.Update();
|
|
|
|
|
|
if(!ImGui.Begin(s_WindowTitle, &_open, .None))
|
|
{
|
|
ImGui.End();
|
|
return;
|
|
}
|
|
|
|
DrawNavigationBar();
|
|
|
|
// Update selection modifies
|
|
{
|
|
_addToSelection = Input.IsKeyPressed(.Control);
|
|
|
|
if (Input.IsKeyPressed(.Shift))
|
|
{
|
|
_selectionMode = .Range;
|
|
}
|
|
else
|
|
{
|
|
_selectionMode = .SingleFile;
|
|
}
|
|
}
|
|
|
|
// Remove paths from selection that no longer exist.
|
|
for (String path in _selectedFiles)
|
|
{
|
|
Result<TreeNode<AssetNode>> node = _manager.AssetHierarchy.GetNodeFromPath(path);
|
|
if (node case .Err)
|
|
{
|
|
@path.Remove();
|
|
delete path;
|
|
}
|
|
}
|
|
|
|
// Deletion
|
|
{
|
|
if (Input.IsKeyPressing(.Delete) && _selectedFiles.Count > 0)
|
|
{
|
|
_wantsDelete = true;
|
|
}
|
|
|
|
if (_wantsDelete)
|
|
{
|
|
ImGui.OpenPopup("Delete?");
|
|
_wantsDelete = false;
|
|
}
|
|
|
|
DrawDeleteItemPopup();
|
|
}
|
|
|
|
ImGui.PushStyleVar(.CellPadding, .(0, 0));
|
|
|
|
bool alt_pressed = ImGui.GetIO().KeyAlt;
|
|
|
|
// Table with two columns, one for Directory-Sidebar and one for File-Browser
|
|
if (ImGui.BeginTable("table", 2, .BordersInnerV | .Resizable | .Reorderable | .NoPadOuterX))
|
|
{
|
|
if (alt_pressed)
|
|
{
|
|
// Header anzeigen, damit sie neu angeordnet werden können
|
|
ImGui.TableSetupColumn("Folder Hierarchy");
|
|
ImGui.TableSetupColumn("Files");
|
|
ImGui.TableHeadersRow();
|
|
}
|
|
|
|
ImGui.TableNextRow();
|
|
ImGui.TableSetColumnIndex(0);
|
|
|
|
if (ImGui.BeginChild("Sidebar"))
|
|
{
|
|
DrawDirectorySideBar();
|
|
|
|
ImGui.EndChild();
|
|
}
|
|
|
|
ImGui.TableNextColumn();
|
|
|
|
DrawSearchBar();
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.BeginChild("Files"))
|
|
{
|
|
Result<TreeNode<AssetNode>> currentDirectoryNode = _manager.AssetHierarchy.GetNodeFromPath(_directoryHistory.CurrentDirectoryPath);
|
|
|
|
if (currentDirectoryNode case .Ok(TreeNode<AssetNode> currentDirectory))
|
|
{
|
|
DrawCurrentDirectory(currentDirectoryNode);
|
|
}
|
|
|
|
// Context menu when clicking on the background.
|
|
if (ImGui.BeginPopupContextWindow("FilePopup", .AnyPopup | .MouseButtonRight))
|
|
{
|
|
ShowContextMenu();
|
|
ImGui.EndPopup();
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
|
|
if (!ImGui.IsAnyItemHovered() && (ImGui.IsItemClicked(.Left) || ImGui.IsItemClicked(.Right)))
|
|
{
|
|
_selectedFiles.ClearAndDeleteItems();
|
|
}
|
|
|
|
if (currentDirectoryNode case .Ok(out currentDirectory))
|
|
{
|
|
FileDropTarget(currentDirectory, .External);
|
|
}
|
|
}
|
|
|
|
ImGui.EndTable();
|
|
}
|
|
|
|
ImGui.PopStyleVar(1);
|
|
|
|
ImGui.End();
|
|
}
|
|
|
|
private bool IsFileSelected(StringView fullFilePath)
|
|
{
|
|
return _selectedFiles.ContainsAlt(fullFilePath);
|
|
}
|
|
|
|
enum SelectionMode
|
|
{
|
|
SingleFile,
|
|
Range
|
|
}
|
|
|
|
private void SelectFile(StringView fullFilePath, bool clearOldSelection, SelectionMode mode, bool toggle = true)
|
|
{
|
|
if (clearOldSelection)
|
|
{
|
|
_selectedFiles.ClearAndDeleteItems();
|
|
}
|
|
|
|
if (mode == .SingleFile && toggle)
|
|
{
|
|
if (IsFileSelected(fullFilePath))
|
|
{
|
|
String oldPath = _selectedFiles.GetAndRemoveAlt(fullFilePath);
|
|
delete oldPath;
|
|
}
|
|
else
|
|
{
|
|
_selectedFiles.Add(new String(fullFilePath));
|
|
}
|
|
}
|
|
else if (mode == .SingleFile && !toggle)
|
|
{
|
|
if (!IsFileSelected(fullFilePath))
|
|
{
|
|
_selectedFiles.Add(new String(fullFilePath));
|
|
}
|
|
}
|
|
else if (mode == .Range)
|
|
{
|
|
if (IsFileSelected(fullFilePath))
|
|
{
|
|
String oldPath = _selectedFiles.GetAndRemoveAlt(fullFilePath);
|
|
delete oldPath;
|
|
}
|
|
else
|
|
{
|
|
_selectedFiles.Add(new String(fullFilePath));
|
|
}
|
|
}
|
|
|
|
|
|
// TODO: Update event to also provide all selected paths?
|
|
OnFileSelected(this, fullFilePath);
|
|
}
|
|
|
|
private void DeselectFile(StringView fullFilePath)
|
|
{
|
|
String oldPath = TrySilent!(_selectedFiles.GetAndRemoveAlt(fullFilePath));
|
|
delete oldPath;
|
|
}
|
|
|
|
private void DrawSearchBar()
|
|
{
|
|
ImGui.TextUnformatted("Search:");
|
|
ImGui.SameLine();
|
|
ImGui.InputText("##search", &_filesFilter, _filesFilter.Count);
|
|
ImGui.SameLine();
|
|
ImGui.Checkbox("Search everywhere", &_searchEverywhere);
|
|
}
|
|
|
|
private void ShowContextMenu()
|
|
{
|
|
if (_selectedFiles.IsEmpty)
|
|
{
|
|
ShowCurrentFolderContextMenu();
|
|
}
|
|
else
|
|
{
|
|
ShowItemContextMenu();
|
|
}
|
|
}
|
|
|
|
/// Renders the context menu that is shown when the user right clicks on the background of the file browser.
|
|
private void ShowCurrentFolderContextMenu()
|
|
{
|
|
if (ImGui.MenuItem("Open in file browser..."))
|
|
{
|
|
if (Path.OpenFolder(CurrentDirectory) case .Err)
|
|
Log.EngineLogger.Error("Failed to open directory in file browser.");
|
|
}
|
|
|
|
ImGui.AttachTooltip("Opens the current folder in the systems file browser.");
|
|
|
|
if (ImGui.BeginMenu("Copy path"))
|
|
{
|
|
if (ImGui.MenuItem("Full path"))
|
|
{
|
|
ImGui.SetClipboardText(CurrentDirectory.ToScopeCStr!());
|
|
}
|
|
|
|
ImGui.AttachTooltip("Copies the full file path of the current folder.");
|
|
|
|
if (ImGui.MenuItem("Asset identifier"))
|
|
{
|
|
Result<TreeNode<AssetNode>> assetNode = _manager.AssetHierarchy.GetNodeFromPath(CurrentDirectory);
|
|
|
|
if (assetNode case .Ok(let treeNode))
|
|
{
|
|
ImGui.SetClipboardText(treeNode->Identifier.FullIdentifier.Ptr);
|
|
}
|
|
}
|
|
|
|
ImGui.AttachTooltip("Copies the asset identifier of the current folder.");
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
ShowCreateContextMenu(_creatorTree);
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Open C# Project..."))
|
|
{
|
|
Editor.Instance.IdeAdapter.OpenScriptProject();
|
|
}
|
|
|
|
ImGui.AttachTooltip("Opens the C# Solution of this project.");
|
|
|
|
ImGui.Separator();
|
|
|
|
if (!_filesToCopy.IsEmpty && ImGui.MenuItem("Paste", "Ctrl+V"))
|
|
{
|
|
PasteFiles(CurrentDirectory);
|
|
}
|
|
}
|
|
|
|
/// Shows the menu for the given asset creator tree.
|
|
private void ShowCreateContextMenu(AssetCreatorTree subtree)
|
|
{
|
|
// Separators have an empty name and no creator
|
|
if (subtree->Name.IsWhiteSpace && subtree->Creator == null)
|
|
{
|
|
ImGui.Separator();
|
|
|
|
return;
|
|
}
|
|
|
|
if (subtree.Children.Count == 0)
|
|
{
|
|
if (ImGui.MenuItem(subtree->Name.ToScopeCStr!()))
|
|
{
|
|
ShowNewFile(subtree->Creator);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ImGui.BeginMenu(subtree->Name.ToScopeCStr!()))
|
|
{
|
|
for (let node in subtree.Children)
|
|
{
|
|
ShowCreateContextMenu(node);
|
|
}
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Shows a dummy file in the asset browser where the user can enter a file name and create an asset.
|
|
private void ShowNewFile(AssetCreator creator)
|
|
{
|
|
_newFileCreator = creator;
|
|
creator.DefaultFileName.CopyTo(_newFileName);
|
|
_showNewFile = true;
|
|
}
|
|
|
|
/// Hides the dummy file.
|
|
private void HideNewFile()
|
|
{
|
|
_newFileCreator = null;
|
|
_showNewFile = false;
|
|
_newFileName = .();
|
|
}
|
|
|
|
/// Creates a new asset with the given creator
|
|
private void CreateAsset()
|
|
{
|
|
if (!Directory.Exists(CurrentDirectory))
|
|
{
|
|
Log.EngineLogger.Error($"Directory {CurrentDirectory} doesn't exist.");
|
|
return;
|
|
}
|
|
|
|
String currentFile = scope String();
|
|
Path.Combine(currentFile, CurrentDirectory, StringView(&_newFileName));
|
|
|
|
// Add file extension, if necessary
|
|
if (!currentFile.EndsWith(_newFileCreator.FileExtension))
|
|
{
|
|
currentFile.Append(_newFileCreator.FileExtension);
|
|
}
|
|
|
|
StringView fileWithoutExtension = currentFile[0...^(_newFileCreator.FileExtension.Length + 1)];
|
|
|
|
// If the file already exists find a unused number to attach to the end.
|
|
int i = 0;
|
|
while (File.Exists(currentFile))
|
|
{
|
|
i++;
|
|
currentFile.Set(fileWithoutExtension);
|
|
currentFile.AppendF($"({i})");
|
|
currentFile.Append(_newFileCreator.FileExtension);
|
|
}
|
|
|
|
_newFileCreator.CreateAsset(currentFile);
|
|
|
|
HideNewFile();
|
|
}
|
|
|
|
/// Renders a sidebar that shows a tree of all directories in the asset folder.
|
|
private void DrawDirectorySideBar()
|
|
{
|
|
for(var child in _manager.AssetHierarchy.RootNode.Children)
|
|
{
|
|
ImGuiPrintEntityTree(child);
|
|
}
|
|
}
|
|
|
|
/// Renders an ImGui tree of all directories in the given tree.
|
|
/// @param tree The file hierarchy of which to render all directories.
|
|
private void ImGuiPrintEntityTree(TreeNode<AssetNode> tree)
|
|
{
|
|
if (!tree->IsDirectory)
|
|
return;
|
|
|
|
String name = tree->Name;
|
|
|
|
ImGui.TreeNodeFlags flags = .OpenOnArrow | .SpanAvailWidth;
|
|
|
|
if(tree.Children.Where((node) => node->IsDirectory).Count() == 0)
|
|
flags |= .Leaf;
|
|
|
|
if (tree->Path == CurrentDirectory)
|
|
flags |= .Selected;
|
|
|
|
// TODO: this kinda works, but the user should be able to close the directory
|
|
/*if (_manager.AssetHierarchy.GetNodeFromPath(_currentDirectory) case .Ok(let currentTreeNode))
|
|
{
|
|
if (currentTreeNode.IsInSubtree(tree))
|
|
ImGui.SetNextItemOpen(true);
|
|
}*/
|
|
|
|
bool isOpen = ImGui.TreeNodeEx(name, flags, $"{name}");
|
|
|
|
FileDropTarget(tree, .Internal | .External);
|
|
|
|
if (!ImGui.IsItemToggledOpen() && ImGui.IsItemClicked(.Left))
|
|
{
|
|
_directoryHistory.Navigate(tree->Path);
|
|
_selectedFiles.ClearAndDeleteItems();
|
|
}
|
|
|
|
if(isOpen)
|
|
{
|
|
for(var child in tree.Children)
|
|
{
|
|
ImGuiPrintEntityTree(child);
|
|
}
|
|
|
|
ImGui.TreePop();
|
|
}
|
|
}
|
|
|
|
const float2 padding = .(24, 24);
|
|
|
|
private ImGui.RectangleSelectionData _rectangleSelection;
|
|
|
|
enum Modifiers
|
|
{
|
|
None,
|
|
Alt,
|
|
Ctrl,
|
|
Shift
|
|
}
|
|
|
|
private void HandleShortcuts()
|
|
{
|
|
Modifiers modifiers = .None;
|
|
|
|
Enum.SetFlagConditionally(ref modifiers, .Alt, Input.IsKeyPressed(.Alt));
|
|
Enum.SetFlagConditionally(ref modifiers, .Ctrl, Input.IsKeyPressed(.Control));
|
|
Enum.SetFlagConditionally(ref modifiers, .Shift, Input.IsKeyPressed(.Shift));
|
|
|
|
// TODO: Come up with a good hotkey system...
|
|
if (modifiers == .Alt)
|
|
{
|
|
if (Input.IsKeyPressing(.Up))
|
|
{
|
|
NavigateUp();
|
|
}
|
|
|
|
if (Input.IsKeyPressing(.Left))
|
|
{
|
|
NavigateBack();
|
|
}
|
|
|
|
if (Input .IsKeyPressing(.Right))
|
|
{
|
|
NavigateForward();
|
|
}
|
|
}
|
|
|
|
if (Input.IsMouseButtonPressing(.XButton1))
|
|
{
|
|
NavigateBack();
|
|
}
|
|
|
|
if (Input.IsMouseButtonPressing(.XButton2))
|
|
{
|
|
NavigateForward();
|
|
}
|
|
|
|
if (modifiers == .Ctrl)
|
|
{
|
|
if (Input.IsKeyPressing(.C))
|
|
{
|
|
CopySelectedFiles(cutFiles: false);
|
|
}
|
|
|
|
if (Input.IsKeyPressing(.X))
|
|
{
|
|
CopySelectedFiles(cutFiles: true);
|
|
}
|
|
|
|
if (Input.IsKeyPressing(.V))
|
|
{
|
|
PasteFiles(CurrentDirectory);
|
|
}
|
|
}
|
|
|
|
if (modifiers == .Ctrl | .Shift)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
/// Renders the contents of _currentDirectory. Returns the node of the current directory, or null if the browser isn't in a directory.
|
|
private void DrawCurrentDirectory(TreeNode<AssetNode> currentDirectoryNode)
|
|
{
|
|
if (ImGui.IsWindowHovered())
|
|
{
|
|
HandleShortcuts();
|
|
}
|
|
|
|
List<TreeNode<AssetNode>> directoryEntries = scope List<TreeNode<AssetNode>>();
|
|
|
|
StringView searchFilter = StringView(&_filesFilter);
|
|
|
|
if (_searchEverywhere && searchFilter.Length > 0)
|
|
{
|
|
_manager.AssetHierarchy.[Friend]_assetRootNode.ForEach(scope (node) =>
|
|
{
|
|
if (node->Name.Contains(searchFilter, ignoreCase: true))
|
|
{
|
|
directoryEntries.Add(node);
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
if (currentDirectoryNode == null)
|
|
{
|
|
ImGui.TextUnformatted("Failed to display contents of directory.");
|
|
return;
|
|
}
|
|
|
|
currentDirectoryNode.Children.Where((entry) => entry->Name.Contains(searchFilter, true)).ToList(directoryEntries);
|
|
}
|
|
|
|
directoryEntries.Sort((entry1, entry2) => {
|
|
// Sort so that directories are before files.
|
|
int score = entry2->IsDirectory <=> entry1->IsDirectory;
|
|
|
|
if (score == 0)
|
|
{
|
|
// If both are either directories or files sort alphabetically.
|
|
score = StringView.Compare(entry1->Name, entry2->Name, ignoreCase: true);
|
|
}
|
|
|
|
return score;
|
|
});
|
|
|
|
float buttonsPerRow = ImGui.GetContentRegionAvail().x / (IconSize.X + ImGui.GetStyle().ItemSpacing.x);
|
|
int actualButtonsPerRow = (int)buttonsPerRow;
|
|
|
|
float actualSpace = (ImGui.GetContentRegionAvail().x - actualButtonsPerRow * IconSize.X) / (actualButtonsPerRow);
|
|
|
|
int column = 0;
|
|
|
|
for (var entry in directoryEntries)
|
|
{
|
|
ImGui.PushID(entry->Path);
|
|
|
|
DrawDirectoryEntry(entry);
|
|
|
|
column++;
|
|
|
|
if (column < actualButtonsPerRow)
|
|
ImGui.SameLine((IconSize.X + actualSpace) * column);
|
|
else
|
|
column = 0;
|
|
|
|
ImGui.PopID();
|
|
}
|
|
|
|
ImGui.BeginRectangleSelection(ref _rectangleSelection, ImGui.IsMouseDown(.Left) || ImGui.IsMouseDown(.Right));
|
|
|
|
if (_showNewFile)
|
|
{
|
|
ImGui.PushID("New File");
|
|
|
|
ShowNewFile();
|
|
|
|
ImGui.PopID();
|
|
}
|
|
}
|
|
|
|
float _zoom = 1.0f;
|
|
static readonly float2 IconBaseSize = .(100, 100);
|
|
|
|
float2 IconSize => IconBaseSize * _zoom;
|
|
|
|
enum DropTargetMode
|
|
{
|
|
Internal = 1,
|
|
External = 2
|
|
}
|
|
|
|
/// Makes a drop target for the given asset node that files and directories can be dropped on, so that Files can be moved
|
|
private void FileDropTarget(TreeNode<AssetNode> dropTarget, DropTargetMode mode)
|
|
{
|
|
if (dropTarget != null && dropTarget->IsDirectory && ImGui.BeginDragDropTarget())
|
|
{
|
|
bool allowDrop = false;
|
|
|
|
if (mode.HasFlag(.Internal))
|
|
{
|
|
ImGui.Payload* peekPayload = ImGui.AcceptDragDropPayload(.ContentBrowserItem, .AcceptBeforeDelivery);
|
|
|
|
if (peekPayload != null)
|
|
{
|
|
StringView assetIdentifier = .((char8*)peekPayload.Data, (int)peekPayload.DataSize);
|
|
|
|
allowDrop = assetIdentifier != dropTarget->Identifier;
|
|
|
|
if (allowDrop && peekPayload.IsDelivery())
|
|
{
|
|
_manager.AssetHierarchy.MoveFileToNode(assetIdentifier, dropTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode.HasFlag(.External))
|
|
{
|
|
ImGui.Payload* peekPayload = ImGui.AcceptDragDropPayload(.ExternFiles, .AcceptBeforeDelivery);
|
|
|
|
if (peekPayload != null)
|
|
{
|
|
if (peekPayload.IsDelivery())
|
|
{
|
|
Log.EngineLogger.Info($"Dropping into {dropTarget->Path}");
|
|
|
|
List<String> droppedFiles = (.)Internal.UnsafeCastToObject(*(void**)peekPayload.Data);
|
|
|
|
_manager.AssetHierarchy.CopyExternFilesToNodeBackground(dropTarget, droppedFiles, .None);
|
|
|
|
/*for (String path in droppedFiles)
|
|
{
|
|
Log.EngineLogger.Info($"Dropping file {path}");
|
|
|
|
//_manager.AssetHierarchy.CopyExternFileToNodeBackground(dropTarget, path, .None);
|
|
}*/
|
|
}
|
|
else
|
|
{
|
|
EditorLayer.SetDropEffect(.Copy);
|
|
|
|
if (dropTarget->Path == CurrentDirectory)
|
|
{
|
|
ImGui.SetTooltip($"Copy here ({dropTarget->Name})");
|
|
}
|
|
else
|
|
{
|
|
ImGui.SetTooltip($"Copy to {dropTarget->Name}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui.EndDragDropTarget();
|
|
}
|
|
}
|
|
|
|
/// Renders the button for the given directory entry.
|
|
private void DrawDirectoryEntry(TreeNode<AssetNode> entry)
|
|
{
|
|
ImGui.PushStyleVar(.WindowPadding, .(0, 0));
|
|
ImGui.PushStyleVar(.FramePadding, .(0, 0));
|
|
ImGui.PushStyleVar(.ItemSpacing, .(0, 0));
|
|
|
|
defer {ImGui.PopStyleVar(3); }
|
|
|
|
ImGui.BeginGroup();
|
|
|
|
if (IsFileSelected(entry->Path))
|
|
{
|
|
var color = ImGui.GetStyleColorVec4(.ButtonHovered);
|
|
ImGui.PushStyleColor(.Button, *color);
|
|
}
|
|
else
|
|
{
|
|
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
|
|
}
|
|
|
|
// TODO: preview images
|
|
// TODO: Special Icon for back-folder?
|
|
SubTexture2D image = _thumbnailManager.GetThumbnail(entry.Value);
|
|
|
|
ColorRGBA imageColor = ColorRGBA.White;
|
|
|
|
if (_filesToCopy.Contains(entry->Path) && _cutFiles)
|
|
{
|
|
imageColor = ColorRGBA(1, 1, 1, 0.5f);
|
|
}
|
|
|
|
ImGui.ImageButton("FileImage", image, (.)IconSize, tint_col: (.)(float4)imageColor);
|
|
|
|
if (ImGui.IsRectangleSelecting(ref _rectangleSelection))
|
|
{
|
|
if (ImGui.IsInRectangleSelection(ref _rectangleSelection))
|
|
{
|
|
SelectFile(entry->Path, false, .SingleFile, false);
|
|
}
|
|
else
|
|
{
|
|
DeselectFile(entry->Path);
|
|
}
|
|
}
|
|
|
|
ImGui.PopStyleColor();
|
|
|
|
if (ImGui.BeginDragDropSource())
|
|
{
|
|
ImGui.SetDragDropPayload(.ContentBrowserItem, entry->Identifier.FullIdentifier.Ptr, (uint64)entry->Identifier.FullIdentifier.Length, .Once);
|
|
|
|
float2 dndIconSize = (ImGui.GetFontSize() * 2.0f).XX;
|
|
|
|
ImGui.Image(image, (.)dndIconSize);
|
|
|
|
ImGui.SameLine();
|
|
|
|
ImGui.SetCursorPosY((dndIconSize.X - ImGui.GetFontSize()) / 2);
|
|
|
|
ImGui.Text(entry->Identifier.FullIdentifier.Ptr);
|
|
|
|
ImGui.EndDragDropSource();
|
|
}
|
|
|
|
FileDropTarget(entry, .Internal | .External);
|
|
|
|
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(.Left))
|
|
{
|
|
SelectFile(entry->Path, !_addToSelection, _selectionMode);
|
|
// TODO: WHY?
|
|
_assetToRename.Clear();
|
|
}
|
|
|
|
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(.Right))
|
|
{
|
|
// If we right click on a selected file we want to open the context menu for all selected files
|
|
// If we click on an unselected file we want to select it (and potentially clear the selection)
|
|
if (!IsFileSelected(entry->Path))
|
|
{
|
|
SelectFile(entry->Path, !_addToSelection, _selectionMode);
|
|
}
|
|
// TODO: WHY?
|
|
_assetToRename.Clear();
|
|
}
|
|
|
|
if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(.Left))
|
|
{
|
|
if (ImGui.IsKeyDown(.LeftCtrl))
|
|
OpenPropertiesWindow(entry);
|
|
else
|
|
OpenEntry(entry);
|
|
}
|
|
|
|
if (_assetToRename == entry->Path)
|
|
{
|
|
ImGui.PushItemWidth((.)IconSize.X);
|
|
|
|
ImGui.InputText("##renameBox", &_renameFileNameBuffer, _renameFileNameBuffer.Count - 1, .AutoSelectAll);
|
|
|
|
ImGui.PopItemWidth();
|
|
|
|
ImGui.SetKeyboardFocusHere(-1);
|
|
|
|
if (ImGui.IsKeyDown(.Escape))
|
|
{
|
|
_assetToRename.Clear();
|
|
}
|
|
else if (ImGui.IsItemDeactivatedAfterEdit())
|
|
{
|
|
StringView newName = StringView(&_renameFileNameBuffer);
|
|
|
|
if (!newName.IsWhiteSpace)
|
|
_manager.AssetHierarchy.RenameFile(entry.Value, newName);
|
|
|
|
_assetToRename.Clear();
|
|
}
|
|
else if (ImGui.IsItemDeactivated())
|
|
{
|
|
_assetToRename.Clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + IconSize.X);
|
|
ImGui.Text(entry->Name);
|
|
ImGui.PopTextWrapPos();
|
|
}
|
|
|
|
/*if (ImGui.BeginPopupContextWindow())
|
|
{
|
|
ShowItemContextMenu(entry, itemType, ref _wantsDelete);
|
|
ImGui.EndPopup();
|
|
}*/
|
|
|
|
// TODO: Sub assets are probably borked now... I don't know if they ever worked, didn't test them
|
|
if (entry->SubAssets?.Count > 0)
|
|
{
|
|
// Button for revealing sub assets (e.g. Meshes in 3D-Model)
|
|
|
|
ImGui.SameLine();
|
|
if (ImGui.Button(">"))
|
|
ImGui.OpenPopup("SubAssets");
|
|
}
|
|
|
|
if (ImGui.BeginPopup("SubAssets", .Popup))
|
|
{
|
|
for (var subAsset in entry->SubAssets)
|
|
{
|
|
ImGui.Button(subAsset.Name);
|
|
|
|
if (ImGui.BeginDragDropSource())
|
|
{
|
|
String fullpath = scope String(entry->Path);
|
|
fullpath.AppendF($"#{subAsset.Name}");
|
|
|
|
ImGui.SetDragDropPayload(.ContentBrowserItem, fullpath.CStr(), (.)fullpath.Length, .Once);
|
|
|
|
ImGui.EndDragDropSource();
|
|
}
|
|
}
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
|
|
ImGui.EndGroup();
|
|
|
|
ImGui.AttachTooltip(entry->Name);
|
|
}
|
|
|
|
/// Renders the button for the given directory item.
|
|
private void ShowNewFile()
|
|
{
|
|
ImGui.PushStyleVar(.WindowPadding, .(0, 0));
|
|
ImGui.PushStyleVar(.FramePadding, .(0, 0));
|
|
ImGui.PushStyleVar(.ItemSpacing, .(0, 0));
|
|
|
|
defer { ImGui.PopStyleVar(3); }
|
|
|
|
float2 DirectoryItemSize = IconSize + (float2)ImGui.GetStyle().WindowPadding * 2 + float2(0, ImGui.GetFontSize());
|
|
|
|
ImGui.BeginChild("item", (.)DirectoryItemSize, .None, .NoScrollbar);
|
|
|
|
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
|
|
|
|
// TODO: preview images
|
|
SubTexture2D image = _newFileCreator.Icon ?? s_FileTexture;
|
|
|
|
ImGui.ImageButton("FileImage", image, (.)IconSize);
|
|
|
|
ImGui.PopStyleColor();
|
|
|
|
ImGui.PushItemWidth((.)IconSize.X);
|
|
|
|
ImGui.InputText("##newFileNameBox", &_newFileName, _newFileName.Count - 1, .AutoSelectAll);
|
|
|
|
ImGui.PopItemWidth();
|
|
|
|
ImGui.SetKeyboardFocusHere(-1);
|
|
|
|
void CreateFile()
|
|
{
|
|
CreateAsset();
|
|
}
|
|
|
|
if (ImGui.IsKeyDown(.Escape))
|
|
{
|
|
// Abort
|
|
HideNewFile();
|
|
}
|
|
else if (ImGui.IsItemDeactivatedAfterEdit())
|
|
{
|
|
CreateFile();
|
|
}
|
|
else if (ImGui.IsItemDeactivated())
|
|
{
|
|
// Abort
|
|
HideNewFile();
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
}
|
|
|
|
private void DrawDeleteItemPopup()
|
|
{
|
|
if (_selectedFiles.IsEmpty)
|
|
return;
|
|
|
|
// Always center this window when appearing
|
|
ImGui.Vec2 center = ImGui.GetMainViewport().GetCenter();
|
|
ImGui.SetNextWindowPos(center, .Appearing, ImGui.Vec2(0.5f, 0.5f));
|
|
|
|
// TODO: fix delete popup
|
|
|
|
if (ImGui.BeginPopupModal("Delete?", null, .AlwaysAutoResize))
|
|
{
|
|
if (_selectedFiles.Count == 1)
|
|
{
|
|
Result<TreeNode<AssetNode>> nodeResult = _manager.AssetHierarchy.GetNodeFromPath(_selectedFiles[0]);
|
|
|
|
if (nodeResult case .Ok(TreeNode<AssetNode> node))
|
|
{
|
|
ImGui.Text(
|
|
$"""
|
|
Do you really want to delete "{node->Name}"?
|
|
|
|
|
|
""");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui.Text(
|
|
$"""
|
|
Do you really want to delete {_selectedFiles.Count} directories/assets?
|
|
|
|
|
|
""");
|
|
}
|
|
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.Button("Yes", ImGui.Vec2(120, 0)))
|
|
{
|
|
List<StringView> paths = scope .();
|
|
for (String path in _selectedFiles)
|
|
{
|
|
paths.Add(path);
|
|
}
|
|
|
|
_manager.AssetHierarchy.DeletePathsBackground(paths);
|
|
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui.SetItemDefaultFocus();
|
|
ImGui.SameLine();
|
|
|
|
if (ImGui.Button("Cancel", ImGui.Vec2(120, 0)))
|
|
{
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
}
|
|
|
|
/// Shows the context menu for the selected files and directories.
|
|
private void ShowItemContextMenu()
|
|
{
|
|
bool singleEntry = _selectedFiles.Count == 1;
|
|
|
|
bool isFile = false;
|
|
|
|
List<TreeNode<AssetNode>> entries = new:ScopedAlloc! .(_selectedFiles.Count);
|
|
|
|
_selectedFiles.Select((e) => {
|
|
return _manager.AssetHierarchy.GetNodeFromPath(e);
|
|
}).Where((e) => e case .Ok).Select((e) => e.Get()).ToList(entries);
|
|
|
|
bool sameParent = entries.Select((e) => e.Parent).Distinct().Count() == 1;
|
|
|
|
TreeNode<AssetNode> firstEntry = entries.First();
|
|
TreeNode<AssetNode> parent = sameParent ? entries.First().Parent : null;
|
|
|
|
if (singleEntry)
|
|
{
|
|
isFile = !firstEntry->IsDirectory;
|
|
}
|
|
|
|
if ((singleEntry || sameParent) && ImGui.MenuItem("Show in file browser..."))
|
|
{
|
|
if (singleEntry)
|
|
{
|
|
if (Path.OpenFolderAndSelectItem(firstEntry->Path) case .Err)
|
|
{
|
|
Log.EngineLogger.Error("Failed to show path in file browser.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Path.OpenFolder(parent->Path) case .Err)
|
|
{
|
|
Log.EngineLogger.Error("Failed to show path in file browser.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isFile && ImGui.MenuItem("Open file with..."))
|
|
{
|
|
if (Path.OpenWithDialog(firstEntry->Path) case .Err)
|
|
{
|
|
Log.EngineLogger.Error("Failed to show \"Open with...\" dialog.");
|
|
}
|
|
}
|
|
|
|
if (singleEntry && ImGui.BeginMenu("Copy path"))
|
|
{
|
|
if (ImGui.MenuItem("Full path"))
|
|
{
|
|
ImGui.SetClipboardText(firstEntry->Path);
|
|
}
|
|
|
|
ImGui.AttachTooltip(scope $"Copies the full file path of this asset.\n(\"{firstEntry->Path}\")");
|
|
|
|
if (ImGui.MenuItem("File name"))
|
|
{
|
|
ImGui.SetClipboardText(firstEntry->Name);
|
|
}
|
|
|
|
ImGui.AttachTooltip(scope $"Copies the file name of this asset.\n(\"{firstEntry->Name}\")");
|
|
|
|
if (ImGui.MenuItem("Asset identifier"))
|
|
{
|
|
ImGui.SetClipboardText(firstEntry->Identifier.FullIdentifier.Ptr);
|
|
}
|
|
|
|
ImGui.AttachTooltip(scope $"Copies the identifier of this asset.\n(\"{firstEntry->Identifier.FullIdentifier}\")");
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Cut", "Ctrl+X"))
|
|
{
|
|
CopySelectedFiles(cutFiles: true);
|
|
}
|
|
|
|
if (ImGui.MenuItem("Copy", "Ctrl+C"))
|
|
{
|
|
CopySelectedFiles(cutFiles: false);
|
|
}
|
|
|
|
if (!_filesToCopy.IsEmpty && singleEntry && parent != null && ImGui.MenuItem("Paste", "Ctrl+V"))
|
|
{
|
|
PasteFiles(parent->Path);
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Rename"))
|
|
{
|
|
// Copy the path, just for the rare case that fileOrFolder gets deleted
|
|
_assetToRename.Set(firstEntry->Path);
|
|
|
|
firstEntry->Name.CopyTo(_renameFileNameBuffer);
|
|
}
|
|
|
|
if (ImGui.MenuItem("Delete"))
|
|
{
|
|
_wantsDelete = true;
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (singleEntry && ImGui.MenuItem("Properties..."))
|
|
{
|
|
OpenPropertiesWindow(firstEntry);
|
|
}
|
|
}
|
|
|
|
private void CopySelectedFiles(bool cutFiles)
|
|
{
|
|
_filesToCopy.ClearAndDeleteItems();
|
|
_selectedFiles.Select((file) => new String(file)).ToList(_filesToCopy);
|
|
_cutFiles = cutFiles;
|
|
}
|
|
|
|
private void PasteFiles(StringView targetDirectoryPath)
|
|
{
|
|
BackgroundTask backgroundTask;
|
|
if (_cutFiles)
|
|
{
|
|
backgroundTask = new MoveFilesBackgroundTask(_filesToCopy, targetDirectoryPath);
|
|
_filesToCopy.ClearAndDeleteItems();
|
|
_cutFiles = false;
|
|
}
|
|
else
|
|
{
|
|
backgroundTask = new CopyBackgroundTask(_filesToCopy, targetDirectoryPath);
|
|
}
|
|
backgroundTask.DeleteWhenEnded = true;
|
|
|
|
EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(backgroundTask);
|
|
}
|
|
|
|
private void OpenPropertiesWindow(TreeNode<AssetNode> fileOrFolder)
|
|
{
|
|
AssetHandle? assetHandle = fileOrFolder->AssetFile?.AssetConfig?.AssetHandle;
|
|
|
|
if (assetHandle != null)
|
|
{
|
|
new PropertiesWindow(_editor, .Asset(assetHandle.Value));
|
|
}
|
|
}
|
|
|
|
private void OpenEntry(TreeNode<AssetNode> entry)
|
|
{
|
|
if (entry->IsDirectory)
|
|
{
|
|
_directoryHistory.Navigate(entry->Path);
|
|
_selectedFiles.ClearAndDeleteItems();
|
|
}
|
|
else
|
|
{
|
|
// Special treatment for scripts, open them in Visual Studio.
|
|
if (entry->Path.EndsWith(".cs"))
|
|
{
|
|
Editor.Instance.IdeAdapter.OpenScript(entry->Path);
|
|
}
|
|
else if (Path.OpenFolder(entry->Path) case .Err)
|
|
Log.EngineLogger.Error("Failed to open file.");
|
|
}
|
|
}
|
|
|
|
void DrawNavigationBar()
|
|
{
|
|
String buttonText = scope .(128);
|
|
|
|
void ShowHistoryContext(bool forward)
|
|
{
|
|
if (ImGui.BeginPopupContextItem())
|
|
{
|
|
int startIndex = -1;
|
|
int endIndex = -1;
|
|
|
|
if (forward)
|
|
{
|
|
startIndex = _directoryHistory.CurrentIndex + 1;
|
|
endIndex = _directoryHistory.HistoryLength;
|
|
}
|
|
else
|
|
{
|
|
startIndex = 0;
|
|
endIndex = _directoryHistory.CurrentIndex;
|
|
}
|
|
|
|
for (int i = endIndex - 1; i >= startIndex; i--)
|
|
{
|
|
String path = _directoryHistory.History[i];
|
|
Result<TreeNode<AssetNode>> node = _manager.AssetHierarchy.GetNodeFromPath(path);
|
|
|
|
if (node case .Err)
|
|
continue;
|
|
|
|
buttonText.SetF($"{node.Get()->Name}##{i}");
|
|
|
|
if (ImGui.MenuItem(buttonText))
|
|
{
|
|
NavigateToIndex(i);
|
|
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
}
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
}
|
|
|
|
ImGui.BeginDisabled(!_directoryHistory.CanGoBack);
|
|
|
|
if (ImGui.Button("<"))
|
|
{
|
|
NavigateBack();
|
|
}
|
|
|
|
if (ImGui.IsItemClicked(.Right))
|
|
{
|
|
ImGui.OpenPopup("History");
|
|
}
|
|
|
|
ShowHistoryContext(forward: false);
|
|
|
|
ImGui.AttachTooltip("Back (Alt + Left)");
|
|
|
|
ImGui.EndDisabled();
|
|
|
|
ImGui.SameLine();
|
|
|
|
ImGui.BeginDisabled(!_directoryHistory.CanGoForward);
|
|
|
|
if (ImGui.Button(">"))
|
|
{
|
|
NavigateForward();
|
|
}
|
|
|
|
if (ImGui.IsItemClicked(.Right))
|
|
{
|
|
ImGui.OpenPopup("History");
|
|
}
|
|
|
|
ShowHistoryContext(forward: true);
|
|
|
|
ImGui.AttachTooltip("Forward (Alt + Right)");
|
|
|
|
ImGui.EndDisabled();
|
|
|
|
|
|
ImGui.SameLine();
|
|
|
|
ImGui.BeginDisabled(!CanNavigateUp);
|
|
|
|
if (ImGui.Button("/\\"))
|
|
{
|
|
NavigateUp();
|
|
}
|
|
|
|
ImGui.AttachTooltip("Up (Alt + Up)");
|
|
|
|
ImGui.EndDisabled();
|
|
|
|
List<TreeNode<AssetNode>> path = scope .();
|
|
|
|
TreeNode<AssetNode> walker = _currentDirectoryNode;
|
|
|
|
if (walker != null)
|
|
{
|
|
repeat
|
|
{
|
|
path.AddFront(walker);
|
|
walker = walker.Parent;
|
|
} while (walker != _manager.AssetHierarchy.RootNode);
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
|
|
float2 spacing = (.)ImGui.GetStyle().ItemSpacing;
|
|
spacing.X = 0;
|
|
ImGui.PushScopedStyleVar!(ImGui.TypedStyleVar.ItemSpacing(spacing));
|
|
ImGui.PushScopedStyleVar!(ImGui.TypedStyleVar.FrameRounding(0));
|
|
|
|
void DirectorySelectionContextMenu(TreeNode<AssetNode> parent, ImGui.PopupFlags flags = .MouseButtonRight)
|
|
{
|
|
void ShowDirectories(TreeNode<AssetNode> parent)
|
|
{
|
|
for (TreeNode<AssetNode> node in parent.Children.Where((c) => c->IsDirectory))
|
|
{
|
|
buttonText.SetF($"{node->Name}##{node->Path}");
|
|
|
|
if (node.Children.Any((c) => c->IsDirectory))
|
|
{
|
|
if (ImGui.BeginMenu(buttonText))
|
|
{
|
|
ShowDirectories(node);
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
if (ImGui.IsItemClicked())
|
|
{
|
|
Navigate(node);
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ImGui.MenuItem(buttonText))
|
|
{
|
|
Navigate(node);
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// id == null -> Use ID of last Item
|
|
if (ImGui.BeginPopupContextItem(null, flags))
|
|
{
|
|
ShowDirectories(parent);
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
}
|
|
|
|
for (TreeNode<AssetNode> node in path)
|
|
{
|
|
buttonText.SetF($"{node->Name}##{node->Path}");
|
|
|
|
if (ImGui.Button(buttonText) && node != _currentDirectoryNode)
|
|
{
|
|
Navigate(node);
|
|
}
|
|
|
|
DirectorySelectionContextMenu(node.Parent);
|
|
|
|
ImGui.SameLine();
|
|
|
|
ImGui.Text("/");
|
|
|
|
ImGui.SameLine();
|
|
}
|
|
|
|
if (_currentDirectoryNode?.Children.Any((c) => c->IsDirectory) ?? false)
|
|
{
|
|
ImGui.Button("...");
|
|
|
|
DirectorySelectionContextMenu(_currentDirectoryNode, .MouseButtonLeft);
|
|
}
|
|
|
|
ImGui.NewLine();
|
|
}
|
|
}
|
|
} |