docs: sync project browser docs

This commit is contained in:
2026-04-04 01:02:57 +08:00
parent 42e2e1b8f2
commit 2abe0bbbd4
30 changed files with 1725 additions and 116 deletions

View File

@@ -0,0 +1,44 @@
# EditorConsoleSink::Clear
清空当前控制台缓冲区。
```cpp
void Clear();
```
## 当前行为
当前实现分两种情况:
### 缓冲区已经为空
- 直接返回
- 不递增 `revision`
- 不触发回调
### 缓冲区非空
1. 在锁内清空 `m_logs`
2. 递增 `m_revision`
3. 拷贝当前回调
4. 解锁后执行回调
## 当前不会重置的状态
`Clear()` 不会重置:
- `m_nextSerial`
- 已注册回调
所以清空之后再写入新日志时,新的 `serial` 会继续从上次编号往后增长。
## 返回值
- 无。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [GetRevision](GetRevision.md)
- [SetCallback](SetCallback.md)
- [Log](Log.md)

View File

@@ -0,0 +1,35 @@
# EditorConsoleSink::Constructor
构造一个编辑器控制台日志 sink并把它登记为当前活动实例。
```cpp
EditorConsoleSink();
```
## 当前行为
当前构造函数本身没有复杂初始化逻辑,它只做一件关键的事:
- 把静态指针 `s_instance` 直接设为 `this`
对象其余状态使用头文件里的默认成员初始化:
- `m_logs` 为空
- `m_callback` 为空
- `m_nextSerial == 1`
- `m_revision == 0`
## 设计含义
这意味着“最后构造出来的实例”会覆盖之前登记的活动实例。
当前实现没有实例栈,也没有显式注册/反注册 API。
## 返回值
- 无。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [GetInstance](GetInstance.md)
- [Destructor](Destructor.md)

View File

@@ -0,0 +1,39 @@
# EditorConsoleSink::Destructor
销毁控制台 sink并在必要时撤销活动实例注册。
```cpp
~EditorConsoleSink() override;
```
## 当前行为
析构函数会检查:
```cpp
s_instance == this
```
只有当当前对象仍然是活动实例时,才会把 `s_instance` 置空。
如果活动实例已经被其他后来构造的对象覆盖,析构当前对象不会再改全局指针。
## 析构后的可见行为
一旦活动实例被清空,后续 [GetInstance](GetInstance.md) 会直接返回 `nullptr`
如果当前对象在析构前已经被后来构造的实例覆盖,则析构不会影响那个更新的活动实例。
## 当前不做的事情
- 不会自动 `Flush()`
- 不会转移现有日志
- 不会通知回调“实例已销毁”
## 返回值
- 无。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [GetInstance](GetInstance.md)
- [Constructor](Constructor.md)

View File

@@ -0,0 +1,45 @@
# EditorConsoleRecord
**命名空间**: `XCEngine::Debug`
**类型**: `struct`
**源文件**: `editor/src/Core/EditorConsoleSink.h`
**描述**: `EditorConsoleSink` 内部使用的控制台记录快照,给每条日志附加一个单调递增的 `serial`
## 概述
`EditorConsoleRecord``LogEntry` 的 Editor 侧包装层。
它解决的问题不是“如何描述一条日志”,而是“如何在内存缓冲区里稳定地区分和追踪每一条日志”。
相比裸 `LogEntry`,它新增了一个 `serial` 字段,用来表达:
- 这条日志在当前 sink 实例中的追加顺序
- Console 列表选中项的稳定身份
- `Error Pause` 扫描“上次检查之后新增日志”的边界
## 字段
| 字段 | 说明 |
|------|------|
| `serial` | 当前 sink 实例内单调递增的记录编号。 |
| `entry` | 原始日志条目副本。 |
## serial 语义
- `serial``1` 开始递增。
- [Clear](Clear.md) 不会把它重置回 `1`
- 它只在当前进程、当前 sink 生命周期内有意义。
- 它不是时间戳,也不是可持久化的稳定 ID。
## 当前使用位置
- [ConsolePanel](../../panels/ConsolePanel/ConsolePanel.md) 用它保持选择、做键盘导航和双击定位。
- `Error Pause` 流程用它记录“最近已经扫描到哪一条”。
## 相关文档
- [EditorConsoleSink](EditorConsoleSink.md)
- [Log](Log.md)
- [GetRecords](GetRecords.md)

