new_editor: isolate project panel state and harden runtime reset

This commit is contained in:
2026-04-22 00:19:35 +08:00
parent fff33185b9
commit 8bfca5e8f2
11 changed files with 144 additions and 61 deletions

View File

@@ -60,12 +60,12 @@ bool EditorContext::Initialize(const std::filesystem::path& repoRoot) {
m_session.projectRoot = (repoRoot / "project").lexically_normal();
m_commandFocusService = {};
m_selectionService = {};
m_projectRuntime = {};
m_projectRuntime.Reset();
AppendUIEditorRuntimeTrace("startup", "EditorProjectRuntime::Initialize begin");
m_projectRuntime.Initialize(repoRoot);
AppendUIEditorRuntimeTrace("startup", "EditorProjectRuntime::Initialize end");
m_projectRuntime.BindSelectionService(&m_selectionService);
m_sceneRuntime = {};
m_sceneRuntime.Reset();
AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize begin");
m_sceneRuntime.Initialize(m_session.projectRoot);
AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize end");
@@ -80,6 +80,8 @@ bool EditorContext::Initialize(const std::filesystem::path& repoRoot) {
m_shellServices = {};
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
m_shellServices.shortcutManager = &m_shortcutManager;
m_lastStatus.clear();
m_lastMessage.clear();
SetReadyStatus();
AppendUIEditorRuntimeTrace("startup", "EditorContext::Initialize end");
return true;

View File

@@ -553,7 +553,16 @@ std::string RemapMovedItemId(
} // namespace
void ProjectBrowserModel::Reset() {
m_assetsRootPath.clear();
m_folderEntries.clear();
m_treeItems.clear();
m_assetEntries.clear();
m_currentFolderId.clear();
}
void ProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) {
Reset();
m_assetsRootPath = (repoRoot / "project/Assets").lexically_normal();
TraceProjectBrowser("ProjectBrowserModel::Initialize assetsRoot=" + PathToUtf8String(m_assetsRootPath));
std::error_code errorCode = {};
@@ -571,21 +580,6 @@ void ProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) {
std::to_string(m_assetEntries.size()));
}
void ProjectBrowserModel::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon) {
if (m_folderIcon.nativeHandle == icon.nativeHandle &&
m_folderIcon.width == icon.width &&
m_folderIcon.height == icon.height &&
m_folderIcon.kind == icon.kind &&
m_folderIcon.resourceHandle == icon.resourceHandle) {
return;
}
m_folderIcon = icon;
if (!m_assetsRootPath.empty()) {
RefreshFolderTree();
}
}
void ProjectBrowserModel::Refresh() {
TraceProjectBrowser("ProjectBrowserModel::Refresh begin");
RefreshFolderTree();
@@ -1307,7 +1301,6 @@ void ProjectBrowserModel::RefreshFolderTree() {
item.label = folderEntry.label;
item.depth = depth;
item.forceLeaf = !HasChildDirectories(folderPath);
item.leadingIcon = m_folderIcon;
m_treeItems.push_back(std::move(item));
const std::vector<std::filesystem::path> childFolders =

View File

@@ -2,8 +2,6 @@
#include <XCEditor/Collections/UIEditorTreeView.h>
#include <XCEngine/UI/Types.h>
#include <cstdint>
#include <filesystem>
#include <optional>
@@ -49,8 +47,14 @@ public:
bool canPreview = false;
};
ProjectBrowserModel() = default;
ProjectBrowserModel(const ProjectBrowserModel&) = default;
ProjectBrowserModel& operator=(const ProjectBrowserModel&) = default;
ProjectBrowserModel(ProjectBrowserModel&&) noexcept = default;
ProjectBrowserModel& operator=(ProjectBrowserModel&&) noexcept = default;
void Reset();
void Initialize(const std::filesystem::path& repoRoot);
void SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon);
void Refresh();
bool Empty() const;
@@ -110,7 +114,6 @@ private:
std::vector<FolderEntry> m_folderEntries = {};
std::vector<Widgets::UIEditorTreeViewItem> m_treeItems = {};
std::vector<AssetEntry> m_assetEntries = {};
::XCEngine::UI::UITextureHandle m_folderIcon = {};
std::string m_currentFolderId = {};
};

View File

