mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
More content browser stuff
- Seperate AssetHierarchy from EditorContentManager - Make files selectable and context menu
This commit is contained in:
@@ -5,6 +5,7 @@ using GlitchyEngine.Collections;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using GlitchyEngine.Renderer;
|
using GlitchyEngine.Renderer;
|
||||||
using GlitchyEngine.Math;
|
using GlitchyEngine.Math;
|
||||||
|
using GlitchyEngine;
|
||||||
|
|
||||||
namespace GlitchyEditor.EditWindows
|
namespace GlitchyEditor.EditWindows
|
||||||
{
|
{
|
||||||
@@ -15,8 +16,9 @@ namespace GlitchyEditor.EditWindows
|
|||||||
// TODO: Get from project
|
// TODO: Get from project
|
||||||
const String ContentDirectory = "./content";
|
const String ContentDirectory = "./content";
|
||||||
|
|
||||||
//private String _currentDirectory ~ delete _;
|
private append String _currentDirectory = .();
|
||||||
private TreeNode<AssetNode> _currentDirectoryNode;
|
|
||||||
|
private append String _selectedFile = .();
|
||||||
|
|
||||||
public static SubTexture2D s_FolderTexture;
|
public static SubTexture2D s_FolderTexture;
|
||||||
public static SubTexture2D s_FileTexture;
|
public static SubTexture2D s_FileTexture;
|
||||||
@@ -31,8 +33,12 @@ namespace GlitchyEditor.EditWindows
|
|||||||
protected override void InternalShow()
|
protected override void InternalShow()
|
||||||
{
|
{
|
||||||
_manager.Update();
|
_manager.Update();
|
||||||
|
|
||||||
_currentDirectoryNode ??= _manager._assetHierarchy;
|
// Make sure we are in an existing directory.
|
||||||
|
if (!_manager.AssetHierarchy.FileExists(_currentDirectory))
|
||||||
|
{
|
||||||
|
_currentDirectory.Set(_manager.ContentDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
if(!ImGui.Begin("Content Browser", &_open, .None))
|
if(!ImGui.Begin("Content Browser", &_open, .None))
|
||||||
{
|
{
|
||||||
@@ -40,6 +46,13 @@ namespace GlitchyEditor.EditWindows
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context menu when clicking on the background.
|
||||||
|
if (ImGui.BeginPopupContextWindow())
|
||||||
|
{
|
||||||
|
ShowCurrentFolderContextMenu();
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.Columns(2);
|
ImGui.Columns(2);
|
||||||
|
|
||||||
DrawDirectorySideBar();
|
DrawDirectorySideBar();
|
||||||
@@ -53,14 +66,26 @@ namespace GlitchyEditor.EditWindows
|
|||||||
ImGui.End();
|
ImGui.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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..."))
|
||||||
|
{
|
||||||
|
Path.OpenFolder(_currentDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders a sidebar that shows a tree of all directories in the asset folder.
|
||||||
private void DrawDirectorySideBar()
|
private void DrawDirectorySideBar()
|
||||||
{
|
{
|
||||||
for(var child in _manager._assetHierarchy.Children)
|
for(var child in _manager.AssetHierarchy.[Friend]_assetHierarchy.Children)
|
||||||
{
|
{
|
||||||
ImGuiPrintEntityTree(child);
|
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)
|
private void ImGuiPrintEntityTree(TreeNode<AssetNode> tree)
|
||||||
{
|
{
|
||||||
if (!tree->IsDirectory)
|
if (!tree->IsDirectory)
|
||||||
@@ -73,7 +98,7 @@ namespace GlitchyEditor.EditWindows
|
|||||||
if(tree.Children.Count == 0)
|
if(tree.Children.Count == 0)
|
||||||
flags |= .Leaf;
|
flags |= .Leaf;
|
||||||
|
|
||||||
if (tree == _currentDirectoryNode)
|
if (tree->Path == _currentDirectory)
|
||||||
{
|
{
|
||||||
flags |= .Selected;
|
flags |= .Selected;
|
||||||
}
|
}
|
||||||
@@ -82,11 +107,7 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
if (ImGui.IsItemClicked(.Left))
|
if (ImGui.IsItemClicked(.Left))
|
||||||
{
|
{
|
||||||
/*if (_currentDirectory != null)
|
_currentDirectory.Set(tree->Path);
|
||||||
delete _currentDirectory;
|
|
||||||
|
|
||||||
_currentDirectory = new String(tree.Value.Path);*/
|
|
||||||
_currentDirectoryNode = tree;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isOpen)
|
if(isOpen)
|
||||||
@@ -104,44 +125,70 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
const Vector2 padding = .(24, 24);
|
const Vector2 padding = .(24, 24);
|
||||||
|
|
||||||
|
/// Renders the contents of _currentDirectory.
|
||||||
private void DrawCurrentDirectory()
|
private void DrawCurrentDirectory()
|
||||||
{
|
{
|
||||||
if (_currentDirectoryNode == null)
|
if (_currentDirectory.IsEmpty)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGui.Style* style = ImGui.GetStyle();
|
ImGui.Style* style = ImGui.GetStyle();
|
||||||
|
|
||||||
// TODO: crashes when _currentDirectoryNode was deleted, obviously
|
|
||||||
|
|
||||||
float window_visible_x2 = ImGui.GetWindowPos().x + ImGui.GetWindowContentRegionMax().x;
|
float window_visible_x2 = ImGui.GetWindowPos().x + ImGui.GetWindowContentRegionMax().x;
|
||||||
for (var entry in _currentDirectoryNode.Children)
|
|
||||||
|
// Get the node of the current directory.
|
||||||
|
var currentDirectoryNode = _manager.AssetHierarchy.GetNodeFromPath(_currentDirectory);
|
||||||
|
|
||||||
|
if (currentDirectoryNode case .Err)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"No node exists for {_currentDirectory}.");
|
||||||
|
ImGui.TextUnformatted("Failed to display contents of directory.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var entry in currentDirectoryNode->Children)
|
||||||
{
|
{
|
||||||
ImGui.PushID(entry->Name);
|
ImGui.PushID(entry->Name);
|
||||||
|
|
||||||
DrawDirectoryItem(entry);
|
DrawDirectoryItem(entry);
|
||||||
|
|
||||||
float last_button_x2 = ImGui.GetItemRectMax().x;
|
// X-Coordinate of the right side of the current entry.
|
||||||
float next_button_x2 = last_button_x2 + style.ItemSpacing.x + DirectoryItemSize.X; // Expected position if next button was on same line
|
float currentButtonRight = ImGui.GetItemRectMax().x;
|
||||||
if (entry != _currentDirectoryNode.Children.Back && next_button_x2 < window_visible_x2)
|
// Expected right-Coordinate if next entry was on the same line.
|
||||||
|
float expectedButtonRight = currentButtonRight + style.ItemSpacing.x + DirectoryItemSize.X;
|
||||||
|
|
||||||
|
// If we aren't the last entry and the next button won't fit on the same line we start a new line.
|
||||||
|
if (entry != currentDirectoryNode->Children.Back && expectedButtonRight < window_visible_x2)
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
ImGui.PopID();
|
ImGui.PopID();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders the button for the given directory item.
|
||||||
private void DrawDirectoryItem(TreeNode<AssetNode> entry)
|
private void DrawDirectoryItem(TreeNode<AssetNode> entry)
|
||||||
{
|
{
|
||||||
ImGui.BeginChild("item", (.)DirectoryItemSize);
|
ImGui.BeginChild("item", (.)DirectoryItemSize);
|
||||||
|
|
||||||
|
if (entry->Path == _selectedFile)
|
||||||
|
{
|
||||||
|
var color = ImGui.GetStyleColorVec4(.ButtonHovered);
|
||||||
|
ImGui.PushStyleColor(.Button, *color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: preview images
|
||||||
SubTexture2D image = entry->IsDirectory ? s_FolderTexture : s_FileTexture;
|
SubTexture2D image = entry->IsDirectory ? s_FolderTexture : s_FileTexture;
|
||||||
|
|
||||||
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
|
|
||||||
|
|
||||||
ImGui.ImageButton(image, (.)(DirectoryItemSize - padding));
|
ImGui.ImageButton(image, (.)(DirectoryItemSize - padding));
|
||||||
|
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
if (ImGui.BeginDragDropSource())
|
if (ImGui.BeginDragDropSource())
|
||||||
{
|
{
|
||||||
String fullpath = scope $"{_currentDirectoryNode->Path}{Path.DirectorySeparatorChar}{entry->Name}";
|
String fullpath = scope String(entry->Path);
|
||||||
|
|
||||||
// TODO: this is dirty
|
// TODO: this is dirty
|
||||||
if (fullpath.StartsWith(ContentDirectory, .OrdinalIgnoreCase))
|
if (fullpath.StartsWith(ContentDirectory, .OrdinalIgnoreCase))
|
||||||
@@ -152,15 +199,27 @@ namespace GlitchyEditor.EditWindows
|
|||||||
ImGui.EndDragDropSource();
|
ImGui.EndDragDropSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(.Left))
|
||||||
|
{
|
||||||
|
if (_selectedFile != entry->Path)
|
||||||
|
{
|
||||||
|
_selectedFile.Set(entry->Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(.Left))
|
if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(.Left))
|
||||||
{
|
{
|
||||||
EntryDoubleClicked(entry);
|
EntryDoubleClicked(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
|
|
||||||
ImGui.TextUnformatted(entry->Name);
|
ImGui.TextUnformatted(entry->Name);
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupContextWindow())
|
||||||
|
{
|
||||||
|
ShowItemContextMenu(entry);
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
if (entry->SubAssets?.Count > 0)
|
if (entry->SubAssets?.Count > 0)
|
||||||
{
|
{
|
||||||
// Button for revealing sub assets (e.g. Meshes in 3D-Model)
|
// Button for revealing sub assets (e.g. Meshes in 3D-Model)
|
||||||
@@ -178,15 +237,9 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
if (ImGui.BeginDragDropSource())
|
if (ImGui.BeginDragDropSource())
|
||||||
{
|
{
|
||||||
String fullpath = Path.InternalCombine(.. scope String(), _currentDirectoryNode->Path, entry->Name);
|
String fullpath = scope String(entry->Path);
|
||||||
fullpath.AppendF($"#{subAsset.Name}");
|
fullpath.AppendF($"#{subAsset.Name}");
|
||||||
//scope $"{_currentDirectoryNode->Path}{Path.DirectorySeparatorChar}{entry->Name}";
|
|
||||||
//String fullpath = scope $"{subAsset.Asset.Path}#{subAsset.Name}";
|
|
||||||
|
|
||||||
// TODO: this is dirty
|
|
||||||
//if (fullpath.StartsWith(ContentDirectory, .OrdinalIgnoreCase))
|
|
||||||
// fullpath.Remove(0, ContentDirectory.Length);
|
|
||||||
|
|
||||||
ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once);
|
ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once);
|
||||||
|
|
||||||
ImGui.EndDragDropSource();
|
ImGui.EndDragDropSource();
|
||||||
@@ -195,15 +248,66 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeleteItemPopup(entry);
|
||||||
|
|
||||||
ImGui.EndChild();
|
ImGui.EndChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DeleteItemPopup(TreeNode<AssetNode> fileOrFolder)
|
||||||
|
{
|
||||||
|
// 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))
|
||||||
|
{
|
||||||
|
ImGui.Text($"""
|
||||||
|
Delete "{fileOrFolder->Name}"?
|
||||||
|
|
||||||
|
|
||||||
|
""");
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (ImGui.Button("Yes", ImGui.Vec2(120, 0)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Cancel", ImGui.Vec2(120, 0)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows the context menu for the given file/folder.
|
||||||
|
private void ShowItemContextMenu(TreeNode<AssetNode> fileOrFolder)
|
||||||
|
{
|
||||||
|
if (ImGui.MenuItem("Show in file browser..."))
|
||||||
|
{
|
||||||
|
Path.OpenFolderAndSelectItem(fileOrFolder->Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Delete"))
|
||||||
|
{
|
||||||
|
ImGui.OpenPopup("Delete?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void EntryDoubleClicked(TreeNode<AssetNode> entry)
|
private void EntryDoubleClicked(TreeNode<AssetNode> entry)
|
||||||
{
|
{
|
||||||
if (entry->IsDirectory)
|
if (entry->IsDirectory)
|
||||||
{
|
{
|
||||||
_currentDirectoryNode = entry;
|
_currentDirectory.Set(entry->Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,93 +99,51 @@ public class Asset
|
|||||||
public Texture2D PreviewImage ~ _?.ReleaseRef();
|
public Texture2D PreviewImage ~ _?.ReleaseRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditorContentManager : IContentManager
|
class AssetHierarchy
|
||||||
{
|
{
|
||||||
// TODO: Get from workspace
|
|
||||||
//const String ContentDirectory = "./content";
|
|
||||||
|
|
||||||
FileSystemWatcher fsw ~ {
|
FileSystemWatcher fsw ~ {
|
||||||
_.StopRaisingEvents();
|
_.StopRaisingEvents();
|
||||||
delete _;
|
delete _;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool _fileSystemDirty = false;
|
bool _fileSystemDirty = false;
|
||||||
|
|
||||||
internal TreeNode<AssetNode> _assetHierarchy = null ~ DeleteTreeAndChildren!(_);
|
internal TreeNode<AssetNode> _assetHierarchy = null ~ DeleteTreeAndChildren!(_);
|
||||||
|
private append Dictionary<StringView, TreeNode<AssetNode>> _pathToAssetNode = .();
|
||||||
|
|
||||||
private append String _contentDirectory = .();
|
private append String _contentDirectory = .();
|
||||||
|
|
||||||
public StringView ContentDirectory => _contentDirectory;
|
private EditorContentManager _contentManager;
|
||||||
|
|
||||||
private append List<String> _identifiers = .() ~ _.ClearAndDeleteItems();
|
|
||||||
|
|
||||||
private append Dictionary<StringView, IRefCounted> _loadedAssets = .(); // Check if all resources are unloaded
|
public StringView ContentDirectory
|
||||||
|
|
||||||
public this()
|
|
||||||
{
|
{
|
||||||
|
get => _contentDirectory;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_contentDirectory.Clear();
|
||||||
|
_contentDirectory.Append(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(EditorContentManager contentManager)
|
||||||
|
{
|
||||||
|
_contentManager = contentManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetContentDirectory(StringView contentDirectory)
|
public void SetContentDirectory(StringView contentDirectory)
|
||||||
{
|
{
|
||||||
_contentDirectory.Clear();
|
ContentDirectory = contentDirectory;
|
||||||
_contentDirectory.Append(contentDirectory);
|
|
||||||
_fileSystemDirty = true;
|
_fileSystemDirty = true;
|
||||||
|
|
||||||
SetupFileSystemWatcher();
|
SetupFileSystemWatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TreeNode<AssetNode> FileFileNode(StringView filePath)
|
|
||||||
{
|
|
||||||
//String relativeFilePath = scope String(filePath.Length);
|
|
||||||
|
|
||||||
//Path.GetRelativePath(filePath, ContentDirectory, relativeFilePath);
|
|
||||||
|
|
||||||
// Having a Path -> AssetNode dictionary would make this a lot easier!
|
|
||||||
|
|
||||||
TreeNode<AssetNode> walker = _assetHierarchy;
|
|
||||||
|
|
||||||
Log.EngineLogger.AssertDebug(filePath.StartsWith(walker->Path), "filePath not in walker :(");
|
|
||||||
|
|
||||||
Walking: while (true)
|
|
||||||
{
|
|
||||||
for (TreeNode<AssetNode> child in walker.Children)
|
|
||||||
{
|
|
||||||
if (filePath.StartsWith(child->Path))
|
|
||||||
{
|
|
||||||
walker = child;
|
|
||||||
|
|
||||||
if (walker->Path == filePath)
|
|
||||||
return walker;
|
|
||||||
|
|
||||||
continue Walking;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we make it here, no child matched
|
|
||||||
Runtime.FatalError("Failed to find treeNode for given path");
|
|
||||||
//Log.EngineLogger.Assert(false, "Failed to find ")
|
|
||||||
}
|
|
||||||
|
|
||||||
/*// Check whether the new file is a directory of a file.
|
|
||||||
if (Directory.Exists(filePath))
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (File.Exists(filePath))
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.EngineLogger.Assert(false, scope $"The given path \"{filePath}\" doesn't exist.");
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes the FSW for the current ContentDirectory and registers the events.
|
/// Initializes the FSW for the current ContentDirectory and registers the events.
|
||||||
private void SetupFileSystemWatcher()
|
private void SetupFileSystemWatcher()
|
||||||
{
|
{
|
||||||
delete fsw;
|
delete fsw;
|
||||||
fsw = new FileSystemWatcher(ContentDirectory);
|
fsw = new FileSystemWatcher(_contentDirectory);
|
||||||
fsw.IncludeSubdirectories = true;
|
fsw.IncludeSubdirectories = true;
|
||||||
|
|
||||||
fsw.OnChanged.Add(new (filename) => {
|
fsw.OnChanged.Add(new (filename) => {
|
||||||
@@ -208,15 +166,40 @@ class EditorContentManager : IContentManager
|
|||||||
_fileSystemDirty = true;
|
_fileSystemDirty = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
fsw.OnRenamed.Add(new (newName, oldName) => {
|
fsw.OnRenamed.Add(new (oldName, newName) => {
|
||||||
Log.EngineLogger.Trace($"File renamed (From \"{oldName}\" to \"{newName}\")");
|
Log.EngineLogger.Trace($"File renamed (From \"{oldName}\" to \"{newName}\")");
|
||||||
|
|
||||||
_fileSystemDirty = true;
|
_fileSystemDirty = true;
|
||||||
|
/*String contentFilePath = scope String();
|
||||||
|
|
||||||
|
Path.InternalCombine(contentFilePath, ContentDirectory, oldName);
|
||||||
|
|
||||||
|
//_fileSystemDirty = true;
|
||||||
|
TreeNode<AssetNode> fileNode = GetNodeFromPath(contentFilePath);
|
||||||
|
fileNode->*/
|
||||||
});
|
});
|
||||||
|
|
||||||
fsw.StartRaisingEvents();
|
fsw.StartRaisingEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the tree node for the given filePath or null, if the file/directory doesn't exist.
|
||||||
|
/// @param filePath the path for which to return the tree node.
|
||||||
|
/// @remarks Do not hold a reference to the TreeNode because it can become invalid when the file hierarchy changes.
|
||||||
|
public Result<TreeNode<AssetNode>> GetNodeFromPath(StringView filePath)
|
||||||
|
{
|
||||||
|
if (_pathToAssetNode.TryGetValue(filePath, let treeNode))
|
||||||
|
{
|
||||||
|
return treeNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FileExists(StringView filePath)
|
||||||
|
{
|
||||||
|
return _pathToAssetNode.ContainsKey(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
// TODO: do we really need to do this in the update loop?
|
// TODO: do we really need to do this in the update loop?
|
||||||
@@ -225,14 +208,7 @@ class EditorContentManager : IContentManager
|
|||||||
UpdateFiles();
|
UpdateFiles();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAssetLoader GetDefaultAssetLoader(StringView fileExtension)
|
|
||||||
{
|
|
||||||
if (_defaultAssetLoaders.TryGetValue(fileExtension, let value))
|
|
||||||
return value;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rebuilds the asset file hierarchy.
|
/// Rebuilds the asset file hierarchy.
|
||||||
private void UpdateFiles()
|
private void UpdateFiles()
|
||||||
@@ -248,11 +224,13 @@ class EditorContentManager : IContentManager
|
|||||||
_assetHierarchy->Name = new String("Content");
|
_assetHierarchy->Name = new String("Content");
|
||||||
|
|
||||||
Log.EngineLogger.Trace($"Created directory node for: \"{_assetHierarchy->Path}\"");
|
Log.EngineLogger.Trace($"Created directory node for: \"{_assetHierarchy->Path}\"");
|
||||||
|
|
||||||
|
_pathToAssetNode.Add(ContentDirectory, _assetHierarchy);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleFile(AssetNode node)
|
void HandleFile(AssetNode node)
|
||||||
{
|
{
|
||||||
node.AssetFile = new AssetFile(this, node.Path, node.IsDirectory);
|
node.AssetFile = new AssetFile(_contentManager, node.Path, node.IsDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the files that belong to the given directory and adds them to the tree.
|
/// Determines the files that belong to the given directory and adds them to the tree.
|
||||||
@@ -288,7 +266,8 @@ class EditorContentManager : IContentManager
|
|||||||
assetNode.IsDirectory = false;
|
assetNode.IsDirectory = false;
|
||||||
|
|
||||||
treeNode = directory.AddChild(assetNode);
|
treeNode = directory.AddChild(assetNode);
|
||||||
|
_pathToAssetNode.Add(assetNode.Path, treeNode);
|
||||||
|
|
||||||
//GrabSubAssets(node);
|
//GrabSubAssets(node);
|
||||||
HandleFile(treeNode.Value);
|
HandleFile(treeNode.Value);
|
||||||
|
|
||||||
@@ -299,6 +278,17 @@ class EditorContentManager : IContentManager
|
|||||||
|
|
||||||
void RemoveOrphanedEntries(TreeNode<AssetNode> node)
|
void RemoveOrphanedEntries(TreeNode<AssetNode> node)
|
||||||
{
|
{
|
||||||
|
/// Removes the node and its children from _pathToAssetNode
|
||||||
|
void RemoveSubtree(TreeNode<AssetNode> tree)
|
||||||
|
{
|
||||||
|
_pathToAssetNode.Remove(tree->Path);
|
||||||
|
|
||||||
|
for (var child in tree.Children)
|
||||||
|
{
|
||||||
|
RemoveSubtree(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (TreeNode<AssetNode> child in node.Children)
|
for (TreeNode<AssetNode> child in node.Children)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(child->Path) && !File.Exists(child->Path))
|
if (!Directory.Exists(child->Path) && !File.Exists(child->Path))
|
||||||
@@ -307,6 +297,8 @@ class EditorContentManager : IContentManager
|
|||||||
|
|
||||||
@child.Remove();
|
@child.Remove();
|
||||||
|
|
||||||
|
RemoveSubtree(child);
|
||||||
|
|
||||||
DeleteTreeAndChildren!(child);
|
DeleteTreeAndChildren!(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +319,9 @@ class EditorContentManager : IContentManager
|
|||||||
assetNode.Name = new String();
|
assetNode.Name = new String();
|
||||||
assetNode.IsDirectory = true;
|
assetNode.IsDirectory = true;
|
||||||
Path.GetFileName(assetNode.Path, assetNode.Name);
|
Path.GetFileName(assetNode.Path, assetNode.Name);
|
||||||
|
|
||||||
treeNode = parentNode.AddChild(assetNode);
|
treeNode = parentNode.AddChild(assetNode);
|
||||||
|
_pathToAssetNode.Add(assetNode.Path, treeNode);
|
||||||
|
|
||||||
Log.EngineLogger.Trace($"Created directory node for: \"{assetNode.Path}\"");
|
Log.EngineLogger.Trace($"Created directory node for: \"{assetNode.Path}\"");
|
||||||
}
|
}
|
||||||
@@ -364,7 +358,49 @@ class EditorContentManager : IContentManager
|
|||||||
|
|
||||||
_fileSystemDirty = false;
|
_fileSystemDirty = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditorContentManager : IContentManager
|
||||||
|
{
|
||||||
|
// TODO: Get from workspace
|
||||||
|
//const String ContentDirectory = "./content";
|
||||||
|
|
||||||
|
private append String _contentDirectory = .();
|
||||||
|
|
||||||
|
public StringView ContentDirectory => _contentDirectory;
|
||||||
|
|
||||||
|
private append List<String> _identifiers = .() ~ _.ClearAndDeleteItems();
|
||||||
|
|
||||||
|
private append Dictionary<StringView, IRefCounted> _loadedAssets = .(); // Check if all resources are unloaded
|
||||||
|
|
||||||
|
private append AssetHierarchy _assetHierarchy = .(this);
|
||||||
|
|
||||||
|
public AssetHierarchy AssetHierarchy => _assetHierarchy;
|
||||||
|
|
||||||
|
public this()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContentDirectory(StringView contentDirectory)
|
||||||
|
{
|
||||||
|
_contentDirectory.Clear();
|
||||||
|
_contentDirectory.Append(contentDirectory);
|
||||||
|
|
||||||
|
_assetHierarchy.SetContentDirectory(contentDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
_assetHierarchy.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAssetLoader GetDefaultAssetLoader(StringView fileExtension)
|
||||||
|
{
|
||||||
|
if (_defaultAssetLoaders.TryGetValue(fileExtension, let value))
|
||||||
|
return value;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
private append List<String> _supportedExtensions = .() ~ ClearAndDeleteItems!(_);
|
private append List<String> _supportedExtensions = .() ~ ClearAndDeleteItems!(_);
|
||||||
private append List<IAssetLoader> _assetLoaders = .() ~ ClearAndDeleteItems!(_);
|
private append List<IAssetLoader> _assetLoaders = .() ~ ClearAndDeleteItems!(_);
|
||||||
private append Dictionary<StringView, IAssetLoader> _defaultAssetLoaders = .();
|
private append Dictionary<StringView, IAssetLoader> _defaultAssetLoaders = .();
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Core;
|
||||||
|
|
||||||
|
// TODO: make usable
|
||||||
|
class FilePath : IHashable
|
||||||
|
{
|
||||||
|
append String _path = .();
|
||||||
|
|
||||||
|
public bool IsRooted => Path.IsPathRooted(_path);
|
||||||
|
|
||||||
|
public this()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(StringView path)
|
||||||
|
{
|
||||||
|
Set(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator StringView(FilePath filePath) => filePath._path;
|
||||||
|
|
||||||
|
/// @param fixDirectorySeperators If true all alternative directory seperators will be replaced by the primary seperator.
|
||||||
|
/// @param resolveRelativeDirectories If true relative directories ('.' and '..') will be removed from the path.
|
||||||
|
public enum CanonicalizationFlags
|
||||||
|
{
|
||||||
|
FixDirectorySeperators = 1,
|
||||||
|
ResolveRelativeDirectories = _ << 1,
|
||||||
|
MakeFullPath = _ << 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(StringView path, CanonicalizationFlags canonicalizationFlags = .FixDirectorySeperators | .ResolveRelativeDirectories)
|
||||||
|
{
|
||||||
|
_path.Append(path);
|
||||||
|
Canonicalize(canonicalizationFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the path to a canonicalized path.
|
||||||
|
public void Canonicalize(CanonicalizationFlags canonicalizationFlags = .FixDirectorySeperators | .ResolveRelativeDirectories)
|
||||||
|
{
|
||||||
|
if (canonicalizationFlags.HasFlag(.FixDirectorySeperators))
|
||||||
|
{
|
||||||
|
FixDirectorySeperators();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canonicalizationFlags.HasFlag(.ResolveRelativeDirectories))
|
||||||
|
{
|
||||||
|
ResolveRelativeDirectories();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canonicalizationFlags.HasFlag(.MakeFullPath))
|
||||||
|
{
|
||||||
|
MakeFullPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MakeFullPath()
|
||||||
|
{
|
||||||
|
if (IsRooted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String buffer = scope String(Path.[Friend]MaxPath);
|
||||||
|
|
||||||
|
Path.GetFullPath(_path, buffer);
|
||||||
|
|
||||||
|
_path..Clear().Append(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FixDirectorySeperators()
|
||||||
|
{
|
||||||
|
_path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResolveRelativeDirectories()
|
||||||
|
{
|
||||||
|
// find .
|
||||||
|
// find entire entry name
|
||||||
|
// remove entry name (if its only .)
|
||||||
|
|
||||||
|
/*for (char32 c in _path.DecodedChars)
|
||||||
|
{
|
||||||
|
if (c == '.')
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (StringView component in _path.Split(Path.DirectorySeparatorChar))
|
||||||
|
{
|
||||||
|
if (component == ".")
|
||||||
|
{
|
||||||
|
// . can be removed without replacement
|
||||||
|
}
|
||||||
|
else if (component == "..")
|
||||||
|
{
|
||||||
|
// .. can only be removed when not at the start of after another ..
|
||||||
|
|
||||||
|
// e.g. "../foo" and "../../foo" can't be changed
|
||||||
|
// but "foo/.." can become "foo"
|
||||||
|
// "foo/../.." can become ".."
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Append(StringView newPath)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode() => _path.GetHashCode();
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
namespace System.IO;
|
||||||
|
|
||||||
|
extension Path
|
||||||
|
{
|
||||||
|
/// Opens the file browser and selects the specified file.
|
||||||
|
/// @param path The path of the file to select.
|
||||||
|
public static void OpenFolderAndSelectItem(String path)
|
||||||
|
{
|
||||||
|
String fullPath = scope String(256);
|
||||||
|
Path.GetFullPath(path, fullPath);
|
||||||
|
|
||||||
|
#if BF_PLATFORM_WINDOWS
|
||||||
|
ProcessStartInfo processInfo = scope .();
|
||||||
|
processInfo.SetFileNameAndArguments(scope $"explorer /select,\"{fullPath}\"");
|
||||||
|
|
||||||
|
scope SpawnedProcess().Start(processInfo);
|
||||||
|
#else
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the file browser in the given directory.
|
||||||
|
/// @param directory The directory to show in the file browser.
|
||||||
|
public static void OpenFolder(String directory)
|
||||||
|
{
|
||||||
|
String fullPath = scope String(256);
|
||||||
|
Path.GetFullPath(directory, fullPath);
|
||||||
|
|
||||||
|
#if BF_PLATFORM_WINDOWS
|
||||||
|
ProcessStartInfo processInfo = scope .();
|
||||||
|
processInfo.SetFileNameAndArguments(scope $"explorer \"{fullPath}\"");
|
||||||
|
|
||||||
|
scope SpawnedProcess().Start(processInfo);
|
||||||
|
#else
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user