View File

@@ -6,37 +6,91 @@
**源文件**: `editor/src/Core/EditorConsoleSink.h`
**描述**: 编辑器专用日志 sink引擎日志桥接到 Console 面板可读取的内存缓冲区
**描述**: 编辑器专用日志 sink `Logger` 的输出桥接成带 `serial` / `revision` 的内存快照,供 `ConsolePanel` 增量消费
## 概述
`EditorConsoleSink` 虽然位于 `editor/src/Core`,但命名空间属于 `XCEngine::Debug`
的定位很明确:作为 `Logger` 的一个输出目标,把日志保存在内存中,供 Console 面板显示。
`EditorConsoleSink` 虽然位于 `editor/src/Core`,但命名空间属于 `XCEngine::Debug`
不是单纯“把日志塞进数组”的轻量容器,而是当前 Editor Console 数据流的中心桥接层,负责三件事:
## 当前实现
1. 作为 `ILogSink` 接收引擎日志。
2. 把日志保存成带 [EditorConsoleRecord](EditorConsoleRecord.md) 的内存快照。
3. 通过 `serial``revision` 和变更回调让 [ConsolePanel](../../panels/ConsolePanel/ConsolePanel.md) 可以做增量刷新、错误暂停扫描和选择保持。
- 继承自 `ILogSink`
- 通过 `std::mutex` 保护 `m_logs`
- `Log()` 会把新日志追加到数组尾部
- 最大缓存条数固定为 `1000`
- 超限时直接丢弃最早的一条日志
- `GetLogs()` 返回日志数组的拷贝
- `GetInstance()` 在没有显式注册实例时会返回一个静态 fallback 实例
## 当前数据模型
## 设计说明
### 日志记录
这是一个典型的“工具层 sink”。
引擎日志系统不应该知道编辑器面板怎么画 UI但编辑器又确实需要消费同一份日志流。把桥接能力做成一个 `ILogSink` 子类,是最干净的集成方式。
内部缓冲区 `m_logs` 保存的是 `EditorConsoleRecord`,而不是裸 `LogEntry`
## 当前限制
- `serial` 是 sink 内单调递增的记录编号。
- `entry` 是完整日志条目副本。
- 当前只保留最近 1000 条日志
- `GetLogs()` 会复制整个缓冲区
- `SetCallback()` 没有更复杂的线程调度机制,回调触发策略非常轻量
`ConsolePanel` 当前依赖这个 `serial` 做:
- 新增错误扫描边界
- 列表选中保持
- 折叠/搜索后重新定位选中项
### revision
`m_revision` 是比 `serial` 更粗粒度的“内容变更版本号”:
- 每次 [Log](Log.md) 成功追加日志时加一
- 每次 [Clear](Clear.md) 真正清空非空缓冲区时加一
- [SetCallback](SetCallback.md) 和 [Flush](Flush.md) 不会改它
它的用途是让 UI 先判断“有没有变化”,再决定是否重建可见行。
## 当前生命周期
- [Constructor](Constructor.md) 会把当前对象登记为全局活动实例。
- [Destructor](Destructor.md) 只在自己仍是活动实例时清掉全局指针。
- [GetInstance](GetInstance.md) 只返回当前登记的活动实例;若当前没有活动 sink则返回 `nullptr`
这意味着当前接口层面上 `GetInstance()` 可能返回空指针。调用方需要像 [ConsolePanel](../../panels/ConsolePanel/ConsolePanel.md) 那样先判空,再进入日志读取与 UI 刷新流程。
## 线程语义
- `m_mutex` 保护 `m_logs``m_callback``m_nextSerial``m_revision`
- [GetLogs](GetLogs.md)、[GetRecords](GetRecords.md)、[GetRevision](GetRevision.md) 都返回锁内拍下的快照,不暴露内部引用。
- [Log](Log.md) 和 [Clear](Clear.md) 会先在锁内取出当前回调,再在解锁后执行回调。
最后一点很关键:
当前回调不是调度到 UI 线程,而是谁触发 `Log()` / `Clear()`,谁就在自己的调用线程上同步执行回调。
## 当前实现边界
- 最大缓存条数固定为 `1000`,超限时直接丢弃最早一条。
- `GetLogs()` / `GetRecords()` 都会复制整份缓冲区,不是零拷贝视图。
- [Flush](Flush.md) 当前是空实现,只是满足 `ILogSink` 契约。
- [SetCallback](SetCallback.md) 只保存一个回调,后设置的会覆盖先前的。
- [Clear](Clear.md) 不会重置 `m_nextSerial`,所以清空后新日志仍继续增长编号。
## 公开方法
| 方法 | 说明 |
|------|------|
| [GetInstance](GetInstance.md) | 获取当前活动 sink若没有已注册实例则返回 `nullptr`。 |
| [Constructor](Constructor.md) | 构造一个可注册为活动实例的编辑器日志 sink。 |
| [Destructor](Destructor.md) | 在销毁时撤销活动实例注册。 |
| [Log](Log.md) | 追加一条日志并在需要时通知观察者。 |
| [Flush](Flush.md) | 满足 `ILogSink` 接口的空刷新函数。 |
| [GetLogs](GetLogs.md) | 返回不含 `serial``LogEntry` 快照。 |
| [GetRecords](GetRecords.md) | 返回带 `serial` 的完整控制台记录快照。 |
| [GetRevision](GetRevision.md) | 返回当前缓冲区的变更版本号。 |
| [Clear](Clear.md) | 清空当前缓冲区并发布一次内容变化。 |
| [SetCallback](SetCallback.md) | 注册或替换一条内容变更回调。 |
## 相关类型
- [EditorConsoleRecord](EditorConsoleRecord.md)
- [LogEntry](../../../Debug/LogEntry/LogEntry.md)
## 相关文档
- [Core](../Core.md)
- [ConsoleActionRouter](../../Actions/ConsoleActionRouter/ConsoleActionRouter.md)
- [ConsolePanel](../../panels/ConsolePanel/ConsolePanel.md)
- [Debug::Logger](../../../Debug/Logger/Logger.md)
- [ConsoleFilterState](../../UI/ConsoleFilterState/ConsoleFilterState.md)
- [ILogSink](../../../Debug/ILogSink/ILogSink.md)
- [Logger](../../../Debug/Logger/Logger.md)

