#include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" #include "Platform/Win32/EditorWindowInternalState.h" #include "Platform/Win32/EditorWindowRuntimeInternal.h" #include "Platform/Win32/EditorWindowStyle.h" #include "Composition/EditorShellPointerInteraction.h" #include "State/EditorContext.h" #include #include #include #include #include namespace XCEngine::UI::Editor::App { using namespace EditorWindowInternal; using ::XCEngine::UI::UIDrawData; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIInputModifiers; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIPointerButton; using ::XCEngine::UI::UIRect; namespace { std::string DescribeInputEventType(const UIInputEvent& event) { switch (event.type) { case UIInputEventType::PointerMove: return "PointerMove"; case UIInputEventType::PointerEnter: return "PointerEnter"; case UIInputEventType::PointerLeave: return "PointerLeave"; case UIInputEventType::PointerButtonDown: return "PointerDown"; case UIInputEventType::PointerButtonUp: return "PointerUp"; case UIInputEventType::PointerWheel: return "PointerWheel"; case UIInputEventType::KeyDown: return "KeyDown"; case UIInputEventType::KeyUp: return "KeyUp"; case UIInputEventType::Character: return "Character"; case UIInputEventType::FocusGained: return "FocusGained"; case UIInputEventType::FocusLost: return "FocusLost"; default: return "Unknown"; } } bool HasPendingPointerStateReconciliationEvent( const std::vector& events) { for (const UIInputEvent& event : events) { switch (event.type) { case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: case UIInputEventType::PointerButtonDown: case UIInputEventType::PointerButtonUp: case UIInputEventType::PointerWheel: case UIInputEventType::FocusLost: return true; case UIInputEventType::PointerLeave: case UIInputEventType::KeyDown: case UIInputEventType::KeyUp: case UIInputEventType::Character: case UIInputEventType::FocusGained: case UIInputEventType::None: default: break; } } return false; } std::uint8_t ButtonMask(UIPointerButton button) { switch (button) { case UIPointerButton::Left: return 1u << 0u; case UIPointerButton::Right: return 1u << 1u; case UIPointerButton::Middle: return 1u << 2u; case UIPointerButton::X1: return 1u << 3u; case UIPointerButton::X2: return 1u << 4u; case UIPointerButton::None: default: return 0u; } } std::uint8_t ButtonMaskFromModifiers(const UIInputModifiers& modifiers) { std::uint8_t mask = 0u; if (modifiers.leftMouse) { mask |= ButtonMask(UIPointerButton::Left); } if (modifiers.rightMouse) { mask |= ButtonMask(UIPointerButton::Right); } if (modifiers.middleMouse) { mask |= ButtonMask(UIPointerButton::Middle); } if (modifiers.x1Mouse) { mask |= ButtonMask(UIPointerButton::X1); } if (modifiers.x2Mouse) { mask |= ButtonMask(UIPointerButton::X2); } return mask; } std::uint8_t ResolveExpectedShellCaptureButtons( const EditorShellRuntime& shellRuntime) { std::uint8_t expectedButtons = 0u; const auto& shellState = shellRuntime.GetShellInteractionState(); const auto& dockHostState = shellState.workspaceInteractionState.dockHostInteractionState; if (dockHostState.splitterDragState.active || !dockHostState.activeTabDragNodeId.empty()) { expectedButtons |= ButtonMask(UIPointerButton::Left); } for (const auto& panelState : shellState.workspaceInteractionState.composeState.panelStates) { const auto& inputBridgeState = panelState.viewportShellState.inputBridgeState; if (inputBridgeState.captured) { expectedButtons |= ButtonMask(inputBridgeState.captureButton); } } return expectedButtons; } } // namespace EditorWindowFrameTransferRequests EditorWindow::RenderFrame( EditorContext& editorContext, bool globalTabDragActive) { if (!m_state->render.ready || m_state->window.hwnd == nullptr) { return {}; } UINT pixelWidth = 0u; UINT pixelHeight = 0u; if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) { return {}; } const float width = PixelsToDips(static_cast(pixelWidth)); const float height = PixelsToDips(static_cast(pixelHeight)); const UIRect workspaceBounds = ResolveWorkspaceBounds(width, height); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("XCEditorShell"); drawList.AddFilledRect( UIRect(0.0f, 0.0f, width, height), kShellSurfaceColor); EditorWindowFrameTransferRequests transferRequests = {}; if (editorContext.IsValid()) { transferRequests = RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawList); } else { RenderInvalidFrame(editorContext, drawList); } AppendBorderlessWindowChrome(drawList, width); const Host::D3D12WindowRenderLoopPresentResult presentResult = m_state->render.windowRenderLoop.Present(drawData); if (!presentResult.warning.empty()) { LogRuntimeTrace("present", presentResult.warning); } m_state->render.autoScreenshot.CaptureIfRequested( m_state->render.renderer, drawData, pixelWidth, pixelHeight, presentResult.framePresented); return transferRequests; } EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage( EditorContext& editorContext, bool globalTabDragActive) { if (!m_state->render.ready || m_state->window.hwnd == nullptr) { return {}; } PAINTSTRUCT paintStruct = {}; BeginPaint(m_state->window.hwnd, &paintStruct); const EditorWindowFrameTransferRequests transferRequests = RenderFrame(editorContext, globalTabDragActive); EndPaint(m_state->window.hwnd, &paintStruct); return transferRequests; } UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const { if (!IsBorderlessWindowEnabled()) { return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips); } if (ShouldUseDetachedTitleBarTabStrip()) { return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips); } const float titleBarHeight = (std::min)(kBorderlessTitleBarHeightDips, clientHeightDips); return UIRect( 0.0f, titleBarHeight, clientWidthDips, (std::max)(0.0f, clientHeightDips - titleBarHeight)); } EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( EditorContext& editorContext, bool globalTabDragActive, const UIRect& workspaceBounds, UIDrawList& drawList) { SyncShellCapturedPointerButtonsFromSystemState(); std::vector frameEvents = std::move(m_state->input.pendingEvents); m_state->input.pendingEvents.clear(); if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) { LogRuntimeTrace( "input", DescribeInputEvents(frameEvents) + " | " + editorContext.DescribeWorkspaceState( GetWorkspaceController(), m_state->composition.shellRuntime.GetShellInteractionState())); } const Host::D3D12WindowRenderLoopFrameContext frameContext = m_state->render.windowRenderLoop.BeginFrame(); if (!frameContext.warning.empty()) { LogRuntimeTrace("viewport", frameContext.warning); } editorContext.AttachTextMeasurer(m_state->render.renderer); const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(); UIEditorWorkspaceController& workspaceController = GetMutableWorkspaceController(); m_state->composition.shellRuntime.Update( editorContext, workspaceController, workspaceBounds, frameEvents, BuildCaptureStatusText(), m_state->window.primary ? EditorShellVariant::Primary : EditorShellVariant::DetachedWindow, useDetachedTitleBarTabStrip, useDetachedTitleBarTabStrip ? kBorderlessTitleBarHeightDips : 0.0f); const UIEditorShellInteractionFrame& shellFrame = m_state->composition.shellRuntime.GetShellFrame(); const UIEditorDockHostInteractionState& dockHostInteractionState = m_state->composition.shellRuntime .GetShellInteractionState() .workspaceInteractionState .dockHostInteractionState; LogFrameInteractionTrace(editorContext, frameEvents, shellFrame); const EditorWindowFrameTransferRequests transferRequests = BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame); ApplyShellRuntimePointerCapture(); for (const WorkspaceTraceEntry& entry : m_state->composition.shellRuntime.GetTraceEntries()) { LogRuntimeTrace(entry.channel, entry.message); } ApplyCurrentCursor(); m_state->composition.shellRuntime.Append(drawList); if (frameContext.canRenderViewports) { m_state->composition.shellRuntime.RenderRequestedViewports(frameContext.renderContext); } return transferRequests; } void EditorWindow::RenderInvalidFrame( EditorContext& editorContext, UIDrawList& drawList) const { drawList.AddText( UIPoint(28.0f, 28.0f), "Editor shell asset invalid.", EditorWindowInternal::kShellTextColor, 16.0f); drawList.AddText( UIPoint(28.0f, 54.0f), editorContext.GetValidationMessage().empty() ? std::string("Unknown validation error.") : editorContext.GetValidationMessage(), EditorWindowInternal::kShellMutedTextColor, 12.0f); } void EditorWindow::LogFrameInteractionTrace( EditorContext& editorContext, const std::vector& frameEvents, const UIEditorShellInteractionFrame& shellFrame) const { if (!IsVerboseRuntimeTraceEnabled() || (frameEvents.empty() && !shellFrame.result.workspaceResult.dockHostResult.layoutChanged && !shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) { return; } std::ostringstream frameTrace = {}; frameTrace << "result consumed=" << (shellFrame.result.consumed ? "true" : "false") << " layoutChanged=" << (shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false") << " commandExecuted=" << (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false") << " active=" << GetWorkspaceController().GetWorkspace().activePanelId << " message=" << shellFrame.result.workspaceResult.dockHostResult.layoutResult.message; LogRuntimeTrace("frame", frameTrace.str()); } EditorWindowFrameTransferRequests EditorWindow::BuildShellTransferRequests( bool globalTabDragActive, const UIEditorDockHostInteractionState& dockHostInteractionState, const UIEditorShellInteractionFrame& shellFrame) const { EditorWindowFrameTransferRequests transferRequests = {}; POINT screenPoint = {}; const bool hasScreenPoint = GetCursorPos(&screenPoint) != FALSE; if (!globalTabDragActive && !dockHostInteractionState.activeTabDragNodeId.empty() && !dockHostInteractionState.activeTabDragPanelId.empty() && hasScreenPoint) { transferRequests.beginGlobalTabDrag = EditorWindowPanelTransferRequest{ dockHostInteractionState.activeTabDragNodeId, dockHostInteractionState.activeTabDragPanelId, screenPoint, }; } if (shellFrame.result.workspaceResult.dockHostResult.detachRequested && hasScreenPoint) { transferRequests.detachPanel = EditorWindowPanelTransferRequest{ shellFrame.result.workspaceResult.dockHostResult.detachedNodeId, shellFrame.result.workspaceResult.dockHostResult.detachedPanelId, screenPoint, }; } return transferRequests; } std::string EditorWindow::BuildCaptureStatusText() const { if (m_state->render.autoScreenshot.HasPendingCapture()) { return "Shot pending..."; } if (!m_state->render.autoScreenshot.GetLastCaptureError().empty()) { return TruncateText(m_state->render.autoScreenshot.GetLastCaptureError(), 38u); } if (!m_state->render.autoScreenshot.GetLastCaptureSummary().empty()) { return TruncateText(m_state->render.autoScreenshot.GetLastCaptureSummary(), 38u); } return {}; } void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { m_state->input.modifierTracker.SyncFromSystemState(); const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons(m_state->composition.shellRuntime); if (expectedButtons == 0u || HasPendingPointerStateReconciliationEvent(m_state->input.pendingEvents)) { return; } const UIInputModifiers modifiers = m_state->input.modifierTracker.GetCurrentModifiers(); if ((ButtonMaskFromModifiers(modifiers) & expectedButtons) == expectedButtons) { return; } QueueSyntheticPointerStateSyncEvent(modifiers); } void EditorWindow::ApplyShellRuntimePointerCapture() { const EditorShellPointerOwner owner = m_state->composition.shellRuntime.GetPointerOwner(); if (IsShellPointerOwner(owner)) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell); return; } if (IsHostedContentPointerOwner(owner)) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::HostedContent); return; } if (OwnsPointerCapture(EditorWindowPointerCaptureOwner::Shell)) { ReleasePointerCapture(EditorWindowPointerCaptureOwner::Shell); } if (OwnsPointerCapture(EditorWindowPointerCaptureOwner::HostedContent)) { ReleasePointerCapture(EditorWindowPointerCaptureOwner::HostedContent); } } std::string EditorWindow::DescribeInputEvents( const std::vector& events) const { std::ostringstream stream = {}; stream << "events=["; for (std::size_t index = 0; index < events.size(); ++index) { if (index > 0u) { stream << " | "; } const UIInputEvent& event = events[index]; stream << DescribeInputEventType(event) << '@' << static_cast(event.position.x) << ',' << static_cast(event.position.y); } stream << ']'; return stream.str(); } } // namespace XCEngine::UI::Editor::App