diff --git a/docs/plan/editor-core-refactor-plan.md b/docs/plan/editor-core-refactor-plan.md index 91501665..03315ef3 100644 --- a/docs/plan/editor-core-refactor-plan.md +++ b/docs/plan/editor-core-refactor-plan.md @@ -39,12 +39,16 @@ The app layer has a good directory vocabulary but weak enforcement: from `editor/app/Features`. - `editor/app/Features` also includes `Composition/EditorContext.h` and `Composition/EditorPanelIds.h`, creating a composition/feature cycle. -- `editor/app/Project` depends on `Features/Project/ProjectBrowserModel.h`, - which makes a service layer depend on a feature UI model. - Win32 and D3D12 currently know about each other through concrete headers. - App-side tests exist but are not consistently wired into CMake; some are stale and reference removed headers. +Completed boundary cuts: + +- Project service ownership has moved to `editor/app/Services/Project`. + `EditorProjectRuntime` depends on the service-owned `ProjectBrowserModel`, + and `Features/Project/ProjectPanel` owns the widget-tree projection. + The root issue is not the existence of a single executable target by itself. The root issue is that shared app contracts are stored inside concrete layer directories, and CMake exposes the whole `editor/app` include root to all app @@ -267,13 +271,14 @@ After `XCEditorCore` exists, remove remaining service-to-feature dependencies. ### Project -Current problem: +Completed: ```text -Project/EditorProjectRuntime.h -> Features/Project/ProjectBrowserModel.h +Services/Project/EditorProjectRuntime.h -> Services/Project/ProjectBrowserModel.h +Features/Project/ProjectPanel.h -> Services/Project/EditorProjectRuntime.h ``` -Target: +Current shape: ```text Services/Project/ProjectBrowserModel.h @@ -281,9 +286,10 @@ Services/Project/EditorProjectRuntime.h Features/Project/ProjectPanel.h ``` -The browser model can be owned by project services if it is truly the domain -model. If it is panel-specific presentation state, split it into a service -model and a panel view model. +`ProjectBrowserModel` is now service-owned domain/project state. It exposes +folder, asset, and breadcrumb data, while `ProjectPanel` converts folders into +`UIEditorTreeViewItem` presentation data. Keep future project filesystem and +command-target behavior in `Services/Project`, not under `Features/Project`. ### Scene diff --git a/editor/AGENTS.md b/editor/AGENTS.md index c0d4dad8..5f44fb82 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -60,8 +60,11 @@ change. workspace-panel runtime interfaces live under `app/Core/WorkspacePanels` so `app/Composition/**` can depend on the contract without depending on the feature registry. -- `app/Project/`, `app/Scene/`, and `app/State/` hold application services that - panels should use instead of owning global state themselves. +- `app/Services/Project/`, `app/Scene/`, and `app/State/` hold application + services that panels should use instead of owning global state themselves. + `ProjectBrowserModel` and `EditorProjectRuntime` live under + `app/Services/Project`; project panels adapt those service models into + widget-specific tree/list presentation data. ## Layering @@ -211,6 +214,10 @@ inside pure shell/widget code. event formatting lives behind the feature registry, while this sync layer owns selection-backed session state, scene-open requests, status updates, and runtime trace entries. +- Project services must not include `app/Features/**`. Keep project filesystem + state and command-target resolution in `app/Services/Project`; keep + `UIEditorTreeViewItem` and other widget presentation projections in + `Features/Project/ProjectPanel`. ## Viewport Rendering @@ -305,6 +312,8 @@ inside pure shell/widget code. - `editor/CMakeLists.txt` - `editor/app/Bootstrap/Application.cpp` - `editor/app/Composition/EditorContext.cpp` +- `editor/app/Services/Project/EditorProjectRuntime.cpp` +- `editor/app/Services/Project/ProjectBrowserModel.cpp` - `editor/app/Composition/EditorShellRuntime.cpp` - `editor/app/Composition/EditorShellAssetBuilder.cpp` - `editor/app/Windowing/EditorWindowManager.cpp` @@ -355,3 +364,7 @@ ownership rule. under `app/Core/Panels`, and the workspace-panel runtime interface lives under `app/Core/WorkspacePanels`. `Application` is the current composition root for the concrete workspace panel factory. +- Project service ownership moved out of `app/Features`: `ProjectBrowserModel` + and `EditorProjectRuntime` now live under `app/Services/Project`, and the + Project panel owns the widget-tree projection instead of making the service + model depend on `UIEditorTreeViewItem`. diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 9d15f880..7bed3ad4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -251,7 +251,6 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Features/Inspector/Components/InspectorComponentEditorRegistry.cpp app/Features/Inspector/Components/TransformInspectorComponentEditor.cpp app/Features/Project/ProjectPanel.cpp - app/Features/Project/ProjectBrowserModel.cpp app/Features/Scene/SceneViewportTransformGizmo.cpp app/Features/Scene/SceneViewportTransformGizmoSupport.cpp app/Features/Scene/SceneViewportSceneOverlay.cpp @@ -275,8 +274,9 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ) set(XCUI_EDITOR_APP_SUPPORT_SOURCES - app/Project/EditorProjectRuntime.cpp app/Scene/EditorSceneRuntime.cpp + app/Services/Project/EditorProjectRuntime.cpp + app/Services/Project/ProjectBrowserModel.cpp app/Support/EmbeddedPngLoader.cpp app/Scene/EditorSceneBridge.cpp ) diff --git a/editor/app/Composition/EditorContext.h b/editor/app/Composition/EditorContext.h index 14b5e1ec..a744da92 100644 --- a/editor/app/Composition/EditorContext.h +++ b/editor/app/Composition/EditorContext.h @@ -1,8 +1,8 @@ #pragma once #include "Composition/EditorShellVariant.h" -#include "Project/EditorProjectRuntime.h" #include "Scene/EditorSceneRuntime.h" +#include "Services/Project/EditorProjectRuntime.h" #include "UtilityWindows/EditorUtilityWindowKind.h" #include "Commands/EditorHostCommandBridge.h" diff --git a/editor/app/Features/Project/ProjectPanel.cpp b/editor/app/Features/Project/ProjectPanel.cpp index 013387a4..2f473cea 100644 --- a/editor/app/Features/Project/ProjectPanel.cpp +++ b/editor/app/Features/Project/ProjectPanel.cpp @@ -7,7 +7,7 @@ #include #include #include "System/SystemInteractionService.h" -#include "Project/EditorProjectRuntime.h" +#include "Services/Project/EditorProjectRuntime.h" #include "State/EditorCommandFocusService.h" #include #include @@ -372,10 +372,18 @@ void ProjectPanel::RebuildWindowTreeItems() { return; } - m_windowTreeItems = GetBrowserModel().GetTreeItems(); const UITextureHandle folderIcon = ResolveFolderIcon(m_icons); - for (Widgets::UIEditorTreeViewItem& item : m_windowTreeItems) { + const std::vector& folderEntries = + GetBrowserModel().GetFolderEntries(); + m_windowTreeItems.reserve(folderEntries.size()); + for (const FolderEntry& folderEntry : folderEntries) { + Widgets::UIEditorTreeViewItem item = {}; + item.itemId = folderEntry.itemId; + item.label = folderEntry.label; + item.depth = folderEntry.depth; + item.forceLeaf = !folderEntry.hasChildFolders; item.leadingIcon = folderIcon; + m_windowTreeItems.push_back(std::move(item)); } } @@ -1660,7 +1668,7 @@ void ProjectPanel::Update( return; } - if (GetBrowserModel().GetTreeItems().empty()) { + if (GetBrowserModel().GetFolderEntries().empty()) { ResolveProjectRuntime()->Refresh(); SyncSelectionsFromRuntime(); } diff --git a/editor/app/Features/Project/ProjectPanel.h b/editor/app/Features/Project/ProjectPanel.h index 730bff61..307c2e6e 100644 --- a/editor/app/Features/Project/ProjectPanel.h +++ b/editor/app/Features/Project/ProjectPanel.h @@ -1,7 +1,7 @@ #pragma once -#include "Project/EditorProjectRuntime.h" -#include "ProjectBrowserModel.h" +#include "Services/Project/EditorProjectRuntime.h" +#include "Services/Project/ProjectBrowserModel.h" #include "Commands/EditorEditCommandRoute.h" #include diff --git a/editor/app/Project/EditorProjectRuntime.cpp b/editor/app/Services/Project/EditorProjectRuntime.cpp similarity index 99% rename from editor/app/Project/EditorProjectRuntime.cpp rename to editor/app/Services/Project/EditorProjectRuntime.cpp index 9b324647..620ce577 100644 --- a/editor/app/Project/EditorProjectRuntime.cpp +++ b/editor/app/Services/Project/EditorProjectRuntime.cpp @@ -1,4 +1,4 @@ -#include "Project/EditorProjectRuntime.h" +#include "Services/Project/EditorProjectRuntime.h" namespace XCEngine::UI::Editor::App { diff --git a/editor/app/Project/EditorProjectRuntime.h b/editor/app/Services/Project/EditorProjectRuntime.h similarity index 98% rename from editor/app/Project/EditorProjectRuntime.h rename to editor/app/Services/Project/EditorProjectRuntime.h index 4c5d6ce2..b2c248ce 100644 --- a/editor/app/Project/EditorProjectRuntime.h +++ b/editor/app/Services/Project/EditorProjectRuntime.h @@ -1,6 +1,6 @@ #pragma once -#include "Features/Project/ProjectBrowserModel.h" +#include "Services/Project/ProjectBrowserModel.h" #include "State/EditorSelectionService.h" #include "State/EditorSession.h" diff --git a/editor/app/Features/Project/ProjectBrowserModel.cpp b/editor/app/Services/Project/ProjectBrowserModel.cpp similarity index 97% rename from editor/app/Features/Project/ProjectBrowserModel.cpp rename to editor/app/Services/Project/ProjectBrowserModel.cpp index f2fb0e84..526fe2ef 100644 --- a/editor/app/Features/Project/ProjectBrowserModel.cpp +++ b/editor/app/Services/Project/ProjectBrowserModel.cpp @@ -28,7 +28,6 @@ std::string BuildRelativeProjectPath( std::string BuildAssetDisplayName(const std::filesystem::path& path, bool directory); std::string BuildAssetNameWithExtension(const std::filesystem::path& path, bool directory); bool IsMetaFile(const std::filesystem::path& path); -bool HasChildDirectories(const std::filesystem::path& folderPath); std::vector CollectSortedChildDirectories( const std::filesystem::path& folderPath); std::wstring MakePathKey(const std::filesystem::path& path); @@ -168,20 +167,6 @@ bool IsMetaFile(const std::filesystem::path& path) { return ToLowerCopy(path.extension().string()) == ".meta"; } -bool HasChildDirectories(const std::filesystem::path& folderPath) { - std::error_code errorCode = {}; - const std::filesystem::directory_iterator end = {}; - for (std::filesystem::directory_iterator iterator(folderPath, errorCode); - !errorCode && iterator != end; - iterator.increment(errorCode)) { - if (iterator->is_directory(errorCode)) { - return true; - } - } - - return false; -} - std::vector CollectSortedChildDirectories( const std::filesystem::path& folderPath) { std::vector paths = {}; @@ -556,7 +541,6 @@ std::string RemapMovedItemId( void ProjectBrowserModel::Reset() { m_assetsRootPath.clear(); m_folderEntries.clear(); - m_treeItems.clear(); m_assetEntries.clear(); m_currentFolderId.clear(); } @@ -599,7 +583,7 @@ void ProjectBrowserModel::RefreshBrowserState() { } bool ProjectBrowserModel::Empty() const { - return m_treeItems.empty(); + return m_folderEntries.empty(); } std::filesystem::path ProjectBrowserModel::GetProjectRootPath() const { @@ -616,10 +600,6 @@ const std::vector& ProjectBrowserModel::GetFol return m_folderEntries; } -const std::vector& ProjectBrowserModel::GetTreeItems() const { - return m_treeItems; -} - const std::vector& ProjectBrowserModel::GetAssetEntries() const { return m_assetEntries; } @@ -1188,9 +1168,9 @@ void ProjectBrowserModel::EnsureValidCurrentFolder() { } if (FindFolderEntry(m_currentFolderId) == nullptr) { - m_currentFolderId = m_treeItems.empty() + m_currentFolderId = m_folderEntries.empty() ? std::string() - : m_treeItems.front().itemId; + : m_folderEntries.front().itemId; } } @@ -1270,7 +1250,6 @@ namespace XCEngine::UI::Editor::App { void ProjectBrowserModel::RefreshFolderTree() { TraceProjectBrowser("ProjectBrowserModel::RefreshFolderTree begin"); m_folderEntries.clear(); - m_treeItems.clear(); if (m_assetsRootPath.empty() || !std::filesystem::exists(m_assetsRootPath)) { return; @@ -1279,22 +1258,17 @@ void ProjectBrowserModel::RefreshFolderTree() { const auto appendFolderRecursive = [&](auto&& self, const std::filesystem::path& folderPath, std::uint32_t depth) -> void { const std::string itemId = BuildRelativeItemId(folderPath, m_assetsRootPath); + const std::vector childFolders = + CollectSortedChildDirectories(folderPath); FolderEntry folderEntry = {}; folderEntry.itemId = itemId; folderEntry.absolutePath = folderPath; folderEntry.label = PathToUtf8String(folderPath.filename()); + folderEntry.depth = depth; + folderEntry.hasChildFolders = !childFolders.empty(); m_folderEntries.push_back(folderEntry); - Widgets::UIEditorTreeViewItem item = {}; - item.itemId = itemId; - item.label = folderEntry.label; - item.depth = depth; - item.forceLeaf = !HasChildDirectories(folderPath); - m_treeItems.push_back(std::move(item)); - - const std::vector childFolders = - CollectSortedChildDirectories(folderPath); for (const std::filesystem::path& childPath : childFolders) { self(self, childPath, depth + 1u); } diff --git a/editor/app/Features/Project/ProjectBrowserModel.h b/editor/app/Services/Project/ProjectBrowserModel.h similarity index 95% rename from editor/app/Features/Project/ProjectBrowserModel.h rename to editor/app/Services/Project/ProjectBrowserModel.h index 83e690dd..696a6a45 100644 --- a/editor/app/Features/Project/ProjectBrowserModel.h +++ b/editor/app/Services/Project/ProjectBrowserModel.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -33,6 +31,8 @@ public: std::string itemId = {}; std::filesystem::path absolutePath = {}; std::string label = {}; + std::uint32_t depth = 0u; + bool hasChildFolders = false; }; struct AssetEntry { @@ -61,7 +61,6 @@ public: std::filesystem::path GetProjectRootPath() const; const std::filesystem::path& GetAssetsRootPath() const; const std::vector& GetFolderEntries() const; - const std::vector& GetTreeItems() const; const std::vector& GetAssetEntries() const; const std::string& GetCurrentFolderId() const; bool IsAssetsRoot(std::string_view itemId) const; @@ -113,7 +112,6 @@ private: std::filesystem::path m_assetsRootPath = {}; std::vector m_folderEntries = {}; - std::vector m_treeItems = {}; std::vector m_assetEntries = {}; std::string m_currentFolderId = {}; }; diff --git a/tests/UI/Editor/unit/test_editor_project_runtime.cpp b/tests/UI/Editor/unit/test_editor_project_runtime.cpp index fd01840d..f063ddfc 100644 --- a/tests/UI/Editor/unit/test_editor_project_runtime.cpp +++ b/tests/UI/Editor/unit/test_editor_project_runtime.cpp @@ -1,4 +1,4 @@ -#include "Project/EditorProjectRuntime.h" +#include "Services/Project/EditorProjectRuntime.h" #include "State/EditorSelectionService.h" #include diff --git a/tests/UI/Editor/unit/test_project_browser_model.cpp b/tests/UI/Editor/unit/test_project_browser_model.cpp index 25956201..4dc9fe1e 100644 --- a/tests/UI/Editor/unit/test_project_browser_model.cpp +++ b/tests/UI/Editor/unit/test_project_browser_model.cpp @@ -1,4 +1,4 @@ -#include "Features/Project/ProjectBrowserModel.h" +#include "Services/Project/ProjectBrowserModel.h" #include