View File

@@ -0,0 +1,30 @@
# EditorConsoleSink::Flush
满足 `ILogSink` 接口的刷新函数。
```cpp
void Flush() override;
```
## 当前行为
当前实现为空函数,不执行任何操作。
## 原因
`EditorConsoleSink` 只维护内存缓冲区,没有:
- 文件句柄
- socket
- 延迟写盘缓存
所以它不需要像文件 sink 那样做显式落地。
## 返回值
- 无。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [ILogSink](../../../Debug/ILogSink/ILogSink.md)

View File

@@ -0,0 +1,47 @@
# EditorConsoleSink::GetInstance
获取当前活动的编辑器控制台 sink。
```cpp
static EditorConsoleSink* GetInstance();
```
## 当前行为
当前实现没有兜底实例,也不会在这里隐式构造对象。它只做一件事:
1. 直接返回静态指针 `s_instance` 的当前值。
因此:
- 若已有活动 sink 被构造并登记,返回那个实例。
- 若当前没有活动 sink直接返回 `nullptr`
## 实例来源
活动实例的登记与撤销完全由生命周期驱动:
- [Constructor](Constructor.md) 会把 `s_instance` 设为 `this`
- [Destructor](Destructor.md) 会在 `s_instance == this` 时把它清空
该方法本身不负责:
- 创建 fallback sink
- 维持实例栈
- 区分“正式实例”和“临时实例”
## 注意事项
- 返回的是原始指针,不表达所有权。
- 调用方必须处理 `nullptr`,不能假设 Editor 全程都已注册控制台 sink。
- 如果较晚构造的新实例覆盖了旧实例,后续调用会直接观察到新的 `s_instance`
## 返回值
- 当前活动实例;如果没有已注册实例,则返回 `nullptr`
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [Constructor](Constructor.md)
- [Destructor](Destructor.md)

