refactor(new_editor): tighten app dependency boundaries

This commit is contained in:
2026-04-19 02:48:41 +08:00
parent 7429f22fb1
commit c59cd83c38
86 changed files with 1754 additions and 1077 deletions

View File

@@ -1,35 +0,0 @@
#pragma once
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
#include <string_view>
namespace XCEngine::UI::Editor::App {
class EditorEditCommandRoute {
public:
virtual ~EditorEditCommandRoute() = default;
virtual UIEditorHostCommandEvaluationResult EvaluateEditCommand(
std::string_view commandId) const = 0;
virtual UIEditorHostCommandDispatchResult DispatchEditCommand(
std::string_view commandId) = 0;
virtual UIEditorHostCommandEvaluationResult EvaluateAssetCommand(
std::string_view commandId) const {
(void)commandId;
UIEditorHostCommandEvaluationResult result = {};
result.message = "Current panel does not expose asset commands.";
return result;
}
virtual UIEditorHostCommandDispatchResult DispatchAssetCommand(
std::string_view commandId) {
UIEditorHostCommandDispatchResult result = {};
result.message = EvaluateAssetCommand(commandId).message;
return result;
}
};
} // namespace XCEngine::UI::Editor::App

View File

@@ -1,58 +0,0 @@
#pragma once
#include <XCEditor/App/EditorEditCommandRoute.h>
#include <XCEditor/App/EditorSession.h>
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
#include <functional>
#include <string_view>
namespace XCEngine::UI::Editor::App {
class EditorHostCommandBridge : public UIEditorHostCommandHandler {
public:
void BindSession(EditorSession& session);
void BindEditCommandRoutes(
EditorEditCommandRoute* hierarchyRoute,
EditorEditCommandRoute* projectRoute,
EditorEditCommandRoute* sceneRoute = nullptr,
EditorEditCommandRoute* inspectorRoute = nullptr);
void SetExitRequestHandler(std::function<void()> handler);
UIEditorHostCommandEvaluationResult EvaluateHostCommand(
std::string_view commandId) const override;
UIEditorHostCommandDispatchResult DispatchHostCommand(
std::string_view commandId) override;
private:
UIEditorHostCommandEvaluationResult BuildDisabledResult(
std::string_view message) const;
UIEditorHostCommandEvaluationResult BuildExecutableResult(
std::string_view message) const;
UIEditorHostCommandEvaluationResult EvaluateFileCommand(
std::string_view commandId) const;
UIEditorHostCommandEvaluationResult EvaluateAssetCommand(
std::string_view commandId) const;
UIEditorHostCommandEvaluationResult EvaluateRunCommand(
std::string_view commandId) const;
UIEditorHostCommandEvaluationResult EvaluateScriptCommand(
std::string_view commandId) const;
UIEditorHostCommandEvaluationResult EvaluateEditCommand(
std::string_view commandId) const;
UIEditorHostCommandDispatchResult DispatchAssetCommand(
std::string_view commandId);
UIEditorHostCommandDispatchResult DispatchEditCommand(
std::string_view commandId);
UIEditorHostCommandEvaluationResult EvaluateUnsupportedHostCommand(
std::string_view commandId) const;
EditorEditCommandRoute* ResolveEditCommandRoute(EditorActionRoute route) const;
EditorSession* m_session = nullptr;
EditorEditCommandRoute* m_hierarchyRoute = nullptr;
EditorEditCommandRoute* m_projectRoute = nullptr;
EditorEditCommandRoute* m_sceneRoute = nullptr;
EditorEditCommandRoute* m_inspectorRoute = nullptr;
std::function<void()> m_requestExit = {};
};
} // namespace XCEngine::UI::Editor::App

View File

