Add editor panel content host contract

This commit is contained in:
2026-04-07 14:03:52 +08:00
parent b2ab516228
commit 1f140c4bab
14 changed files with 1292 additions and 37 deletions

View File

@@ -18,6 +18,7 @@ add_library(XCUIEditorLib STATIC
src/Core/UIEditorDockHostInteraction.cpp
src/Core/UIEditorMenuModel.cpp
src/Core/UIEditorMenuSession.cpp
src/Core/UIEditorPanelContentHost.cpp
src/Core/UIEditorPanelHostLifecycle.cpp
src/Core/UIEditorPanelRegistry.cpp
src/Core/UIEditorShellCompose.cpp

View File

@@ -0,0 +1,88 @@
#pragma once
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Widgets/UIEditorDockHost.h>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor {
enum class UIEditorPanelContentHostEventKind : std::uint8_t {
Mounted = 0,
Unmounted,
BoundsChanged
};
std::string_view GetUIEditorPanelContentHostEventKindName(
UIEditorPanelContentHostEventKind kind);
bool IsUIEditorPanelPresentationExternallyHosted(UIEditorPanelPresentationKind kind);
struct UIEditorPanelContentHostBinding {
std::string panelId = {};
UIEditorPanelPresentationKind kind = UIEditorPanelPresentationKind::Placeholder;
};
struct UIEditorPanelContentHostMountRequest {
std::string panelId = {};
UIEditorPanelPresentationKind kind = UIEditorPanelPresentationKind::Placeholder;
::XCEngine::UI::UIRect bounds = {};
};
struct UIEditorPanelContentHostRequest {
std::vector<UIEditorPanelContentHostMountRequest> mountRequests = {};
};
struct UIEditorPanelContentHostPanelState {
std::string panelId = {};
UIEditorPanelPresentationKind kind = UIEditorPanelPresentationKind::Placeholder;
bool mounted = false;
::XCEngine::UI::UIRect bounds = {};
};
struct UIEditorPanelContentHostState {
std::vector<UIEditorPanelContentHostPanelState> panelStates = {};
};
struct UIEditorPanelContentHostEvent {
UIEditorPanelContentHostEventKind kind = UIEditorPanelContentHostEventKind::Mounted;
std::string panelId = {};
UIEditorPanelPresentationKind presentationKind = UIEditorPanelPresentationKind::Placeholder;
::XCEngine::UI::UIRect bounds = {};
};
struct UIEditorPanelContentHostFrame {
std::vector<UIEditorPanelContentHostPanelState> panelStates = {};
std::vector<UIEditorPanelContentHostEvent> events = {};
};
const UIEditorPanelContentHostMountRequest* FindUIEditorPanelContentHostMountRequest(
const UIEditorPanelContentHostRequest& request,
std::string_view panelId);
const UIEditorPanelContentHostPanelState* FindUIEditorPanelContentHostPanelState(
const UIEditorPanelContentHostState& state,
std::string_view panelId);
const UIEditorPanelContentHostPanelState* FindUIEditorPanelContentHostPanelState(
const UIEditorPanelContentHostFrame& frame,
std::string_view panelId);
UIEditorPanelContentHostRequest ResolveUIEditorPanelContentHostRequest(
const Widgets::UIEditorDockHostLayout& dockHostLayout,
const UIEditorPanelRegistry& panelRegistry,
const std::vector<UIEditorPanelContentHostBinding>& bindings);
UIEditorPanelContentHostFrame UpdateUIEditorPanelContentHost(
UIEditorPanelContentHostState& state,
const UIEditorPanelContentHostRequest& request,
const UIEditorPanelRegistry& panelRegistry,
const std::vector<UIEditorPanelContentHostBinding>& bindings);
std::vector<std::string> CollectMountedUIEditorPanelContentHostPanelIds(
const UIEditorPanelContentHostFrame& frame);
} // namespace XCEngine::UI::Editor

View File

@@ -9,7 +9,8 @@ namespace XCEngine::UI::Editor {
enum class UIEditorPanelPresentationKind : std::uint8_t {
Placeholder = 0,
ViewportShell
ViewportShell,
HostedContent
};
struct UIEditorPanelDescriptor {

View File

@@ -1,5 +1,6 @@
#pragma once
#include <XCEditor/Core/UIEditorPanelContentHost.h>
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Core/UIEditorViewportShell.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
@@ -25,6 +26,7 @@ struct UIEditorWorkspacePanelPresentationState {
};
struct UIEditorWorkspaceComposeState {
UIEditorPanelContentHostState contentHostState = {};
std::vector<UIEditorWorkspacePanelPresentationState> panelStates = {};
};
@@ -36,6 +38,7 @@ struct UIEditorWorkspaceViewportComposeRequest {
struct UIEditorWorkspaceComposeRequest {
Widgets::UIEditorDockHostLayout dockHostLayout = {};
UIEditorPanelContentHostRequest contentHostRequest = {};
std::vector<UIEditorWorkspaceViewportComposeRequest> viewportRequests = {};
};
@@ -48,6 +51,7 @@ struct UIEditorWorkspaceViewportComposeFrame {
struct UIEditorWorkspaceComposeFrame {
Widgets::UIEditorDockHostLayout dockHostLayout = {};
UIEditorPanelContentHostFrame contentHostFrame = {};
std::vector<UIEditorWorkspaceViewportComposeFrame> viewportFrames = {};
};

View File

@@ -0,0 +1,241 @@
#include <XCEditor/Core/UIEditorPanelContentHost.h>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
const ::XCEngine::UI::UIRect* FindVisiblePanelBodyRect(
const Widgets::UIEditorDockHostLayout& layout,
std::string_view panelId) {
for (const Widgets::UIEditorDockHostPanelLayout& panel : layout.panels) {
if (panel.panelId == panelId) {
return &panel.frameLayout.bodyRect;
}
}
for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) {
if (tabStack.selectedPanelId == panelId) {
return &tabStack.contentFrameLayout.bodyRect;
}
}
return nullptr;
}
bool AreRectsEquivalent(
const ::XCEngine::UI::UIRect& lhs,
const ::XCEngine::UI::UIRect& rhs) {
return lhs.x == rhs.x &&
lhs.y == rhs.y &&
lhs.width == rhs.width &&
lhs.height == rhs.height;
}
bool SupportsBinding(
const UIEditorPanelRegistry& panelRegistry,
const UIEditorPanelContentHostBinding& binding) {
if (!IsUIEditorPanelPresentationExternallyHosted(binding.kind)) {
return false;
}
const UIEditorPanelDescriptor* descriptor =
FindUIEditorPanelDescriptor(panelRegistry, binding.panelId);
return descriptor != nullptr && descriptor->presentationKind == binding.kind;
}
UIEditorPanelContentHostPanelState* FindMutablePanelState(
UIEditorPanelContentHostState& state,
std::string_view panelId) {
for (UIEditorPanelContentHostPanelState& panelState : state.panelStates) {
if (panelState.panelId == panelId) {
return &panelState;
}
}
return nullptr;
}
UIEditorPanelContentHostPanelState& EnsurePanelState(
UIEditorPanelContentHostState& state,
std::string_view panelId,
UIEditorPanelPresentationKind kind) {
if (UIEditorPanelContentHostPanelState* existing =
FindMutablePanelState(state, panelId)) {
existing->kind = kind;
return *existing;
}
UIEditorPanelContentHostPanelState panelState = {};
panelState.panelId = std::string(panelId);
panelState.kind = kind;
state.panelStates.push_back(std::move(panelState));
return state.panelStates.back();
}
} // namespace
std::string_view GetUIEditorPanelContentHostEventKindName(
UIEditorPanelContentHostEventKind kind) {
switch (kind) {
case UIEditorPanelContentHostEventKind::Mounted:
return "Mounted";
case UIEditorPanelContentHostEventKind::Unmounted:
return "Unmounted";
case UIEditorPanelContentHostEventKind::BoundsChanged:
return "BoundsChanged";
}
return "Unknown";
}
bool IsUIEditorPanelPresentationExternallyHosted(UIEditorPanelPresentationKind kind) {
return kind == UIEditorPanelPresentationKind::ViewportShell ||
kind == UIEditorPanelPresentationKind::HostedContent;
}
const UIEditorPanelContentHostMountRequest* FindUIEditorPanelContentHostMountRequest(
const UIEditorPanelContentHostRequest& request,
std::string_view panelId) {
for (const UIEditorPanelContentHostMountRequest& mountRequest : request.mountRequests) {
if (mountRequest.panelId == panelId) {
return &mountRequest;
}
}
return nullptr;
}
const UIEditorPanelContentHostPanelState* FindUIEditorPanelContentHostPanelState(
const UIEditorPanelContentHostState& state,
std::string_view panelId) {
for (const UIEditorPanelContentHostPanelState& panelState : state.panelStates) {
if (panelState.panelId == panelId) {
return &panelState;
}
}
return nullptr;
}
const UIEditorPanelContentHostPanelState* FindUIEditorPanelContentHostPanelState(
const UIEditorPanelContentHostFrame& frame,
std::string_view panelId) {
for (const UIEditorPanelContentHostPanelState& panelState : frame.panelStates) {
if (panelState.panelId == panelId) {
return &panelState;
}
}
return nullptr;
}
UIEditorPanelContentHostRequest ResolveUIEditorPanelContentHostRequest(
const Widgets::UIEditorDockHostLayout& dockHostLayout,
const UIEditorPanelRegistry& panelRegistry,
const std::vector<UIEditorPanelContentHostBinding>& bindings) {
UIEditorPanelContentHostRequest request = {};
for (const UIEditorPanelContentHostBinding& binding : bindings) {
if (!SupportsBinding(panelRegistry, binding)) {
continue;
}
const ::XCEngine::UI::UIRect* bodyRect =
FindVisiblePanelBodyRect(dockHostLayout, binding.panelId);
if (bodyRect == nullptr) {
continue;
}
UIEditorPanelContentHostMountRequest mountRequest = {};
mountRequest.panelId = binding.panelId;
mountRequest.kind = binding.kind;
mountRequest.bounds = *bodyRect;
request.mountRequests.push_back(std::move(mountRequest));
}
return request;
}
UIEditorPanelContentHostFrame UpdateUIEditorPanelContentHost(
UIEditorPanelContentHostState& state,
const UIEditorPanelContentHostRequest& request,
const UIEditorPanelRegistry& panelRegistry,
const std::vector<UIEditorPanelContentHostBinding>& bindings) {
UIEditorPanelContentHostFrame frame = {};
std::unordered_set<std::string> supportedPanelIds = {};
for (const UIEditorPanelContentHostBinding& binding : bindings) {
if (!SupportsBinding(panelRegistry, binding)) {
continue;
}
supportedPanelIds.insert(binding.panelId);
EnsurePanelState(state, binding.panelId, binding.kind);
}
state.panelStates.erase(
std::remove_if(
state.panelStates.begin(),
state.panelStates.end(),
[&](const UIEditorPanelContentHostPanelState& panelState) {
return !supportedPanelIds.contains(panelState.panelId);
}),
state.panelStates.end());
for (UIEditorPanelContentHostPanelState& panelState : state.panelStates) {
const UIEditorPanelContentHostMountRequest* mountRequest =
FindUIEditorPanelContentHostMountRequest(request, panelState.panelId);
const bool wasMounted = panelState.mounted;
const ::XCEngine::UI::UIRect previousBounds = panelState.bounds;
panelState.mounted = mountRequest != nullptr;
panelState.bounds = mountRequest != nullptr ? mountRequest->bounds : ::XCEngine::UI::UIRect{};
if (mountRequest != nullptr) {
panelState.kind = mountRequest->kind;
}
if (!wasMounted && panelState.mounted) {
frame.events.push_back({
UIEditorPanelContentHostEventKind::Mounted,
panelState.panelId,
panelState.kind,
panelState.bounds
});
} else if (wasMounted && !panelState.mounted) {
frame.events.push_back({
UIEditorPanelContentHostEventKind::Unmounted,
panelState.panelId,
panelState.kind,
previousBounds
});
} else if (panelState.mounted &&
!AreRectsEquivalent(previousBounds, panelState.bounds)) {
frame.events.push_back({
UIEditorPanelContentHostEventKind::BoundsChanged,
panelState.panelId,
panelState.kind,
panelState.bounds
});
}
}
frame.panelStates = state.panelStates;
return frame;
}
std::vector<std::string> CollectMountedUIEditorPanelContentHostPanelIds(
const UIEditorPanelContentHostFrame& frame) {
std::vector<std::string> panelIds = {};
for (const UIEditorPanelContentHostPanelState& panelState : frame.panelStates) {
if (panelState.mounted) {
panelIds.push_back(panelState.panelId);
}
}
return panelIds;
}
} // namespace XCEngine::UI::Editor

View File

@@ -51,6 +51,28 @@ bool SupportsExternalViewportPresentation(
descriptor->presentationKind == UIEditorPanelPresentationKind::ViewportShell;
}
std::vector<UIEditorPanelContentHostBinding> BuildContentHostBindings(
const UIEditorPanelRegistry& panelRegistry,
const std::vector<UIEditorWorkspacePanelPresentationModel>& presentations) {
std::vector<UIEditorPanelContentHostBinding> bindings = {};
bindings.reserve(presentations.size());
for (const UIEditorWorkspacePanelPresentationModel& presentation : presentations) {
const UIEditorPanelDescriptor* descriptor =
FindUIEditorPanelDescriptor(panelRegistry, presentation.panelId);
if (descriptor == nullptr ||
descriptor->presentationKind != presentation.kind ||
!IsUIEditorPanelPresentationExternallyHosted(presentation.kind)) {
continue;
}
UIEditorPanelContentHostBinding binding = {};
binding.panelId = presentation.panelId;
binding.kind = presentation.kind;
bindings.push_back(std::move(binding));
}
return bindings;
}
UIEditorWorkspacePanelPresentationState& EnsurePanelState(
UIEditorWorkspaceComposeState& state,
std::string_view panelId) {
@@ -99,24 +121,6 @@ void TrimObsoleteViewportPresentationStates(
state.panelStates.end());
}
const ::XCEngine::UI::UIRect* FindVisiblePanelBodyRect(
const Widgets::UIEditorDockHostLayout& layout,
std::string_view panelId) {
for (const auto& panel : layout.panels) {
if (panel.panelId == panelId) {
return &panel.frameLayout.bodyRect;
}
}
for (const auto& tabStack : layout.tabStacks) {
if (tabStack.selectedPanelId == panelId) {
return &tabStack.contentFrameLayout.bodyRect;
}
}
return nullptr;
}
} // namespace
const UIEditorWorkspacePanelPresentationModel* FindUIEditorWorkspacePanelPresentationModel(
@@ -177,23 +181,32 @@ UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest(
session,
dockHostState,
dockHostMetrics);
const std::vector<UIEditorPanelContentHostBinding> contentHostBindings =
BuildContentHostBindings(panelRegistry, presentations);
request.contentHostRequest = ResolveUIEditorPanelContentHostRequest(
request.dockHostLayout,
panelRegistry,
contentHostBindings);
for (const UIEditorWorkspacePanelPresentationModel& presentation : presentations) {
if (!SupportsExternalViewportPresentation(panelRegistry, presentation)) {
for (const UIEditorPanelContentHostMountRequest& mountRequest :
request.contentHostRequest.mountRequests) {
if (mountRequest.kind != UIEditorPanelPresentationKind::ViewportShell) {
continue;
}
const auto* bodyRect = FindVisiblePanelBodyRect(request.dockHostLayout, presentation.panelId);
if (bodyRect == nullptr) {
const UIEditorWorkspacePanelPresentationModel* presentation =
FindUIEditorWorkspacePanelPresentationModel(presentations, mountRequest.panelId);
if (presentation == nullptr ||
!SupportsExternalViewportPresentation(panelRegistry, *presentation)) {
continue;
}
UIEditorWorkspaceViewportComposeRequest viewportRequest = {};
viewportRequest.panelId = presentation.panelId;
viewportRequest.bounds = *bodyRect;
viewportRequest.panelId = mountRequest.panelId;
viewportRequest.bounds = mountRequest.bounds;
viewportRequest.viewportShellRequest = ResolveUIEditorViewportShellRequest(
*bodyRect,
presentation.viewportShellModel.spec);
mountRequest.bounds,
presentation->viewportShellModel.spec);
request.viewportRequests.push_back(std::move(viewportRequest));
}
@@ -218,6 +231,18 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
session,
dockHostState,
dockHostMetrics);
const std::vector<UIEditorPanelContentHostBinding> contentHostBindings =
BuildContentHostBindings(panelRegistry, presentations);
const UIEditorPanelContentHostRequest contentHostRequest =
ResolveUIEditorPanelContentHostRequest(
frame.dockHostLayout,
panelRegistry,
contentHostBindings);
frame.contentHostFrame = UpdateUIEditorPanelContentHost(
state.contentHostState,
contentHostRequest,
panelRegistry,
contentHostBindings);
TrimObsoleteViewportPresentationStates(state, panelRegistry, presentations);
for (const UIEditorWorkspacePanelPresentationModel& presentation : presentations) {
@@ -225,8 +250,9 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
continue;
}
const auto* bodyRect = FindVisiblePanelBodyRect(frame.dockHostLayout, presentation.panelId);
if (bodyRect == nullptr) {
const UIEditorPanelContentHostPanelState* contentHostPanelState =
FindUIEditorPanelContentHostPanelState(frame.contentHostFrame, presentation.panelId);
if (contentHostPanelState == nullptr || !contentHostPanelState->mounted) {
ResetHiddenViewportPresentationState(state, presentation.panelId);
continue;
}
@@ -236,11 +262,11 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
UIEditorWorkspaceViewportComposeFrame viewportFrame = {};
viewportFrame.panelId = presentation.panelId;
viewportFrame.bounds = *bodyRect;
viewportFrame.bounds = contentHostPanelState->bounds;
viewportFrame.viewportShellModel = presentation.viewportShellModel;
viewportFrame.viewportShellFrame = UpdateUIEditorViewportShell(
panelState.viewportShellState,
*bodyRect,
contentHostPanelState->bounds,
presentation.viewportShellModel,
inputEvents);
frame.viewportFrames.push_back(std::move(viewportFrame));
@@ -251,12 +277,7 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
std::vector<std::string> CollectUIEditorWorkspaceComposeExternalBodyPanelIds(
const UIEditorWorkspaceComposeFrame& frame) {
std::vector<std::string> panelIds = {};
panelIds.reserve(frame.viewportFrames.size());
for (const UIEditorWorkspaceViewportComposeFrame& viewportFrame : frame.viewportFrames) {
panelIds.push_back(viewportFrame.panelId);
}
return panelIds;
return CollectMountedUIEditorPanelContentHostPanelIds(frame.contentHostFrame);
}
void AppendUIEditorWorkspaceCompose(