@@ -306,12 +306,26 @@ const ProjectPanel::BrowserModel& ProjectPanel::GetBrowserModel() const {
return ResolveProjectRuntime()->GetBrowserModel();
}
void ProjectPanel::RebuildWindowTreeItems() {
m_windowTreeItems.clear();
if (!HasProjectRuntime()) {
return;
}
m_windowTreeItems = GetBrowserModel().GetTreeItems();
const UITextureHandle folderIcon = ResolveFolderIcon(m_icons);
for (Widgets::UIEditorTreeViewItem& item : m_windowTreeItems) {
item.leadingIcon = folderIcon;
}
}
const std::vector<Widgets::UIEditorTreeViewItem>& ProjectPanel::GetWindowTreeItems() const {
return m_windowTreeItems;
}
void ProjectPanel::Initialize(const std::filesystem::path& repoRoot) {
m_ownedProjectRuntime = std::make_unique<EditorProjectRuntime>();
m_ownedProjectRuntime->Initialize(repoRoot);
if (m_icons != nullptr) {
m_ownedProjectRuntime->SetFolderIcon(ResolveFolderIcon(m_icons));
}
SyncCurrentFolderSelection();
SyncAssetSelectionFromRuntime();
}
@@ -322,9 +336,6 @@ void ProjectPanel::SetProjectRuntime(EditorProjectRuntime* projectRuntime) {
}
m_projectRuntime = projectRuntime;
if (m_projectRuntime != nullptr && m_icons != nullptr) {
m_projectRuntime->SetFolderIcon(ResolveFolderIcon(m_icons));
}
SyncCurrentFolderSelection();
SyncAssetSelectionFromRuntime();
}
@@ -341,10 +352,7 @@ void ProjectPanel::SetSystemInteractionHost(
void ProjectPanel::SetBuiltInIcons(BuiltInIcons* icons) {
m_icons = icons;
if (EditorProjectRuntime* runtime = ResolveProjectRuntime();
runtime != nullptr) {
runtime->SetFolderIcon(ResolveFolderIcon(m_icons));
}
RebuildWindowTreeItems();
}
void ProjectPanel::SetTextMeasurer(const UIEditorTextMeasurer* textMeasurer) {
@@ -490,7 +498,7 @@ UIRect ProjectPanel::BuildRenameBounds(
if (surface == RenameSurface::Tree) {
return BuildUIEditorTreePanelInlineRenameBounds(
m_treeFrame.layout,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
itemId,
hostedMetrics);
}
@@ -673,10 +681,12 @@ const UIEditorPanelContentHostPanelState* ProjectPanel::FindMountedProjectPanel(
void ProjectPanel::SyncCurrentFolderSelection() {
if (!HasProjectRuntime()) {
m_windowTreeItems.clear();
m_folderSelection.ClearSelection();
return;
}
RebuildWindowTreeItems();
const std::string& currentFolderId = GetBrowserModel().GetCurrentFolderId();
if (currentFolderId.empty()) {
m_folderSelection.ClearSelection();
@@ -1103,14 +1113,14 @@ std::vector<UIInputEvent> ProjectPanel::BuildTreeInteractionInputEvents(
? m_treeFrame.layout
: Widgets::BuildUIEditorTreeViewLayout(
m_layout.treeRect,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
m_folderExpansion,
ResolveUIEditorTreeViewMetrics(),
m_treeInteractionState.verticalOffset);
return BuildUIEditorTreePanelInteractionInputEvents(
m_treeDragState,
layout,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
rawEvents,
m_splitterDragging || m_assetDragState.dragging);
}
@@ -1521,6 +1531,7 @@ void ProjectPanel::Update(
SyncAssetSelectionFromRuntime();
}
RebuildWindowTreeItems();
m_visible = true;
SyncAssetSelectionFromRuntime();
const std::vector<UIInputEvent> filteredEvents =
@@ -1551,7 +1562,7 @@ void ProjectPanel::Update(
const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics();
m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout(
m_layout.treeRect,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
m_folderExpansion,
treeMetrics,
m_treeInteractionState.verticalOffset);
@@ -1579,7 +1590,7 @@ void ProjectPanel::Update(
m_folderSelection,
m_folderExpansion,
m_layout.treeRect,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
treeEvents,
treeMetrics);
@@ -1657,7 +1668,7 @@ void ProjectPanel::Update(
TreeDrag::ProcessInputEvents(
m_treeDragState,
m_treeFrame.layout,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
FilterUIEditorTreePanelPointerInputEvents(
filteredEvents,
m_splitterDragging || m_assetDragState.dragging),
@@ -1677,7 +1688,7 @@ void ProjectPanel::Update(
m_layout = BuildLayout(panelState->bounds);
m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout(
m_layout.treeRect,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
m_folderExpansion,
treeMetrics,
m_treeInteractionState.verticalOffset);
@@ -1800,7 +1811,7 @@ void ProjectPanel::Update(
m_layout = BuildLayout(panelState->bounds);
m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout(
m_layout.treeRect,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
m_folderExpansion,
treeMetrics,
m_treeInteractionState.verticalOffset);
@@ -2148,13 +2159,13 @@ std::string ProjectPanel::ResolveAssetDropTargetItemId(
if (ContainsPoint(m_treeFrame.layout.bounds, point)) {
Widgets::UIEditorTreeViewHitTarget hitTarget =
Widgets::HitTestUIEditorTreeView(m_treeFrame.layout, point);
if (hitTarget.itemIndex < GetBrowserModel().GetTreeItems().size() &&
if (hitTarget.itemIndex < GetWindowTreeItems().size() &&
(hitTarget.kind == Widgets::UIEditorTreeViewHitTargetKind::Row ||
hitTarget.kind == Widgets::UIEditorTreeViewHitTargetKind::Disclosure)) {
if (surface != nullptr) {
*surface = DropTargetSurface::Tree;
}
return GetBrowserModel().GetTreeItems()[hitTarget.itemIndex].itemId;
return GetWindowTreeItems()[hitTarget.itemIndex].itemId;
}
}
@@ -2205,7 +2216,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
AppendUIEditorTreeViewBackground(
drawList,
m_treeFrame.layout,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
m_folderSelection,
m_treeInteractionState.treeViewState,
treePalette,
@@ -2213,7 +2224,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
AppendUIEditorTreeViewForeground(
drawList,
m_treeFrame.layout,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
treePalette,
treeMetrics);
@@ -2227,7 +2238,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
} else {
const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex(
m_treeFrame.layout,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
m_treeDragState.dropTargetItemId);
if (visibleIndex != Widgets::UIEditorTreeViewInvalidIndex &&
visibleIndex < m_treeFrame.layout.rowRects.size()) {
@@ -2245,7 +2256,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
m_assetDropTargetSurface == DropTargetSurface::Tree) {
const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex(
m_treeFrame.layout,
GetBrowserModel().GetTreeItems(),
GetWindowTreeItems(),
m_assetDragState.dropTargetItemId);
if (visibleIndex != Widgets::UIEditorTreeViewInvalidIndex &&
visibleIndex < m_treeFrame.layout.rowRects.size()) {

View File

@@ -170,6 +170,8 @@ private:
bool HasProjectRuntime() const;
BrowserModel& GetBrowserModel();
const BrowserModel& GetBrowserModel() const;
void RebuildWindowTreeItems();
const std::vector<Widgets::UIEditorTreeViewItem>& GetWindowTreeItems() const;
const FolderEntry* FindFolderEntry(std::string_view itemId) const;
const AssetEntry* FindAssetEntry(std::string_view itemId) const;
AssetCommandTarget ResolveAssetCommandTarget(
@@ -246,6 +248,7 @@ private:
Ports::SystemInteractionPort* m_systemInteractionHost = nullptr;
BuiltInIcons* m_icons = nullptr;
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* m_textMeasurer = nullptr;
std::vector<Widgets::UIEditorTreeViewItem> m_windowTreeItems = {};
::XCEngine::UI::Widgets::UISelectionModel m_folderSelection = {};
::XCEngine::UI::Widgets::UIExpansionModel m_folderExpansion = {};
::XCEngine::UI::Widgets::UISelectionModel m_assetSelection = {};

View File

@@ -77,11 +77,16 @@ EditCommandTarget BuildEditCommandTarget(
} // namespace
bool EditorProjectRuntime::Initialize(const std::filesystem::path& repoRoot) {
m_browserModel.Initialize(repoRoot);
m_ownedSelectionService = {};
void EditorProjectRuntime::Reset() {
m_browserModel.Reset();
m_ownedSelectionService.ClearSelection();
m_selectionService = &m_ownedSelectionService;
m_pendingSceneOpenPath.reset();
}
bool EditorProjectRuntime::Initialize(const std::filesystem::path& repoRoot) {
Reset();
m_browserModel.Initialize(repoRoot);
return true;
}
@@ -91,10 +96,6 @@ void EditorProjectRuntime::BindSelectionService(
selectionService != nullptr ? selectionService : &m_ownedSelectionService;
}
void EditorProjectRuntime::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon) {
m_browserModel.SetFolderIcon(icon);
}
void EditorProjectRuntime::Refresh() {
m_browserModel.Refresh();
RevalidateSelection();

View File

@@ -16,6 +16,12 @@ namespace XCEngine::UI::Editor::App {
class EditorProjectRuntime {
public:
EditorProjectRuntime() = default;
EditorProjectRuntime(const EditorProjectRuntime&) = delete;
EditorProjectRuntime& operator=(const EditorProjectRuntime&) = delete;
EditorProjectRuntime(EditorProjectRuntime&&) = delete;
EditorProjectRuntime& operator=(EditorProjectRuntime&&) = delete;
struct EditCommandTarget {
std::string itemId = {};
std::filesystem::path absolutePath = {};
@@ -38,9 +44,9 @@ public:
bool showInExplorerSelectTarget = false;
};
void Reset();
bool Initialize(const std::filesystem::path& repoRoot);
void BindSelectionService(EditorSelectionService* selectionService);
void SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon);
void Refresh();
const ProjectBrowserModel& GetBrowserModel() const;

View File

@@ -170,15 +170,22 @@ std::string_view GetSceneToolInteractionLockName(SceneToolInteractionLock lock)
}
}
bool EditorSceneRuntime::Initialize(const std::filesystem::path& projectRoot) {
m_projectRoot = projectRoot;
m_ownedSelectionService = {};
void EditorSceneRuntime::Reset() {
m_projectRoot.clear();
m_startupSceneResult = {};
m_ownedSelectionService.ClearSelection();
m_selectionService = &m_ownedSelectionService;
m_sceneViewCamera = {};
m_toolState = {};
ResetTransformEditHistory();
m_inspectorRevision = 0u;
}
bool EditorSceneRuntime::Initialize(const std::filesystem::path& projectRoot) {
Reset();
m_projectRoot = projectRoot;
m_startupSceneResult = EnsureEditorStartupScene(projectRoot);
EnsureSceneViewCamera();
ResetTransformEditHistory();
m_toolState = {};
RefreshScene();
return m_startupSceneResult.ready;
}

View File

@@ -48,6 +48,13 @@ struct EditorSceneComponentDescriptor {
class EditorSceneRuntime {
public:
EditorSceneRuntime() = default;
EditorSceneRuntime(const EditorSceneRuntime&) = delete;
EditorSceneRuntime& operator=(const EditorSceneRuntime&) = delete;
EditorSceneRuntime(EditorSceneRuntime&&) = delete;
EditorSceneRuntime& operator=(EditorSceneRuntime&&) = delete;
void Reset();
bool Initialize(const std::filesystem::path& projectRoot);
void BindSelectionService(EditorSelectionService* selectionService);

View File

@@ -1,6 +1,8 @@
#include <XCEditor/Collections/UIEditorTabStrip.h>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <cmath>
#include <string>
#include <utility>
@@ -65,7 +67,55 @@ float ResolveEstimatedLabelWidth(
return item.desiredHeaderLabelWidth;
}
return static_cast<float>(item.title.size()) * ClampNonNegative(metrics.estimatedGlyphWidth);
const float baseGlyphWidth = ClampNonNegative(metrics.estimatedGlyphWidth);
if (item.title.empty() || baseGlyphWidth <= 0.0f) {
return 0.0f;
}
const auto estimateAsciiAdvance = [baseGlyphWidth](unsigned char ch) {
if (ch == ' ') {
return baseGlyphWidth * 0.55f;
}
if (std::isdigit(ch) != 0) {
return baseGlyphWidth * 1.08f;
}
if (std::strchr(".,:;!|'`ijlft()[]{}", static_cast<int>(ch)) != nullptr) {
return baseGlyphWidth * 0.58f;
}
if (std::strchr("mwMW@#%&QG", static_cast<int>(ch)) != nullptr) {
return baseGlyphWidth * 1.45f;
}
if (std::isupper(ch) != 0) {
return baseGlyphWidth * 1.18f;
}
if (std::strchr("_-+=/\\\\<>*", static_cast<int>(ch)) != nullptr) {
return baseGlyphWidth * 0.88f;
}
return baseGlyphWidth;
};
float width = 0.0f;
for (std::size_t index = 0; index < item.title.size();) {
const unsigned char ch = static_cast<unsigned char>(item.title[index]);
if (ch < 0x80u) {
width += estimateAsciiAdvance(ch);
++index;
continue;
}
width += baseGlyphWidth * 1.95f;
if ((ch & 0xE0u) == 0xC0u) {
index += 2u;
} else if ((ch & 0xF0u) == 0xE0u) {
index += 3u;
} else if ((ch & 0xF8u) == 0xF0u) {
index += 4u;
} else {
++index;
}
}
return width;
}
float ResolveTabTextTop(

View File

@@ -583,7 +583,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction(
!eventResult.committedFieldId.empty() ||
!eventResult.changedFieldId.empty() ||
!eventResult.requestedFieldId.empty()) {
interactionResult = std::move(eventResult);
Internal::MergeInteractionResult(interactionResult, eventResult);
}
}