View File

@@ -0,0 +1,44 @@
# EditorConsoleSink::GetLogs
返回不含 `serial` 的日志条目快照。
```cpp
std::vector<LogEntry> GetLogs() const;
```
## 当前行为
当前实现会:
1. 在锁内读取 `m_logs`
2. 新建一个 `std::vector<LogEntry>`
3. 把每条 `EditorConsoleRecord::entry` 复制进去
4. 返回这份副本
## 与 GetRecords() 的区别
- [GetLogs](GetLogs.md) 只返回 `LogEntry`
- [GetRecords](GetRecords.md) 保留 `serial`
如果调用方需要做:
- 增量刷新
- 选中保持
- 错误扫描边界
应优先使用 `GetRecords()`
## 成本与限制
- 返回值是整份副本,不是视图。
- 当前调用一次就会复制当前全部缓冲区。
## 返回值
- 当前日志缓冲区的 `LogEntry` 副本数组。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [GetRecords](GetRecords.md)
- [Log](Log.md)

View File

@@ -0,0 +1,41 @@
# EditorConsoleSink::GetRecords
返回带 `serial` 的控制台记录快照。
```cpp
std::vector<EditorConsoleRecord> GetRecords() const;
```
## 当前行为
当前实现非常直接:
1. 在锁内复制 `m_logs`
2. 返回整份 `std::vector<EditorConsoleRecord>` 副本
这也是 [ConsolePanel](../../panels/ConsolePanel/ConsolePanel.md) 当前的主要读取接口。
## 为什么需要它
相比 [GetLogs](GetLogs.md),这里保留了 `serial`
这让调用方可以知道:
- 哪些日志是“新追加”的
- 当前选中项在过滤/折叠后对应哪条记录
- `Error Pause` 应该从哪条记录之后开始扫描
## 快照语义
- 返回的是调用时刻的拷贝。
- 后续新的 `Log()``Clear()` 不会回写到已经拿到的返回值。
## 返回值
- 当前控制台缓冲区的完整 `EditorConsoleRecord` 副本数组。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [EditorConsoleRecord](EditorConsoleRecord.md)
- [GetLogs](GetLogs.md)
- [GetRevision](GetRevision.md)

View File

@@ -0,0 +1,40 @@
# EditorConsoleSink::GetRevision
返回当前控制台缓冲区的变更版本号。
```cpp
uint64_t GetRevision() const;
```
## 当前行为
该方法会在锁内读取并返回 `m_revision`
## revision 递增规则
当前实现里,`revision` 只在两类内容变化时递增:
- [Log](Log.md) 追加一条日志
- [Clear](Clear.md) 真正清空一个非空缓冲区
以下操作不会改它:
- [SetCallback](SetCallback.md)
- [Flush](Flush.md)
- 对空缓冲区调用 [Clear](Clear.md)
## 典型用途
UI 层通常先比较 `revision`,只有检测到变化时才重建过滤结果或决定是否滚动到底部。
这比每帧都深比较整份日志数组更便宜。
## 返回值
- 当前缓冲区版本号。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [Log](Log.md)
- [Clear](Clear.md)
- [GetRecords](GetRecords.md)

View File