@@ -1,25 +0,0 @@
#pragma once
#include <string_view>
namespace XCEngine::UI::Editor::App {
inline constexpr std::string_view kHierarchyPanelId = "hierarchy";
inline constexpr std::string_view kScenePanelId = "scene";
inline constexpr std::string_view kGamePanelId = "game";
inline constexpr std::string_view kInspectorPanelId = "inspector";
inline constexpr std::string_view kConsolePanelId = "console";
inline constexpr std::string_view kProjectPanelId = "project";
inline constexpr std::string_view kHierarchyPanelTitle = "Hierarchy";
inline constexpr std::string_view kScenePanelTitle = "Scene";
inline constexpr std::string_view kGamePanelTitle = "Game";
inline constexpr std::string_view kInspectorPanelTitle = "Inspector";
inline constexpr std::string_view kConsolePanelTitle = "Console";
inline constexpr std::string_view kProjectPanelTitle = "Project";
[[nodiscard]] constexpr bool IsEditorViewportPanelId(std::string_view panelId) {
return panelId == kScenePanelId || panelId == kGamePanelId;
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -1,73 +0,0 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor {
class UIEditorWorkspaceController;
} // namespace XCEngine::UI::Editor
namespace XCEngine::UI::Editor::App {
enum class EditorRuntimeMode : std::uint8_t {
Edit = 0,
Play,
Paused
};
enum class EditorActionRoute : std::uint8_t {
None = 0,
Hierarchy,
Project,
Inspector,
Console,
Scene,
Game
};
enum class EditorSelectionKind : std::uint8_t {
None = 0,
HierarchyNode,
ProjectItem
};
struct EditorSelectionState {
EditorSelectionKind kind = EditorSelectionKind::None;
std::string itemId = {};
std::string displayName = {};
std::filesystem::path absolutePath = {};
bool directory = false;
std::uint64_t stamp = 0u;
};
struct EditorConsoleEntry {
std::string channel = {};
std::string message = {};
};
struct EditorSession {
std::filesystem::path repoRoot = {};
std::filesystem::path projectRoot = {};
std::string activePanelId = {};
EditorRuntimeMode runtimeMode = EditorRuntimeMode::Edit;
EditorActionRoute activeRoute = EditorActionRoute::None;
EditorSelectionState selection = {};
std::vector<EditorConsoleEntry> consoleEntries = {};
};
std::string_view GetEditorRuntimeModeName(EditorRuntimeMode mode);
std::string_view GetEditorActionRouteName(EditorActionRoute route);
std::string_view GetEditorSelectionKindName(EditorSelectionKind kind);
EditorActionRoute ResolveEditorActionRoute(std::string_view panelId);
void SyncEditorSessionFromWorkspace(
EditorSession& session,
const UIEditorWorkspaceController& controller);
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,242 @@
#pragma once
#include <XCEngine/UI/Types.h>
#include <string>
#include <vector>
namespace XCEngine::UI::Editor::Collections::DragDropInteraction {
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIPointerButton;
inline constexpr float kDefaultDragThreshold = 4.0f;
struct State {
std::string armedItemId = {};
std::string draggedItemId = {};
std::string dropTargetItemId = {};
UIPoint pressPosition = {};
bool armed = false;
bool dragging = false;
bool validDropTarget = false;
bool requestPointerCapture = false;
bool requestPointerRelease = false;
};
struct PreviewState {
std::string armedItemId = {};
UIPoint pressPosition = {};
bool armed = false;
bool dragging = false;
};
struct ProcessResult {
bool selectionForced = false;
bool dropCommitted = false;
std::string draggedItemId = {};
std::string dropTargetItemId = {};
};
template <typename StateLike>
inline void ResetTransientRequests(StateLike& state) {
state.requestPointerCapture = false;
state.requestPointerRelease = false;
}
template <typename StateLike>
inline bool HasActivePointerCapture(const StateLike& state) {
return state.dragging;
}
inline float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) {
const float dx = lhs.x - rhs.x;
const float dy = lhs.y - rhs.y;
return dx * dx + dy * dy;
}
template <typename StateLike>
inline PreviewState BuildPreviewState(const StateLike& state) {
PreviewState preview = {};
preview.armedItemId = state.armedItemId;
preview.pressPosition = state.pressPosition;
preview.armed = state.armed;
preview.dragging = state.dragging;
return preview;
}
template <typename ResolveDraggableItem>
inline bool ProcessPreviewEvent(
PreviewState& preview,
const UIInputEvent& event,
ResolveDraggableItem&& resolveDraggableItem,
float dragThreshold = kDefaultDragThreshold) {
bool suppress = false;
switch (event.type) {
case UIInputEventType::PointerButtonDown:
if (event.pointerButton == UIPointerButton::Left) {
preview.armedItemId = resolveDraggableItem(event.position);
preview.pressPosition = event.position;
preview.armed = !preview.armedItemId.empty();
if (!preview.armed) {
preview.armedItemId.clear();
}
}
if (preview.dragging) {
suppress = true;
}
break;
case UIInputEventType::PointerMove:
if (preview.dragging) {
suppress = true;
break;
}
if (preview.armed &&
ComputeSquaredDistance(event.position, preview.pressPosition) >=
dragThreshold * dragThreshold) {
preview.dragging = !preview.armedItemId.empty();
suppress = preview.dragging;
}
break;
case UIInputEventType::PointerButtonUp:
if (event.pointerButton == UIPointerButton::Left) {
if (preview.dragging) {
suppress = true;
preview.dragging = false;
}
preview.armed = false;
preview.armedItemId.clear();
} else if (preview.dragging) {
suppress = true;
}
break;
case UIInputEventType::PointerLeave:
if (preview.dragging) {
suppress = true;
}
break;
case UIInputEventType::FocusLost:
preview.armed = false;
preview.dragging = false;
preview.armedItemId.clear();
break;
default:
if (preview.dragging &&
(event.type == UIInputEventType::PointerWheel ||
event.type == UIInputEventType::PointerEnter)) {
suppress = true;
}
break;
}
return suppress;
}
template <typename StateLike, typename Callbacks>
inline void ResetInteractionSession(StateLike& state, Callbacks& callbacks) {
state.armed = false;
state.dragging = false;
state.armedItemId.clear();
state.draggedItemId.clear();
callbacks.ResetDropTarget(state);
}
template <typename StateLike, typename Callbacks>
ProcessResult ProcessInputEvents(
StateLike& state,
const std::vector<UIInputEvent>& inputEvents,
Callbacks& callbacks,
float dragThreshold = kDefaultDragThreshold) {
ProcessResult result = {};
for (const UIInputEvent& event : inputEvents) {
switch (event.type) {
case UIInputEventType::PointerButtonDown:
if (event.pointerButton == UIPointerButton::Left) {
state.draggedItemId.clear();
callbacks.ResetDropTarget(state);
state.armedItemId = callbacks.ResolveDraggableItem(event.position);
state.pressPosition = event.position;
state.armed = !state.armedItemId.empty();
if (!state.armed) {
state.armedItemId.clear();
}
}
break;
case UIInputEventType::PointerMove:
if (state.armed &&
!state.dragging &&
ComputeSquaredDistance(event.position, state.pressPosition) >=
dragThreshold * dragThreshold) {
state.dragging = !state.armedItemId.empty();
state.draggedItemId = state.armedItemId;
callbacks.ResetDropTarget(state);
if (state.dragging) {
state.requestPointerCapture = true;
if (!callbacks.IsItemSelected(state.draggedItemId)) {
result.selectionForced = callbacks.SelectDraggedItem(state.draggedItemId);
}
}
}
if (state.dragging) {
callbacks.ResetDropTarget(state);
callbacks.UpdateDropTarget(state, state.draggedItemId, event.position);
}
break;
case UIInputEventType::PointerButtonUp:
if (event.pointerButton != UIPointerButton::Left) {
break;
}
if (state.dragging) {
if (state.validDropTarget) {
result.draggedItemId = state.draggedItemId;
result.dropTargetItemId = state.dropTargetItemId;
result.dropCommitted = callbacks.CommitDrop(state, result);
}
ResetInteractionSession(state, callbacks);
state.requestPointerRelease = true;
} else {
state.armed = false;
state.armedItemId.clear();
}
break;
case UIInputEventType::PointerLeave:
if (state.dragging) {
callbacks.ResetDropTarget(state);
}
break;
case UIInputEventType::FocusLost:
{
const bool requestPointerRelease = state.dragging;
ResetInteractionSession(state, callbacks);
state.pressPosition = {};
state.requestPointerCapture = false;
state.requestPointerRelease = requestPointerRelease;
}
break;
default:
break;
}
}
return result;
}
} // namespace XCEngine::UI::Editor::Collections::DragDropInteraction

View File

@@ -0,0 +1,73 @@
#pragma once
#include <XCEditor/Collections/UIEditorDragDropInteraction.h>
#include <string_view>
namespace XCEngine::UI::Editor::Collections::GridDragDrop {
using ::XCEngine::UI::UIPoint;
using DragDropInteraction::kDefaultDragThreshold;
using DragDropInteraction::ProcessResult;
using DragDropInteraction::State;
inline void ResetTransientRequests(State& state) {
DragDropInteraction::ResetTransientRequests(state);
}
inline bool HasActivePointerCapture(const State& state) {
return DragDropInteraction::HasActivePointerCapture(state);
}
template <typename Callbacks>
ProcessResult ProcessInputEvents(
State& state,
const std::vector<DragDropInteraction::UIInputEvent>& inputEvents,
Callbacks& callbacks,
float dragThreshold = kDefaultDragThreshold) {
struct AdaptedCallbacks {
Callbacks& callbacks;
std::string ResolveDraggableItem(const UIPoint& point) const {
return callbacks.ResolveDraggableItem(point);
}
void ResetDropTarget(State& state) const {
state.dropTargetItemId.clear();
state.validDropTarget = false;
}
void UpdateDropTarget(
State& state,
std::string_view draggedItemId,
const UIPoint& point) const {
state.dropTargetItemId =
callbacks.ResolveDropTargetItem(draggedItemId, point);
state.validDropTarget =
!state.dropTargetItemId.empty() &&
callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId);
}
bool IsItemSelected(std::string_view itemId) const {
return callbacks.IsItemSelected(itemId);
}
bool SelectDraggedItem(std::string_view itemId) const {
return callbacks.SelectDraggedItem(itemId);
}
bool CommitDrop(State& state, ProcessResult&) const {
return callbacks.CommitDropOnItem(
state.draggedItemId,
state.dropTargetItemId);
}
} adaptedCallbacks{ callbacks };
return DragDropInteraction::ProcessInputEvents(
state,
inputEvents,
adaptedCallbacks,
dragThreshold);
}
} // namespace XCEngine::UI::Editor::Collections::GridDragDrop

View File

@@ -0,0 +1,201 @@
#pragma once
#include <XCEditor/Collections/UIEditorDragDropInteraction.h>
#include <XCEditor/Collections/UIEditorTreeView.h>
#include <XCEngine/UI/Types.h>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor::Collections::TreeDragDrop {
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
using DragDropInteraction::kDefaultDragThreshold;
using Widgets::HitTestUIEditorTreeView;
using Widgets::UIEditorTreeViewHitTarget;
using Widgets::UIEditorTreeViewHitTargetKind;
using Widgets::UIEditorTreeViewInvalidIndex;
struct State : DragDropInteraction::State {
bool dropToRoot = false;
};
struct ProcessResult : DragDropInteraction::ProcessResult {
bool droppedToRoot = false;
};
inline void ResetTransientRequests(State& state) {
DragDropInteraction::ResetTransientRequests(state);
}
inline bool HasActivePointerCapture(const State& state) {
return DragDropInteraction::HasActivePointerCapture(state);
}
inline bool ContainsPoint(const UIRect& rect, const UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
inline const Widgets::UIEditorTreeViewItem* ResolveHitItem(
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const UIPoint& point,
UIEditorTreeViewHitTarget* hitTargetOutput = nullptr) {
const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point);
if (hitTargetOutput != nullptr) {
*hitTargetOutput = hitTarget;
}
if (hitTarget.itemIndex >= items.size()) {
return nullptr;
}
return &items[hitTarget.itemIndex];
}
inline std::size_t FindVisibleIndexForItemId(
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
std::string_view itemId) {
for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) {
const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex];
if (itemIndex < items.size() && items[itemIndex].itemId == itemId) {
return visibleIndex;
}
}
return UIEditorTreeViewInvalidIndex;
}
inline std::vector<DragDropInteraction::UIInputEvent> BuildInteractionInputEvents(
const State& state,
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const std::vector<DragDropInteraction::UIInputEvent>& rawEvents,
float dragThreshold = kDefaultDragThreshold) {
auto resolveDraggableItem =
[&layout, &items](const UIPoint& point) -> std::string {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(layout, items, point, &hitTarget);
if (hitItem != nullptr &&
hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) {
return hitItem->itemId;
}
return {};
};
DragDropInteraction::PreviewState preview =
DragDropInteraction::BuildPreviewState(state);
std::vector<DragDropInteraction::UIInputEvent> filteredEvents = {};
filteredEvents.reserve(rawEvents.size());
for (const DragDropInteraction::UIInputEvent& event : rawEvents) {
if (!DragDropInteraction::ProcessPreviewEvent(
preview,
event,
resolveDraggableItem,
dragThreshold)) {
filteredEvents.push_back(event);
}
}
return filteredEvents;
}
template <typename Callbacks>
ProcessResult ProcessInputEvents(
State& state,
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const std::vector<DragDropInteraction::UIInputEvent>& inputEvents,
const UIRect& bounds,
Callbacks& callbacks,
float dragThreshold = kDefaultDragThreshold) {
struct AdaptedCallbacks {
const Widgets::UIEditorTreeViewLayout& layout;
const std::vector<Widgets::UIEditorTreeViewItem>& items;
const UIRect& bounds;
Callbacks& callbacks;
bool droppedToRoot = false;
std::string ResolveDraggableItem(const UIPoint& point) const {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(layout, items, point, &hitTarget);
if (hitItem != nullptr &&
hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) {
return hitItem->itemId;
}
return {};
}
void ResetDropTarget(State& state) const {
state.dropTargetItemId.clear();
state.dropToRoot = false;
state.validDropTarget = false;
}
void UpdateDropTarget(
State& state,
std::string_view draggedItemId,
const UIPoint& point) const {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(layout, items, point, &hitTarget);
if (hitItem != nullptr &&
(hitTarget.kind == UIEditorTreeViewHitTargetKind::Row ||
hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) {
state.dropTargetItemId = hitItem->itemId;
state.validDropTarget =
callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId);
return;
}
if (ContainsPoint(bounds, point)) {
state.dropToRoot = true;
state.validDropTarget = callbacks.CanDropToRoot(draggedItemId);
}
}
bool IsItemSelected(std::string_view itemId) const {
return callbacks.IsItemSelected(itemId);
}
bool SelectDraggedItem(std::string_view itemId) const {
return callbacks.SelectDraggedItem(itemId);
}
bool CommitDrop(State& state, DragDropInteraction::ProcessResult&) {
droppedToRoot = state.dropToRoot;
return state.dropToRoot
? callbacks.CommitDropToRoot(state.draggedItemId)
: callbacks.CommitDropOnItem(
state.draggedItemId,
state.dropTargetItemId);
}
} adaptedCallbacks{ layout, items, bounds, callbacks };
ProcessResult result = {};
const DragDropInteraction::ProcessResult interactionResult =
DragDropInteraction::ProcessInputEvents(
state,
inputEvents,
adaptedCallbacks,
dragThreshold);
result.selectionForced = interactionResult.selectionForced;
result.dropCommitted = interactionResult.dropCommitted;
result.draggedItemId = interactionResult.draggedItemId;
result.dropTargetItemId = interactionResult.dropTargetItemId;
result.droppedToRoot = adaptedCallbacks.droppedToRoot;
return result;
}
} // namespace XCEngine::UI::Editor::Collections::TreeDragDrop

