2026-04-11 20:20:30 +08:00
|
|
|
#pragma once
|
|
|
|
|
|
2026-04-19 00:03:25 +08:00
|
|
|
#include "Project/EditorProjectRuntime.h"
|
2026-04-15 08:24:06 +08:00
|
|
|
#include "ProjectBrowserModel.h"
|
2026-04-12 11:12:27 +08:00
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
#include "Commands/EditorEditCommandRoute.h"
|
|
|
|
|
#include <XCEditor/Collections/UIEditorGridDragDrop.h>
|
2026-04-19 00:03:25 +08:00
|
|
|
#include <XCEditor/Collections/UIEditorInlineRenameSession.h>
|
2026-04-19 02:48:41 +08:00
|
|
|
#include <XCEditor/Collections/UIEditorTreeDragDrop.h>
|
2026-04-11 20:20:30 +08:00
|
|
|
#include <XCEditor/Collections/UIEditorTreeViewInteraction.h>
|
|
|
|
|
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
|
2026-04-19 00:03:25 +08:00
|
|
|
#include <XCEditor/Menu/UIEditorMenuPopup.h>
|
2026-04-15 08:24:06 +08:00
|
|
|
#include <XCEditor/Panels/UIEditorPanelContentHost.h>
|
2026-04-11 20:20:30 +08:00
|
|
|
|
|
|
|
|
#include <XCEngine/UI/DrawData.h>
|
|
|
|
|
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
|
|
|
|
|
#include <XCEngine/UI/Widgets/UISelectionModel.h>
|
|
|
|
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <filesystem>
|
2026-04-19 00:03:25 +08:00
|
|
|
#include <memory>
|
2026-04-15 19:30:58 +08:00
|
|
|
#include <optional>
|
2026-04-11 20:20:30 +08:00
|
|
|
#include <string>
|
|
|
|
|
#include <string_view>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
|
|
2026-04-15 08:24:06 +08:00
|
|
|
class BuiltInIcons;
|
2026-04-19 04:36:52 +08:00
|
|
|
class EditorCommandFocusService;
|
2026-04-15 19:30:58 +08:00
|
|
|
class ProjectPanel final : public EditorEditCommandRoute {
|
2026-04-11 20:20:30 +08:00
|
|
|
public:
|
|
|
|
|
enum class CursorKind : std::uint8_t {
|
|
|
|
|
Arrow = 0,
|
|
|
|
|
ResizeEW
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-11 22:31:14 +08:00
|
|
|
enum class EventKind : std::uint8_t {
|
|
|
|
|
None = 0,
|
|
|
|
|
AssetSelected,
|
|
|
|
|
AssetSelectionCleared,
|
|
|
|
|
FolderNavigated,
|
|
|
|
|
AssetOpened,
|
2026-04-15 19:30:58 +08:00
|
|
|
RenameRequested,
|
2026-04-11 22:31:14 +08:00
|
|
|
ContextMenuRequested
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum class EventSource : std::uint8_t {
|
|
|
|
|
None = 0,
|
|
|
|
|
Tree,
|
|
|
|
|
Breadcrumb,
|
2026-04-19 00:03:25 +08:00
|
|
|
Command,
|
2026-04-11 22:31:14 +08:00
|
|
|
GridPrimary,
|
|
|
|
|
GridDoubleClick,
|
|
|
|
|
GridSecondary,
|
2026-04-19 00:03:25 +08:00
|
|
|
GridDrag,
|
2026-04-11 22:31:14 +08:00
|
|
|
Background
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Event {
|
|
|
|
|
EventKind kind = EventKind::None;
|
|
|
|
|
EventSource source = EventSource::None;
|
|
|
|
|
std::string itemId = {};
|
|
|
|
|
std::filesystem::path absolutePath = {};
|
|
|
|
|
std::string displayName = {};
|
2026-04-19 00:03:25 +08:00
|
|
|
ProjectBrowserModel::ItemKind itemKind =
|
|
|
|
|
ProjectBrowserModel::ItemKind::File;
|
2026-04-11 22:31:14 +08:00
|
|
|
bool directory = false;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-11 20:20:30 +08:00
|
|
|
void Initialize(const std::filesystem::path& repoRoot);
|
2026-04-19 00:03:25 +08:00
|
|
|
void SetProjectRuntime(EditorProjectRuntime* projectRuntime);
|
2026-04-19 04:36:52 +08:00
|
|
|
void SetCommandFocusService(EditorCommandFocusService* commandFocusService);
|
2026-04-15 08:24:06 +08:00
|
|
|
void SetBuiltInIcons(const BuiltInIcons* icons);
|
2026-04-11 20:20:30 +08:00
|
|
|
void SetTextMeasurer(const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer);
|
2026-04-14 16:19:23 +08:00
|
|
|
void ResetInteractionState();
|
2026-04-11 20:20:30 +08:00
|
|
|
void Update(
|
|
|
|
|
const UIEditorPanelContentHostFrame& contentHostFrame,
|
|
|
|
|
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
|
|
|
|
bool allowInteraction,
|
|
|
|
|
bool panelActive);
|
|
|
|
|
void Append(::XCEngine::UI::UIDrawList& drawList) const;
|
|
|
|
|
|
|
|
|
|
CursorKind GetCursorKind() const;
|
|
|
|
|
bool WantsHostPointerCapture() const;
|
|
|
|
|
bool WantsHostPointerRelease() const;
|
|
|
|
|
bool HasActivePointerCapture() const;
|
2026-04-11 22:31:14 +08:00
|
|
|
const std::vector<Event>& GetFrameEvents() const;
|
2026-04-19 00:03:25 +08:00
|
|
|
UIEditorHostCommandEvaluationResult EvaluateAssetCommand(
|
|
|
|
|
std::string_view commandId) const override;
|
|
|
|
|
UIEditorHostCommandDispatchResult DispatchAssetCommand(
|
|
|
|
|
std::string_view commandId) override;
|
2026-04-15 19:30:58 +08:00
|
|
|
UIEditorHostCommandEvaluationResult EvaluateEditCommand(
|
|
|
|
|
std::string_view commandId) const override;
|
|
|
|
|
UIEditorHostCommandDispatchResult DispatchEditCommand(
|
|
|
|
|
std::string_view commandId) override;
|
2026-04-11 20:20:30 +08:00
|
|
|
|
|
|
|
|
private:
|
2026-04-15 08:24:06 +08:00
|
|
|
using BrowserModel = ::XCEngine::UI::Editor::App::ProjectBrowserModel;
|
2026-04-12 11:12:27 +08:00
|
|
|
using FolderEntry = BrowserModel::FolderEntry;
|
|
|
|
|
using AssetEntry = BrowserModel::AssetEntry;
|
2026-04-19 00:03:25 +08:00
|
|
|
using EditCommandTarget = EditorProjectRuntime::EditCommandTarget;
|
|
|
|
|
using AssetCommandTarget = EditorProjectRuntime::AssetCommandTarget;
|
|
|
|
|
enum class RenameSurface : std::uint8_t {
|
|
|
|
|
None = 0,
|
|
|
|
|
Tree,
|
|
|
|
|
Grid
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum class DropTargetSurface : std::uint8_t {
|
|
|
|
|
None = 0,
|
|
|
|
|
Tree,
|
|
|
|
|
Grid
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ContextMenuState {
|
|
|
|
|
bool open = false;
|
|
|
|
|
bool forceCurrentFolder = false;
|
|
|
|
|
::XCEngine::UI::UIPoint anchorPosition = {};
|
|
|
|
|
std::string targetItemId = {};
|
|
|
|
|
std::vector<Widgets::UIEditorMenuPopupItem> items = {};
|
|
|
|
|
Widgets::UIEditorMenuPopupLayout layout = {};
|
|
|
|
|
Widgets::UIEditorMenuPopupState widgetState = {};
|
2026-04-15 19:30:58 +08:00
|
|
|
};
|
2026-04-11 20:20:30 +08:00
|
|
|
|
|
|
|
|
struct BreadcrumbItemLayout {
|
|
|
|
|
std::string label = {};
|
|
|
|
|
std::string targetFolderId = {};
|
|
|
|
|
::XCEngine::UI::UIRect rect = {};
|
|
|
|
|
bool separator = false;
|
|
|
|
|
bool clickable = false;
|
|
|
|
|
bool current = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct AssetTileLayout {
|
|
|
|
|
std::size_t itemIndex = static_cast<std::size_t>(-1);
|
|
|
|
|
::XCEngine::UI::UIRect tileRect = {};
|
|
|
|
|
::XCEngine::UI::UIRect previewRect = {};
|
|
|
|
|
::XCEngine::UI::UIRect labelRect = {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Layout {
|
|
|
|
|
::XCEngine::UI::UIRect bounds = {};
|
|
|
|
|
::XCEngine::UI::UIRect leftPaneRect = {};
|
|
|
|
|
::XCEngine::UI::UIRect treeRect = {};
|
|
|
|
|
::XCEngine::UI::UIRect dividerRect = {};
|
|
|
|
|
::XCEngine::UI::UIRect rightPaneRect = {};
|
|
|
|
|
::XCEngine::UI::UIRect browserHeaderRect = {};
|
|
|
|
|
::XCEngine::UI::UIRect browserBodyRect = {};
|
|
|
|
|
::XCEngine::UI::UIRect gridRect = {};
|
|
|
|
|
std::vector<BreadcrumbItemLayout> breadcrumbItems = {};
|
|
|
|
|
std::vector<AssetTileLayout> assetTiles = {};
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-19 00:03:25 +08:00
|
|
|
EditorProjectRuntime* ResolveProjectRuntime();
|
|
|
|
|
const EditorProjectRuntime* ResolveProjectRuntime() const;
|
|
|
|
|
bool HasProjectRuntime() const;
|
|
|
|
|
BrowserModel& GetBrowserModel();
|
|
|
|
|
const BrowserModel& GetBrowserModel() const;
|
2026-04-11 20:20:30 +08:00
|
|
|
const FolderEntry* FindFolderEntry(std::string_view itemId) const;
|
2026-04-11 22:31:14 +08:00
|
|
|
const AssetEntry* FindAssetEntry(std::string_view itemId) const;
|
2026-04-19 00:03:25 +08:00
|
|
|
AssetCommandTarget ResolveAssetCommandTarget(
|
|
|
|
|
std::string_view explicitItemId = {},
|
|
|
|
|
bool forceCurrentFolder = false) const;
|
|
|
|
|
std::optional<EditCommandTarget> ResolveEditCommandTarget(
|
|
|
|
|
std::string_view explicitItemId = {},
|
|
|
|
|
bool forceCurrentFolder = false) const;
|
2026-04-11 20:20:30 +08:00
|
|
|
const UIEditorPanelContentHostPanelState* FindMountedProjectPanel(
|
|
|
|
|
const UIEditorPanelContentHostFrame& contentHostFrame) const;
|
|
|
|
|
Layout BuildLayout(const ::XCEngine::UI::UIRect& bounds) const;
|
|
|
|
|
std::size_t HitTestBreadcrumbItem(const ::XCEngine::UI::UIPoint& point) const;
|
|
|
|
|
std::size_t HitTestAssetTile(const ::XCEngine::UI::UIPoint& point) const;
|
2026-04-19 00:03:25 +08:00
|
|
|
std::string ResolveAssetDropTargetItemId(
|
|
|
|
|
const ::XCEngine::UI::UIPoint& point,
|
|
|
|
|
DropTargetSurface* surface = nullptr) const;
|
2026-04-11 20:20:30 +08:00
|
|
|
void SyncCurrentFolderSelection();
|
2026-04-11 22:31:14 +08:00
|
|
|
bool NavigateToFolder(std::string_view itemId, EventSource source = EventSource::None);
|
|
|
|
|
void EmitEvent(EventKind kind, EventSource source, const FolderEntry* folder);
|
|
|
|
|
void EmitEvent(EventKind kind, EventSource source, const AssetEntry* asset);
|
|
|
|
|
void EmitSelectionClearedEvent(EventSource source);
|
2026-04-19 00:03:25 +08:00
|
|
|
bool OpenProjectItem(std::string_view itemId, EventSource source);
|
|
|
|
|
void OpenContextMenu(
|
|
|
|
|
const ::XCEngine::UI::UIPoint& anchorPosition,
|
|
|
|
|
std::string_view targetItemId,
|
|
|
|
|
bool forceCurrentFolder);
|
|
|
|
|
void CloseContextMenu();
|
|
|
|
|
void RebuildContextMenu();
|
|
|
|
|
bool HandleContextMenuEvent(const ::XCEngine::UI::UIInputEvent& event);
|
|
|
|
|
bool DispatchContextMenuItem(std::string_view itemId);
|
|
|
|
|
void AppendContextMenu(::XCEngine::UI::UIDrawList& drawList) const;
|
|
|
|
|
void ClearRenameState();
|
|
|
|
|
void SyncAssetSelectionFromRuntime();
|
|
|
|
|
void QueueRenameSession(
|
|
|
|
|
std::string_view itemId,
|
|
|
|
|
RenameSurface surface);
|
|
|
|
|
bool TryStartQueuedRenameSession();
|
|
|
|
|
void UpdateRenameSession(
|
|
|
|
|
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents);
|
|
|
|
|
::XCEngine::UI::UIRect BuildRenameBounds(
|
|
|
|
|
std::string_view itemId,
|
|
|
|
|
RenameSurface surface) const;
|
|
|
|
|
const AssetEntry* GetSelectedAssetEntry() const;
|
|
|
|
|
const FolderEntry* GetSelectedFolderEntry() const;
|
2026-04-11 20:20:30 +08:00
|
|
|
void ResetTransientFrames();
|
2026-04-19 00:03:25 +08:00
|
|
|
std::vector<::XCEngine::UI::UIInputEvent> BuildTreeInteractionInputEvents(
|
|
|
|
|
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
|
|
|
|
const ::XCEngine::UI::UIRect& bounds,
|
|
|
|
|
bool allowInteraction,
|
|
|
|
|
bool panelActive) const;
|
2026-04-19 04:36:52 +08:00
|
|
|
void ClaimCommandFocus(
|
|
|
|
|
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
|
|
|
|
const ::XCEngine::UI::UIRect& bounds,
|
|
|
|
|
bool allowInteraction);
|
2026-04-19 00:03:25 +08:00
|
|
|
UIEditorHostCommandEvaluationResult EvaluateAssetCommand(
|
|
|
|
|
std::string_view commandId,
|
|
|
|
|
std::string_view explicitItemId,
|
|
|
|
|
bool forceCurrentFolder) const;
|
|
|
|
|
UIEditorHostCommandDispatchResult DispatchAssetCommand(
|
|
|
|
|
std::string_view commandId,
|
|
|
|
|
std::string_view explicitItemId,
|
|
|
|
|
bool forceCurrentFolder);
|
|
|
|
|
UIEditorHostCommandEvaluationResult EvaluateEditCommand(
|
|
|
|
|
std::string_view commandId,
|
|
|
|
|
std::string_view explicitItemId,
|
|
|
|
|
bool forceCurrentFolder) const;
|
|
|
|
|
UIEditorHostCommandDispatchResult DispatchEditCommand(
|
|
|
|
|
std::string_view commandId,
|
|
|
|
|
std::string_view explicitItemId,
|
|
|
|
|
bool forceCurrentFolder);
|
2026-04-11 20:20:30 +08:00
|
|
|
|
2026-04-19 00:03:25 +08:00
|
|
|
std::unique_ptr<EditorProjectRuntime> m_ownedProjectRuntime = {};
|
|
|
|
|
EditorProjectRuntime* m_projectRuntime = nullptr;
|
2026-04-19 04:36:52 +08:00
|
|
|
EditorCommandFocusService* m_commandFocusService = nullptr;
|
2026-04-15 08:24:06 +08:00
|
|
|
const BuiltInIcons* m_icons = nullptr;
|
2026-04-11 20:20:30 +08:00
|
|
|
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* m_textMeasurer = nullptr;
|
|
|
|
|
::XCEngine::UI::Widgets::UISelectionModel m_folderSelection = {};
|
|
|
|
|
::XCEngine::UI::Widgets::UIExpansionModel m_folderExpansion = {};
|
|
|
|
|
::XCEngine::UI::Widgets::UISelectionModel m_assetSelection = {};
|
2026-04-19 02:48:41 +08:00
|
|
|
Collections::GridDragDrop::State m_assetDragState = {};
|
|
|
|
|
Collections::TreeDragDrop::State m_treeDragState = {};
|
2026-04-11 20:20:30 +08:00
|
|
|
UIEditorTreeViewInteractionState m_treeInteractionState = {};
|
|
|
|
|
UIEditorTreeViewInteractionFrame m_treeFrame = {};
|
2026-04-19 00:03:25 +08:00
|
|
|
UIEditorInlineRenameSessionState m_renameState = {};
|
|
|
|
|
UIEditorInlineRenameSessionFrame m_renameFrame = {};
|
2026-04-11 22:31:14 +08:00
|
|
|
std::vector<Event> m_frameEvents = {};
|
2026-04-11 20:20:30 +08:00
|
|
|
Layout m_layout = {};
|
2026-04-19 00:03:25 +08:00
|
|
|
ContextMenuState m_contextMenu = {};
|
|
|
|
|
std::string m_pendingRenameItemId = {};
|
|
|
|
|
RenameSurface m_pendingRenameSurface = RenameSurface::None;
|
|
|
|
|
RenameSurface m_activeRenameSurface = RenameSurface::None;
|
|
|
|
|
DropTargetSurface m_assetDropTargetSurface = DropTargetSurface::None;
|
2026-04-11 20:20:30 +08:00
|
|
|
std::string m_hoveredAssetItemId = {};
|
|
|
|
|
std::string m_lastPrimaryClickedAssetId = {};
|
|
|
|
|
float m_navigationWidth = 248.0f;
|
|
|
|
|
std::uint64_t m_lastPrimaryClickTimeMs = 0u;
|
|
|
|
|
std::size_t m_hoveredBreadcrumbIndex = static_cast<std::size_t>(-1);
|
|
|
|
|
std::size_t m_pressedBreadcrumbIndex = static_cast<std::size_t>(-1);
|
|
|
|
|
bool m_visible = false;
|
|
|
|
|
bool m_splitterHovered = false;
|
|
|
|
|
bool m_splitterDragging = false;
|
|
|
|
|
bool m_requestPointerCapture = false;
|
|
|
|
|
bool m_requestPointerRelease = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|
2026-04-15 08:24:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|