@@ -0,0 +1,45 @@
# EditorConsoleSink::Log
追加一条日志到控制台缓冲区,并在需要时通知观察者。
```cpp
void Log(const LogEntry& entry) override;
```
## 参数
- `entry` - 待写入控制台缓冲区的日志条目。
## 当前行为
当前实现流程是:
1. 进入互斥区。
2. 如果缓冲区已达到 `MAX_LOGS == 1000`,先删除最早的一条。
3. 以当前 `m_nextSerial` 构造一条 [EditorConsoleRecord](EditorConsoleRecord.md)。
4. 递增 `m_nextSerial`
5. 递增 `m_revision`
6. 拷贝当前回调到局部变量。
7. 离开互斥区后执行回调。
## serial 与裁剪语义
- 新日志总是追加到尾部。
- 超限裁剪只丢弃最老记录,不影响新记录的 `serial` 递增。
- 即使缓冲区被多次清空,后续日志的 `serial` 仍然继续增长。
## 回调线程语义
回调不是异步投递,而是由 `Log()` 的调用线程同步执行。
当前实现之所以先复制回调、再解锁后调用,是为了避免回调内部再次访问 sink 时发生重入锁问题。
## 返回值
- 无。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [EditorConsoleRecord](EditorConsoleRecord.md)
- [GetRevision](GetRevision.md)
- [SetCallback](SetCallback.md)

View File

@@ -0,0 +1,46 @@
# EditorConsoleSink::SetCallback
注册或替换一条“控制台内容变化”回调。
```cpp
void SetCallback(std::function<void()> callback);
```
## 参数
- `callback` - 新的通知回调;可以为空。
## 当前行为
当前实现只是在锁内执行:
- `m_callback = std::move(callback)`
它不会:
- 立即触发新回调
- 保留旧回调形成链式通知
- 做线程切换或延迟调度
## 触发时机
新回调会在以下操作后被调用:
- [Log](Log.md) 成功追加日志
- [Clear](Clear.md) 真正清空非空缓冲区
并且都是在对应调用线程上、解锁之后同步执行。
## 使用建议
如果回调里要刷新 UI 或触发更上层逻辑,调用方应自己保证线程上下文安全,不要假设这里已经切到主线程。
## 返回值
- 无。
## 相关文档
- [返回类型总览](EditorConsoleSink.md)
- [Log](Log.md)
- [Clear](Clear.md)

View File

@@ -0,0 +1,58 @@
# IProjectManager / Current Items And Selection
**命名空间**: `XCEngine::Editor`
**类型**: `interface-group`
**源文件**: `editor/src/Core/IProjectManager.h`
## 相关签名
```cpp
virtual const std::vector<AssetItemPtr>& GetCurrentItems() const = 0;
virtual AssetItemPtr GetRootFolder() const = 0;
virtual AssetItemPtr GetCurrentFolder() const = 0;
virtual AssetItemPtr GetSelectedItem() const = 0;
virtual const std::string& GetSelectedItemPath() const = 0;
virtual int GetSelectedIndex() const = 0;
virtual void SetSelectedIndex(int index) = 0;
virtual void SetSelectedItem(const AssetItemPtr& item) = 0;
virtual void ClearSelection() = 0;
virtual int FindCurrentItemIndex(const std::string& fullPath) const = 0;
```
## 作用
这一组接口定义的是“当前正在浏览的目录里有什么,以及当前选中了什么”。
调用方通常用它们驱动:
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md) 的目录内容绘制
- 资源卡片选中高亮
- 上下文菜单目标同步
- 基于当前目录的资源创建、删除、重命名和拖放移动
## 语义说明
- `GetCurrentItems()` 返回当前目录直接子项,不是全项目递归结果。
- `GetRootFolder()``GetCurrentFolder()` 都是 `AssetItem` 视图节点,而不是磁盘句柄。
- `GetSelectedItemPath()` 提供稳定的路径标识,便于刷新后重新解析当前选择。
- `GetSelectedIndex()` / `SetSelectedIndex()` 以“当前目录列表索引”为坐标,不跨目录。
- `FindCurrentItemIndex(fullPath)` 只在当前目录中找,不做全树搜索。
## 当前默认实现
在 [ProjectManager](../../Managers/ProjectManager/ProjectManager.md) 中:
- 选择状态按 `m_selectedItemPath` 保存,而不是长期持有旧 `AssetItemPtr`
- `GetSelectedItem()` 会在当前目录里按路径重新解析
- `SetSelectedIndex()` 越界时会清空选择
- `RefreshCurrentFolder()` 后若旧路径仍在当前目录内,选择可被恢复
## 相关文档
- [IProjectManager](IProjectManager.md)
- [Navigation And Path](Navigation-And-Path.md)
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)