View File

@@ -45,8 +45,6 @@ struct UIEditorPanelRegistryValidationResult {
}
};
UIEditorPanelRegistry BuildEditorFoundationPanelRegistry();
const UIEditorPanelDescriptor* FindUIEditorPanelDescriptor(
const UIEditorPanelRegistry& registry,
std::string_view panelId);

View File

@@ -20,7 +20,7 @@ struct EditorShellShortcutAsset {
};
struct EditorShellAsset {
std::string screenId = "editor.shell";
std::string screenId = {};
std::filesystem::path documentPath = {};
std::filesystem::path captureRootPath = {};
UIEditorPanelRegistry panelRegistry = {};
@@ -55,7 +55,6 @@ struct EditorShellAssetValidationResult {
}
};
EditorShellAsset BuildEditorFoundationShellAsset(const std::filesystem::path& repoRoot);
UIEditorShortcutManager BuildEditorShellShortcutManager(const EditorShellAsset& asset);
EditorShellAssetValidationResult ValidateEditorShellAsset(const EditorShellAsset& asset);

View File

@@ -57,8 +57,6 @@ struct UIEditorWorkspaceVisiblePanel {
bool placeholder = false;
};
UIEditorWorkspaceModel BuildEditorFoundationWorkspaceModel();
UIEditorWorkspaceNode BuildUIEditorWorkspacePanel(
std::string nodeId,
std::string panelId,