#include "Composition/EditorShellRuntime.h" #include "Ports/TexturePort.h" #include "Ports/ViewportRenderPort.h" #include "Composition/EditorContext.h" #include "Composition/EditorPanelIds.h" #include #include #include #include "Features/PanelInputContext.h" namespace XCEngine::UI::Editor::App { void ApplyViewportFramesToShellFrame( UIEditorShellInteractionFrame& shellFrame, ViewportHostService& viewportHostService); std::vector<::XCEngine::UI::UIInputEvent> FilterShellInputEventsForHostedContentCapture( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); bool ShouldHostedContentYieldPointerStream( const UIEditorShellInteractionFrame& shellFrame, bool shellInteractiveCaptureActive); std::vector<::XCEngine::UI::UIInputEvent> FilterHostedContentInputEventsForShellOwnership( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, bool shellOwnsPointerStream); UIEditorShellComposeModel BuildShellComposeModelFromFrame( const UIEditorShellInteractionFrame& frame); void AppendShellPopups( ::XCEngine::UI::UIDrawList& drawList, const UIEditorShellInteractionFrame& frame, const UIEditorShellInteractionPalette& palette, const UIEditorShellInteractionMetrics& metrics); void EditorShellRuntime::Initialize( const std::filesystem::path& repoRoot, Ports::TexturePort& textureHost, UIEditorTextMeasurer& textMeasurer) { m_textureHost = &textureHost; m_builtInIcons.Initialize(textureHost); m_sceneViewportFeature.Initialize( repoRoot, textureHost, &m_builtInIcons, m_viewportHostService); m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons); m_projectPanel.SetBuiltInIcons(&m_builtInIcons); m_projectPanel.SetTextMeasurer(&textMeasurer); m_hierarchyPanel.Initialize(); } void EditorShellRuntime::AttachViewportWindowRenderer(Ports::ViewportRenderPort& renderer) { m_viewportHostService.AttachWindowRenderer(renderer); } void EditorShellRuntime::DetachViewportWindowRenderer() { m_viewportHostService.DetachWindowRenderer(); } void EditorShellRuntime::SetViewportSurfacePresentationEnabled(bool enabled) { m_viewportHostService.SetSurfacePresentationEnabled(enabled); } void EditorShellRuntime::Shutdown() { m_shellFrame = {}; m_shellInteractionState = {}; m_splitterDragCorrectionState = {}; m_traceEntries.clear(); m_sceneEditCommandRoute = {}; if (m_textureHost != nullptr) { m_sceneViewportFeature.Shutdown(*m_textureHost, m_viewportHostService); m_builtInIcons.Shutdown(); m_textureHost = nullptr; } else { m_builtInIcons.Shutdown(); } m_viewportHostService.Shutdown(); } void EditorShellRuntime::ResetInteractionState() { m_shellFrame = {}; m_shellInteractionState = {}; m_splitterDragCorrectionState = {}; m_traceEntries.clear(); m_sceneViewportFeature.ResetInteractionState(); m_hierarchyPanel.ResetInteractionState(); m_colorPickerPanel.ResetInteractionState(); m_projectPanel.ResetInteractionState(); } const UIEditorShellInteractionFrame& EditorShellRuntime::GetShellFrame() const { return m_shellFrame; } const UIEditorShellInteractionState& EditorShellRuntime::GetShellInteractionState() const { return m_shellInteractionState; } const std::vector& EditorShellRuntime::GetTraceEntries() const { return m_traceEntries; } const std::vector& EditorShellRuntime::GetHierarchyPanelEvents() const { return m_hierarchyPanel.GetFrameEvents(); } const std::vector& EditorShellRuntime::GetProjectPanelEvents() const { return m_projectPanel.GetFrameEvents(); } const std::string& EditorShellRuntime::GetBuiltInIconError() const { return m_builtInIcons.GetLastError(); } void EditorShellRuntime::SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview) { m_shellInteractionState.workspaceInteractionState.dockHostInteractionState .dockHostState.dropPreview = preview; } void EditorShellRuntime::ClearExternalDockHostDropPreview() { m_shellInteractionState.workspaceInteractionState.dockHostInteractionState .dockHostState.dropPreview = {}; } ProjectPanel::CursorKind EditorShellRuntime::GetHostedContentCursorKind() const { return m_projectPanel.GetCursorKind(); } Widgets::UIEditorDockHostCursorKind EditorShellRuntime::GetDockCursorKind() const { return Widgets::ResolveUIEditorDockHostCursorKind( m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout); } bool EditorShellRuntime::TryResolveDockTabDragHotspot( std::string_view nodeId, std::string_view panelId, const ::XCEngine::UI::UIPoint& point, ::XCEngine::UI::UIPoint& outHotspot) const { return TryResolveUIEditorDockHostTabDragHotspot( m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout, nodeId, panelId, point, outHotspot); } UIEditorDockHostTabDropTarget EditorShellRuntime::ResolveDockTabDropTarget( const ::XCEngine::UI::UIPoint& point) const { return ResolveUIEditorDockHostTabDropTarget( m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout, point); } EditorShellPointerOwner EditorShellRuntime::GetPointerOwner() const { const auto& dockHostInteractionState = m_shellInteractionState.workspaceInteractionState.dockHostInteractionState; if (dockHostInteractionState.splitterDragState.active || !dockHostInteractionState.activeTabDragNodeId.empty()) { return EditorShellPointerOwner::DockHost; } for (const auto& panelState : m_shellInteractionState.workspaceInteractionState.composeState.panelStates) { if (!panelState.viewportShellState.inputBridgeState.captured) { continue; } if (panelState.panelId == kGamePanelId) { return EditorShellPointerOwner::GameViewport; } return EditorShellPointerOwner::SceneViewport; } if (m_hierarchyPanel.HasActivePointerCapture()) { return EditorShellPointerOwner::HierarchyPanel; } if (m_projectPanel.HasActivePointerCapture()) { return EditorShellPointerOwner::ProjectPanel; } return EditorShellPointerOwner::None; } bool EditorShellRuntime::WantsHostPointerCapture() const { return IsHostedContentPointerOwner(GetPointerOwner()); } bool EditorShellRuntime::WantsHostPointerRelease() const { return !IsHostedContentPointerOwner(GetPointerOwner()); } bool EditorShellRuntime::HasHostedContentCapture() const { return IsHostedContentPointerOwner(GetPointerOwner()); } bool EditorShellRuntime::HasShellInteractiveCapture() const { return IsShellPointerOwner(GetPointerOwner()); } bool EditorShellRuntime::HasInteractiveCapture() const { return GetPointerOwner() != EditorShellPointerOwner::None; } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { void EditorShellRuntime::RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext) { m_viewportHostService.RenderRequestedViewports(renderContext); } void EditorShellRuntime::Append(::XCEngine::UI::UIDrawData& drawData) const { m_drawComposer.Append( EditorShellDrawComposerContext{ .shellFrame = m_shellFrame, .shellInteractionState = m_shellInteractionState, .builtInIcons = m_builtInIcons, .consolePanel = m_consolePanel, .colorPickerPanel = m_colorPickerPanel, .hierarchyPanel = m_hierarchyPanel, .inspectorPanel = m_inspectorPanel, .projectPanel = m_projectPanel, .sceneViewportFeature = m_sceneViewportFeature, }, drawData); } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { namespace { using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using Widgets::UIEditorDockHostHitTargetKind; bool IsPointerInputEventType(UIInputEventType type) { switch (type) { case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: case UIInputEventType::PointerLeave: case UIInputEventType::PointerButtonDown: case UIInputEventType::PointerButtonUp: case UIInputEventType::PointerWheel: return true; default: return false; } } const UIEditorPanelDescriptor* ResolveSingleVisibleToolWindowPanelDescriptor( const UIEditorWorkspaceController& workspaceController) { const UIEditorWorkspaceNode& root = workspaceController.GetWorkspace().root; if (root.kind != UIEditorWorkspaceNodeKind::TabStack) { return nullptr; } const UIEditorWorkspaceSession& session = workspaceController.GetSession(); const UIEditorPanelRegistry& panelRegistry = workspaceController.GetPanelRegistry(); const UIEditorPanelDescriptor* visibleDescriptor = nullptr; std::size_t visibleCount = 0u; for (const UIEditorWorkspaceNode& child : root.children) { if (child.kind != UIEditorWorkspaceNodeKind::Panel) { continue; } const UIEditorPanelSessionState* panelState = FindUIEditorPanelSessionState(session, child.panel.panelId); if (panelState == nullptr || !panelState->open || !panelState->visible) { continue; } const UIEditorPanelDescriptor* descriptor = FindUIEditorPanelDescriptor(panelRegistry, child.panel.panelId); if (descriptor == nullptr) { return nullptr; } ++visibleCount; visibleDescriptor = descriptor; if (visibleCount > 1u) { return nullptr; } } return visibleDescriptor != nullptr && visibleDescriptor->toolWindow ? visibleDescriptor : nullptr; } } // namespace std::vector FilterShellInputEventsForHostedContentCapture( const std::vector& inputEvents) { std::vector filteredEvents = {}; filteredEvents.reserve(inputEvents.size()); for (const UIInputEvent& event : inputEvents) { switch (event.type) { case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: case UIInputEventType::PointerLeave: case UIInputEventType::PointerButtonDown: case UIInputEventType::PointerButtonUp: case UIInputEventType::PointerWheel: break; default: filteredEvents.push_back(event); break; } } return filteredEvents; } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { void EditorShellRuntime::Update( EditorContext& context, UIEditorWorkspaceController& workspaceController, const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, std::string_view captureText, EditorShellVariant shellVariant, bool useDetachedTitleBarTabStrip, float detachedTitleBarTabHeight, float detachedWindowChromeHeight) { UIEditorShellInteractionMetrics metrics = ResolveUIEditorShellInteractionMetrics(); if (const UIEditorPanelDescriptor* toolWindowDescriptor = ResolveSingleVisibleToolWindowPanelDescriptor(workspaceController); toolWindowDescriptor != nullptr) { metrics.shellMetrics.dockHostMetrics.tabStripMetrics.layoutMetrics.headerHeight = 0.0f; const float minimumContentWidth = toolWindowDescriptor->minimumDetachedWindowSize.width > 0.0f ? toolWindowDescriptor->minimumDetachedWindowSize.width : metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize.width; const float minimumContentHeight = toolWindowDescriptor->minimumDetachedWindowSize.height > detachedWindowChromeHeight ? toolWindowDescriptor->minimumDetachedWindowSize.height - detachedWindowChromeHeight : metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize.height; metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize = ::XCEngine::UI::UISize(minimumContentWidth, minimumContentHeight); metrics.shellMetrics.dockHostMetrics.minimumTabContentBodySize = metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize; } if (useDetachedTitleBarTabStrip && detachedTitleBarTabHeight > 0.0f) { metrics.shellMetrics.dockHostMetrics.tabStripMetrics.layoutMetrics.headerHeight = detachedTitleBarTabHeight; } const UIEditorWorkspaceLayoutSnapshot preUpdateSnapshot = workspaceController.CaptureLayoutSnapshot(); const Widgets::UIEditorDockHostLayout preUpdateDockLayout = m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout; UIEditorShellInteractionDefinition definition = m_sessionCoordinator.PrepareShellDefinition( EditorShellSessionCoordinatorContext{ .context = context, .workspaceController = workspaceController, .captureText = captureText, .shellVariant = shellVariant, .hierarchyPanel = m_hierarchyPanel, .inspectorPanel = m_inspectorPanel, .projectPanel = m_projectPanel, .sceneEditCommandRoute = m_sceneEditCommandRoute, .sceneViewportFeature = m_sceneViewportFeature, }); m_viewportHostService.BeginFrame(); const std::vector<::XCEngine::UI::UIInputEvent> shellEvents = HasHostedContentCapture() ? FilterShellInputEventsForHostedContentCapture(inputEvents) : inputEvents; m_shellFrame = UpdateUIEditorShellInteraction( m_shellInteractionState, workspaceController, bounds, definition, shellEvents, context.GetShellServices(), metrics); if (TryApplyUIEditorWorkspaceSplitterDragCorrection( m_splitterDragCorrectionState, m_shellInteractionState.workspaceInteractionState.dockHostInteractionState, preUpdateSnapshot, preUpdateDockLayout, workspaceController, metrics.shellMetrics.dockHostMetrics)) { definition = m_sessionCoordinator.PrepareShellDefinition( EditorShellSessionCoordinatorContext{ .context = context, .workspaceController = workspaceController, .captureText = captureText, .shellVariant = shellVariant, .hierarchyPanel = m_hierarchyPanel, .inspectorPanel = m_inspectorPanel, .projectPanel = m_projectPanel, .sceneEditCommandRoute = m_sceneEditCommandRoute, .sceneViewportFeature = m_sceneViewportFeature, }); m_shellFrame = UpdateUIEditorShellInteraction( m_shellInteractionState, workspaceController, bounds, definition, {}, context.GetShellServices(), metrics); } ApplyViewportFramesToShellFrame(m_shellFrame, m_viewportHostService); m_sessionCoordinator.FinalizeFrame(context, workspaceController, m_shellFrame.result); m_hostedPanelCoordinator.Update( EditorShellHostedPanelCoordinatorContext{ .context = context, .shellFrame = m_shellFrame, .shellInteractionState = m_shellInteractionState, .inputEvents = inputEvents, .shellInteractiveCaptureActive = HasShellInteractiveCapture(), .consolePanel = m_consolePanel, .colorPickerPanel = m_colorPickerPanel, .hierarchyPanel = m_hierarchyPanel, .inspectorPanel = m_inspectorPanel, .projectPanel = m_projectPanel, .sceneViewportFeature = m_sceneViewportFeature, }); m_traceEntries = SyncWorkspaceEvents(context, *this); } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { namespace { bool IsViewportPanel(std::string_view panelId) { return IsEditorViewportPanelId(panelId); } void ApplyViewportFrameToPresentation( const ViewportFrame& viewportFrame, UIEditorWorkspacePanelPresentationModel& presentation) { presentation.viewportShellModel.frame.texture = viewportFrame.texture; presentation.viewportShellModel.frame.requestedSize = viewportFrame.requestedSize; presentation.viewportShellModel.frame.presentedSize = viewportFrame.renderSize; presentation.viewportShellModel.frame.hasTexture = viewportFrame.hasTexture; presentation.viewportShellModel.frame.statusText = viewportFrame.statusText; } void ApplyViewportFrameToShellModel( const ViewportFrame& viewportFrame, UIEditorViewportShellModel& shellModel) { shellModel.frame.texture = viewportFrame.texture; shellModel.frame.requestedSize = viewportFrame.requestedSize; shellModel.frame.presentedSize = viewportFrame.renderSize; shellModel.frame.hasTexture = viewportFrame.hasTexture; shellModel.frame.statusText = viewportFrame.statusText; } UIEditorWorkspacePanelPresentationModel* FindMutableWorkspacePresentation( std::vector& presentations, std::string_view panelId) { for (UIEditorWorkspacePanelPresentationModel& presentation : presentations) { if (presentation.panelId == panelId) { return &presentation; } } return nullptr; } } // namespace void ApplyViewportFramesToShellFrame( UIEditorShellInteractionFrame& shellFrame, ViewportHostService& viewportHostService) { auto applyToViewportFrames = [&](std::vector& viewportFrames) { for (UIEditorWorkspaceViewportComposeFrame& viewportComposeFrame : viewportFrames) { if (!IsViewportPanel(viewportComposeFrame.panelId)) { continue; } const ViewportFrame viewportFrame = viewportHostService.RequestViewport( viewportComposeFrame.panelId, viewportComposeFrame.viewportShellFrame.requestedViewportSize); ApplyViewportFrameToShellModel( viewportFrame, viewportComposeFrame.viewportShellModel); if (UIEditorWorkspacePanelPresentationModel* presentation = FindMutableWorkspacePresentation( shellFrame.model.workspacePresentations, viewportComposeFrame.panelId); presentation != nullptr) { ApplyViewportFrameToPresentation(viewportFrame, *presentation); } } }; applyToViewportFrames(shellFrame.workspaceInteractionFrame.composeFrame.viewportFrames); } } // namespace XCEngine::UI::Editor::App