#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(); m_projectPanel.Initialize(repoRoot); } 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 { using ::XCEngine::UI::UIDrawList; UIEditorShellComposeModel BuildShellComposeModelFromFrame( const UIEditorShellInteractionFrame& frame) { UIEditorShellComposeModel model = {}; model.menuBarItems = frame.request.menuBarItems; model.toolbarButtons = frame.model.toolbarButtons; model.statusSegments = frame.model.statusSegments; model.workspacePresentations = frame.model.workspacePresentations; return model; } void AppendShellPopups( UIDrawList& drawList, const UIEditorShellInteractionFrame& frame, const UIEditorShellInteractionPalette& palette, const UIEditorShellInteractionMetrics& metrics) { const std::size_t popupCount = (std::min)(frame.request.popupRequests.size(), frame.popupFrames.size()); for (std::size_t index = 0; index < popupCount; ++index) { const UIEditorShellInteractionPopupRequest& popupRequest = frame.request.popupRequests[index]; const UIEditorShellInteractionPopupFrame& popupFrame = frame.popupFrames[index]; Widgets::AppendUIEditorMenuPopupBackground( drawList, popupRequest.layout, popupRequest.widgetItems, popupFrame.popupState, palette.popupPalette, metrics.popupMetrics); Widgets::AppendUIEditorMenuPopupForeground( drawList, popupRequest.layout, popupRequest.widgetItems, popupFrame.popupState, palette.popupPalette, metrics.popupMetrics); } } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { using ::XCEngine::UI::UIDrawList; void EditorShellRuntime::RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext) { m_viewportHostService.RenderRequestedViewports(renderContext); } void EditorShellRuntime::Append(UIDrawList& drawList) const { const auto& metrics = ResolveUIEditorShellInteractionMetrics(); const auto& palette = ResolveUIEditorShellInteractionPalette(); const UIEditorShellComposeModel shellComposeModel = BuildShellComposeModelFromFrame(m_shellFrame); AppendUIEditorShellCompose( drawList, m_shellFrame.shellFrame, shellComposeModel, m_shellInteractionState.composeState, palette.shellPalette, metrics.shellMetrics, &m_builtInIcons); m_consolePanel.Append(drawList); m_colorPickerPanel.Append(drawList); m_hierarchyPanel.Append(drawList); m_inspectorPanel.Append(drawList); m_projectPanel.Append(drawList); m_sceneViewportFeature.Append(drawList); AppendUIEditorShellComposeOverlay( drawList, m_shellFrame.shellFrame, palette.shellPalette, metrics.shellMetrics); AppendShellPopups(drawList, m_shellFrame, palette, metrics); } } // 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; } PanelInputContext BuildHostedPanelInputContext( const UIEditorWorkspaceInteractionFrame& workspaceFrame, bool allowInteraction, std::string_view panelId) { PanelInputContext inputContext = {}; inputContext.allowInteraction = allowInteraction; inputContext.hasInputFocus = IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.inputOwner, panelId); inputContext.focusGained = !IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.previousInputOwner, panelId) && inputContext.hasInputFocus; inputContext.focusLost = IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.previousInputOwner, panelId) && !inputContext.hasInputFocus; return inputContext; } } // 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; } bool ShouldHostedContentYieldPointerStream( const UIEditorShellInteractionFrame& shellFrame, bool shellInteractiveCaptureActive) { if (shellInteractiveCaptureActive || shellFrame.result.requestPointerCapture || shellFrame.result.releasePointerCapture) { return true; } return shellFrame.result.workspaceResult.dockHostResult.hitTarget.kind == UIEditorDockHostHitTargetKind::SplitterHandle; } std::vector FilterHostedContentInputEventsForShellOwnership( const std::vector& inputEvents, bool shellOwnsPointerStream) { if (!shellOwnsPointerStream) { return inputEvents; } std::vector filteredEvents = {}; filteredEvents.reserve(inputEvents.size() + 1u); bool strippedPointerInput = false; UIInputEvent lastPointerEvent = {}; for (const UIInputEvent& event : inputEvents) { if (IsPointerInputEventType(event.type)) { strippedPointerInput = true; lastPointerEvent = event; continue; } filteredEvents.push_back(event); } if (strippedPointerInput) { UIInputEvent leaveEvent = {}; leaveEvent.type = UIInputEventType::PointerLeave; leaveEvent.position = lastPointerEvent.position; leaveEvent.modifiers = lastPointerEvent.modifiers; filteredEvents.push_back(leaveEvent); } 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; m_hierarchyPanel.SetSceneRuntime(&context.GetSceneRuntime()); m_hierarchyPanel.SetCommandFocusService(&context.GetCommandFocusService()); m_sceneEditCommandRoute.BindSceneRuntime(&context.GetSceneRuntime()); m_sceneViewportFeature.SetCommandFocusService(&context.GetCommandFocusService()); // Keep the previous render request available for readback-based picking during // this update, then refresh it again after camera/navigation state changes. m_sceneViewportFeature.SyncRenderRequest(context.GetSceneRuntime()); context.BindEditCommandRoutes( &m_hierarchyPanel, &m_projectPanel, &m_sceneEditCommandRoute, &m_inspectorPanel); context.SyncSessionFromWorkspace(workspaceController); UIEditorShellInteractionDefinition definition = context.BuildShellDefinition(workspaceController, captureText, shellVariant); 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)) { context.SyncSessionFromWorkspace(workspaceController); definition = context.BuildShellDefinition(workspaceController, captureText, shellVariant); m_shellFrame = UpdateUIEditorShellInteraction( m_shellInteractionState, workspaceController, bounds, definition, {}, context.GetShellServices(), metrics); } const bool shellOwnsHostedContentPointerStream = ShouldHostedContentYieldPointerStream(m_shellFrame, HasShellInteractiveCapture()); const std::vector<::XCEngine::UI::UIInputEvent> hostedContentEvents = FilterHostedContentInputEventsForShellOwnership( inputEvents, shellOwnsHostedContentPointerStream); m_sceneViewportFeature.Update( context.GetSceneRuntime(), m_shellInteractionState.workspaceInteractionState.composeState, m_shellFrame.workspaceInteractionFrame.composeFrame); ApplyViewportFramesToShellFrame(m_shellFrame, m_viewportHostService); context.SyncSessionFromWorkspace(workspaceController); context.UpdateStatusFromShellResult(workspaceController, m_shellFrame.result); const bool allowHostedInteraction = !m_shellFrame.result.workspaceInputSuppressed; const PanelInputContext hierarchyInputContext = BuildHostedPanelInputContext( m_shellFrame.workspaceInteractionFrame, allowHostedInteraction, kHierarchyPanelId); const PanelInputContext projectInputContext = BuildHostedPanelInputContext( m_shellFrame.workspaceInteractionFrame, allowHostedInteraction, kProjectPanelId); const PanelInputContext inspectorInputContext = BuildHostedPanelInputContext( m_shellFrame.workspaceInteractionFrame, allowHostedInteraction, kInspectorPanelId); const PanelInputContext colorPickerInputContext = BuildHostedPanelInputContext( m_shellFrame.workspaceInteractionFrame, allowHostedInteraction, kColorPickerPanelId); m_projectPanel.SetProjectRuntime(&context.GetProjectRuntime()); m_projectPanel.SetCommandFocusService(&context.GetCommandFocusService()); m_projectPanel.SetSystemInteractionHost(context.GetSystemInteractionHost()); m_inspectorPanel.SetCommandFocusService(&context.GetCommandFocusService()); m_hierarchyPanel.Update( m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, hierarchyInputContext); m_projectPanel.Update( m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, projectInputContext); m_traceEntries = SyncWorkspaceEvents(context, *this); m_colorPickerPanel.Update( context, m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, colorPickerInputContext); m_inspectorPanel.Update( context, m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, inspectorInputContext); context.SyncSessionFromCommandFocusService(); m_consolePanel.Update( context.GetSession(), m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); } } // 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); applyToViewportFrames(shellFrame.shellFrame.workspaceFrame.viewportFrames); } } // namespace XCEngine::UI::Editor::App