View File

@@ -0,0 +1,54 @@
# IProjectManager / File Operations
**命名空间**: `XCEngine::Editor`
**类型**: `interface-group`
**源文件**: `editor/src/Core/IProjectManager.h`
## 相关签名
```cpp
virtual AssetItemPtr CreateFolder(const std::string& name) = 0;
virtual bool DeleteItem(const std::string& fullPath) = 0;
virtual bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) = 0;
virtual bool RenameItem(const std::string& sourceFullPath, const std::string& newName) = 0;
virtual const std::string& GetProjectPath() const = 0;
```
## 作用
这一组接口定义的是项目浏览器层面的基础资源改动能力。
它们负责:
- 在当前目录下创建文件夹
- 删除某个资源或目录
- 把资源移动到目标目录
- 重命名资源或目录
- 暴露项目根路径,供命令层生成相对路径、项目描述文件和 fallback 场景路径
## 参数与返回值
- `CreateFolder(name)` 返回新建目录对应的 `AssetItemPtr`,失败时返回 `nullptr`
- `DeleteItem(...)` / `MoveItem(...)` / `RenameItem(...)` 以布尔值表示成功或失败
- `MoveItem(...)` 的目标参数是“目标目录完整路径”,不是目标文件完整路径
- `GetProjectPath()` 返回项目根目录;它和 `GetCurrentPath()` 的含义不同
## 当前默认实现
在 [ProjectManager](../../Managers/ProjectManager/ProjectManager.md) 中,这组操作还承担了若干实际约束:
- 名称会先 trim并拒绝 Windows 非法文件名
- 所有改动必须位于当前项目 `Assets` 根目录之内
- 根目录本身不能被删除、移动或重命名
- `.meta` sidecar 会在删除、移动、重命名时跟随同步
- 完成后会刷新目录树,并尽量保持当前路径与选择一致
## 相关文档
- [IProjectManager](IProjectManager.md)
- [Initialization And Refresh](Initialization-And-Refresh.md)
- [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)

View File

@@ -6,36 +6,82 @@
**源文件**: `editor/src/Core/IProjectManager.h`
**描述**: 定义项目浏览器接口,负责当前资产列表、路径导航、文件夹刷新以及基础资源文件操作。
**描述**: 定义 editor 项目工作流的抽象接口,负责当前资产列表、路径导航、选择状态,以及项目 `Assets` 树上的基础文件操作契约
## 概述
`IProjectManager` 面向的是编辑器 Project 面板这一类工作流
`IProjectManager` 面向的不只是 `ProjectPanel` 的目录浏览,它还是 editor 侧项目资源模型的抽象接口
它的接口组合非常清晰
按当前头文件,它的接口可以分成五组
- 当前目录的资产项列表
- 当前选中项索引
- 当前选中项与选中路径
- 路径导航
- 项目根路径
- 文件夹刷新/创建/删除/移动
- 项目根路径与目录刷新
- 创建 / 删除 / 移动 / 重命名
因此它是当前这条链路里的中间抽象层:
```text
MainMenuActionRouter / ProjectPanel
-> ProjectCommands
-> IProjectManager
-> ProjectManager
```
菜单层和面板层都不直接操作文件系统细节,而是通过这个接口进入项目模型与文件操作。
## 核心接口
| 方法 | 作用 |
|------|------|
| `GetCurrentItems()` | 获取当前目录条目。 |
| `GetSelectedItem()` / `GetSelectedItemPath()` | 获取当前选中资源及其路径。 |
| `GetSelectedIndex()` / `SetSelectedIndex()` | 管理当前选中项索引。 |
| `SetSelectedItem()` / `ClearSelection()` | 直接管理当前选择。 |
| `NavigateToFolder()` / `NavigateBack()` / `NavigateToIndex()` | 处理路径导航。 |
| `CanNavigateBack()` | 判断能否返回上一级。 |
| `GetCurrentPath()` / `GetPathDepth()` / `GetPathName()` | 查询当前路径面包屑。 |
| `Initialize()` | 初始化项目目录。 |
| `RefreshCurrentFolder()` | 刷新当前目录内容。 |
| `CreateFolder()` / `DeleteItem()` / `MoveItem()` | 执行基础文件操作。 |
| `CreateFolder()` / `DeleteItem()` / `MoveItem()` / `RenameItem()` | 执行基础文件操作。 |
| `GetProjectPath()` | 获取项目根目录。 |
## 边界与职责
`IProjectManager` 当前明确不负责:
- 文件对话框
- 菜单文本或快捷键
- 资产数据库导入 / artifact 构建
- 项目级脚本构建
- 项目保存与项目切换策略
它负责的是同步项目模型与维护动作契约,也就是:
- UI 层能看到什么
- 基础文件操作怎么做
- 当前项目根目录和 `Assets` 浏览树如何对外暴露
这也是当前接口设计上的一个取舍:
- 资源浏览和基础文件操作都通过同步返回值表达结果
- 更高层的项目保存、脚本构建和项目切换仍留给 [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md) 与 `Application`
- 界面层如何呈现这些结果,仍由上层自己决定
## 设计说明
`ProjectPanel` 依赖的目录模型、导航状态和文件操作抽象成接口,有两个直接收益:
- UI 层可以围绕 `AssetItem` 模型稳定开发,而不必直接接触文件系统细节
- 更高层的项目命令层可以只依赖抽象,而不耦合到具体实现
这也是为什么 `IProjectManager` 的当前职责比较克制:它更像“项目资源浏览模型接口”,而不是完整的项目系统门面。
## 相关文档
- [Core](../Core.md)
- [AssetItem](../AssetItem/AssetItem.md)
- [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)

