#include "ProductEditorWorkspace.h" #include "Workspace/ProductEditorWorkspaceEventRouter.h" #include #include #include namespace XCEngine::UI::Editor::App { namespace { using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using Widgets::UIEditorDockHostHitTargetKind; bool IsProductViewportPanel(std::string_view panelId) { return panelId == "scene" || panelId == "game"; } ProductViewportKind ResolveProductViewportKind(std::string_view panelId) { return panelId == "game" ? ProductViewportKind::Game : ProductViewportKind::Scene; } void ApplyViewportFrameToPresentation( const ProductViewportFrame& 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 ProductViewportFrame& 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; } void ApplyViewportFramesToShellFrame( UIEditorShellInteractionFrame& shellFrame, ProductViewportHostService& viewportHostService) { auto applyToViewportFrames = [&](std::vector& viewportFrames) { for (UIEditorWorkspaceViewportComposeFrame& viewportComposeFrame : viewportFrames) { if (!IsProductViewportPanel(viewportComposeFrame.panelId)) { continue; } const ProductViewportFrame viewportFrame = viewportHostService.RequestViewport( ResolveProductViewportKind(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); } std::vector BuildWorkspacePresentations( const UIEditorShellInteractionDefinition& definition) { return definition.workspacePresentations; } 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); } } 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 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; } } 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 void ProductEditorWorkspace::Initialize( const std::filesystem::path& repoRoot, Host::NativeRenderer& renderer) { m_builtInIcons.Initialize(renderer); m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons); m_projectPanel.SetBuiltInIcons(&m_builtInIcons); m_projectPanel.SetTextMeasurer(&renderer); m_hierarchyPanel.Initialize(); m_projectPanel.Initialize(repoRoot); } void ProductEditorWorkspace::AttachViewportWindowRenderer(Host::D3D12WindowRenderer& renderer) { m_viewportHostService.AttachWindowRenderer(renderer); } void ProductEditorWorkspace::DetachViewportWindowRenderer() { m_viewportHostService.DetachWindowRenderer(); } void ProductEditorWorkspace::SetViewportSurfacePresentationEnabled(bool enabled) { m_viewportHostService.SetSurfacePresentationEnabled(enabled); } void ProductEditorWorkspace::Shutdown() { m_shellFrame = {}; m_shellInteractionState = {}; m_traceEntries.clear(); m_viewportHostService.Shutdown(); m_builtInIcons.Shutdown(); } void ProductEditorWorkspace::ResetInteractionState() { m_shellFrame = {}; m_shellInteractionState = {}; m_traceEntries.clear(); m_hierarchyPanel.ResetInteractionState(); m_projectPanel.ResetInteractionState(); } void ProductEditorWorkspace::Update( ProductEditorContext& context, UIEditorWorkspaceController& workspaceController, const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, std::string_view captureText, ProductEditorShellVariant shellVariant) { const auto& metrics = ResolveUIEditorShellInteractionMetrics(); context.SyncSessionFromWorkspace(workspaceController); UIEditorShellInteractionDefinition definition = context.BuildShellDefinition(workspaceController, captureText, shellVariant); m_viewportHostService.BeginFrame(); definition.workspacePresentations = BuildWorkspacePresentations(definition); const std::vector shellEvents = HasHostedContentCapture() ? FilterShellInputEventsForHostedContentCapture(inputEvents) : inputEvents; m_shellFrame = UpdateUIEditorShellInteraction( m_shellInteractionState, workspaceController, bounds, definition, shellEvents, context.GetShellServices(), metrics); const bool shellOwnsHostedContentPointerStream = ShouldHostedContentYieldPointerStream(m_shellFrame, HasShellInteractiveCapture()); const std::vector hostedContentEvents = FilterHostedContentInputEventsForShellOwnership( inputEvents, shellOwnsHostedContentPointerStream); ApplyViewportFramesToShellFrame(m_shellFrame, m_viewportHostService); context.SyncSessionFromWorkspace(workspaceController); context.UpdateStatusFromShellResult(workspaceController, m_shellFrame.result); const std::string& activePanelId = workspaceController.GetWorkspace().activePanelId; m_hierarchyPanel.Update( m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, !m_shellFrame.result.workspaceInputSuppressed, activePanelId == "hierarchy"); m_projectPanel.Update( m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, !m_shellFrame.result.workspaceInputSuppressed, activePanelId == "project"); m_traceEntries = ConsumeProductEditorWorkspaceEvents(context, *this); m_inspectorPanel.Update( context.GetSession(), m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); m_consolePanel.Update( context.GetSession(), m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); } void ProductEditorWorkspace::RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext) { m_viewportHostService.RenderRequestedViewports(renderContext); } void ProductEditorWorkspace::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_consolePanel.Append(drawList); m_hierarchyPanel.Append(drawList); m_inspectorPanel.Append(drawList); m_projectPanel.Append(drawList); AppendShellPopups(drawList, m_shellFrame, palette, metrics); } const UIEditorShellInteractionFrame& ProductEditorWorkspace::GetShellFrame() const { return m_shellFrame; } const UIEditorShellInteractionState& ProductEditorWorkspace::GetShellInteractionState() const { return m_shellInteractionState; } const std::vector& ProductEditorWorkspace::GetTraceEntries() const { return m_traceEntries; } const std::vector& ProductEditorWorkspace::GetHierarchyPanelEvents() const { return m_hierarchyPanel.GetFrameEvents(); } const std::vector& ProductEditorWorkspace::GetProjectPanelEvents() const { return m_projectPanel.GetFrameEvents(); } const std::string& ProductEditorWorkspace::GetBuiltInIconError() const { return m_builtInIcons.GetLastError(); } ProductProjectPanel::CursorKind ProductEditorWorkspace::GetHostedContentCursorKind() const { return m_projectPanel.GetCursorKind(); } Widgets::UIEditorDockHostCursorKind ProductEditorWorkspace::GetDockCursorKind() const { return Widgets::ResolveUIEditorDockHostCursorKind( m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout); } bool ProductEditorWorkspace::WantsHostPointerCapture() const { return m_hierarchyPanel.WantsHostPointerCapture() || m_projectPanel.WantsHostPointerCapture(); } bool ProductEditorWorkspace::WantsHostPointerRelease() const { return (m_hierarchyPanel.WantsHostPointerRelease() || m_projectPanel.WantsHostPointerRelease()) && !HasHostedContentCapture(); } bool ProductEditorWorkspace::HasHostedContentCapture() const { return m_hierarchyPanel.HasActivePointerCapture() || m_projectPanel.HasActivePointerCapture(); } bool ProductEditorWorkspace::HasShellInteractiveCapture() const { if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) { return true; } if (!m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.activeTabDragNodeId.empty()) { return true; } for (const auto& panelState : m_shellInteractionState.workspaceInteractionState.composeState.panelStates) { if (panelState.viewportShellState.inputBridgeState.captured) { return true; } } return false; } bool ProductEditorWorkspace::HasInteractiveCapture() const { return HasShellInteractiveCapture() || HasHostedContentCapture(); } } // namespace XCEngine::UI::Editor::App