View File

@@ -0,0 +1,45 @@
# IProjectManager / Initialization And Refresh
**命名空间**: `XCEngine::Editor`
**类型**: `interface-group`
**源文件**: `editor/src/Core/IProjectManager.h`
## 相关签名
```cpp
virtual void Initialize(const std::string& projectPath) = 0;
virtual void RefreshCurrentFolder() = 0;
```
## 作用
这一组接口负责把 manager 绑定到某个项目根目录,并在外部文件系统状态变化后刷新当前浏览视图。
它们通常由以下工作流触发:
- [ProjectPanel::Initialize](../../panels/ProjectPanel/Initialize.md)
- [ProjectCommands::SwitchProject](../../Commands/ProjectCommands/ProjectCommands.md)
- `SaveProject(...)``RebuildScriptAssemblies(...)` 这类可能影响项目目录状态的命令
## 语义说明
- `Initialize(projectPath)` 把 manager 切到新的项目上下文。
- `RefreshCurrentFolder()` 重建当前项目资产树的可见部分。
- 这两者都只负责项目浏览模型,不负责文件对话框、场景切换确认或撤销历史重置。
## 当前默认实现
在 [ProjectManager](../../Managers/ProjectManager/ProjectManager.md) 中:
- `Initialize(...)` 会把 `<project>/Assets` 作为根目录,并确保最小的 `Assets/Scenes` 结构存在
- `RefreshCurrentFolder()` 会重建目录树,同时尽量保留当前路径和当前选择
- 若扫描或文件系统操作抛异常,默认实现倾向于保留最小可工作的空根目录,而不是把异常继续上抛
## 相关文档
- [IProjectManager](IProjectManager.md)
- [File Operations](File-Operations.md)
- [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)

View File

@@ -0,0 +1,54 @@
# IProjectManager / Navigation And Path
**命名空间**: `XCEngine::Editor`
**类型**: `interface-group`
**源文件**: `editor/src/Core/IProjectManager.h`
## 相关签名
```cpp
virtual void NavigateToFolder(const AssetItemPtr& folder) = 0;
virtual void NavigateBack() = 0;
virtual void NavigateToIndex(size_t index) = 0;
virtual bool CanNavigateBack() const = 0;
virtual std::string GetCurrentPath() const = 0;
virtual size_t GetPathDepth() const = 0;
virtual std::string GetPathName(size_t index) const = 0;
```
## 作用
这一组接口把项目浏览路径暴露成了一个显式的面包屑模型,而不只是“当前目录指针”。
上层通常用它们驱动:
- 左侧目录树点击导航
- 右侧 breadcrumb 点击回跳
- “是否还能返回上级目录”的按钮或状态判断
## 路径模型
- `NavigateToFolder(...)` 直接跳到某个目录节点。
- `NavigateBack()` 回退一级。
- `NavigateToIndex(index)` 以 breadcrumb 层级为单位回跳。
- `GetPathDepth()` / `GetPathName(index)` 提供 breadcrumb 数据源。
- `GetCurrentPath()` 返回逻辑浏览路径,而不是磁盘绝对路径。
## 当前默认实现
在 [ProjectManager](../../Managers/ProjectManager/ProjectManager.md) 中:
- 路径栈由 `m_path` 维护
- `GetCurrentPath()` 始终从 `"Assets"` 开始拼接
- `CanNavigateBack()` 只在 `m_path.size() > 1` 时返回 `true`
- 导航发生后会清空当前资源选择,避免旧目录索引泄漏到新目录
## 相关文档
- [IProjectManager](IProjectManager.md)
- [Current Items And Selection](Current-Items-And-Selection.md)
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)

View File

@@ -0,0 +1,68 @@
# ProjectRootResolver
**命名空间**: `XCEngine::Editor`
**类型**: `header-helper`
**源文件**: `editor/src/Core/ProjectRootResolver.h`
**描述**: 提供编辑器启动时的项目根目录解析和工作目录切换 helper。
## 概述
`ProjectRootResolver.h` 解决的是“编辑器现在应该把哪个目录视为当前项目根”的问题。
当前 [Application](../../Application/Application.md) 会在启动时调用这里的 helper尽量把项目定位到
- 命令行显式指定的路径
- 当前工作区下的默认 `project/`
- 或者最后回退到工作目录 / 可执行文件目录
## 当前解析顺序
`ResolveEditorProjectRootUtf8()` 的顺序是:
1. 读取当前工作目录与可执行目录。
2. 先解析命令行 `--project` / `-p` 覆盖项。
3. 向上查找工作区根目录。
4. 若找到工作区,则优先使用 `<workspace>/project`
5. 若仍找不到,再尝试从可执行目录向上查找工作区。
6. 最后回退到工作目录,若工作目录为空则回退到可执行目录。
## 工作区与项目判定
内部 helper 当前使用的规则很直接:
- `IsWorkspaceRoot(candidate)`
- 需要同时存在 `CMakeLists.txt`
- 需要有 `editor/``engine/` 目录
- `IsEditorProjectRoot(candidate)`
- 只要存在 `Project.xcproject`,或存在 `Assets/` 目录
这说明它不是通用项目发现器,而是明显面向当前仓库结构的启动辅助逻辑。
## 默认项目策略
`ResolveWorkspaceDefaultProjectRoot()` 当前优先返回:
- `<workspace>/project`
如果该目录本身已经符合项目根判定,就直接使用它;否则才尝试把工作区根本身当作项目根。
## 工作目录切换
`SetEditorWorkingDirectory(projectRootUtf8)` 会把进程当前工作目录切换到解析出来的项目根,并返回是否成功。
这保证了后续相对路径、资源加载和项目输出目录尽量围绕项目根工作,而不是围绕可执行文件目录。
## 当前实现边界
- 当前实现明显偏 Windows 桌面路径和当前仓库布局,不是跨平台项目定位库。
- 命令行解析只识别 `--project` / `-p` 两种形式。
- `FindWorkspaceRoot()` 依赖向上查找 `CMakeLists.txt + editor + engine` 组合,不适合其他目录结构。
## 相关文档
- [Core](../Core.md)
- [Application](../../Application/Application.md)
- [Win32Utf8](../../Platform/Win32Utf8/Win32Utf8.md)