#include "Windowing/Content/EditorWindowFrameOrchestrator.h" #include "Composition/EditorContext.h" #include "Composition/EditorShellRuntime.h" #include "Composition/EditorShellVariant.h" #include "Support/EnvironmentFlags.h" #include "Windowing/Content/EditorWindowContentStyle.h" #include "Windowing/Utility/EditorUtilityWindowRequestSink.h" #include "Windowing/Workspace/WindowWorkspaceTransferQueue.h" #include "Windowing/Workspace/UIEditorDetachedWindowPolicy.h" #include #include #include #include #include namespace XCEngine::UI::Editor::App { using namespace EditorWindowContentStyle; using ::XCEngine::UI::UIDrawData; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPoint; namespace WindowingDomain = ::XCEngine::UI::Editor::Windowing::Domain; namespace { bool IsVerboseRuntimeTraceEnabled() { static const bool s_enabled = IsEnvironmentFlagEnabled("XCUIEDITOR_VERBOSE_TRACE"); return s_enabled; } 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"; } } WindowingDomain::WindowCursorType ToWindowCursorType(ProjectPanel::CursorKind cursorType) { switch (cursorType) { case ProjectPanel::CursorKind::ResizeEW: return WindowingDomain::WindowCursorType::ResizeEW; case ProjectPanel::CursorKind::Arrow: default: return WindowingDomain::WindowCursorType::Arrow; } } WindowingDomain::WindowCursorType ToWindowCursorType( Widgets::UIEditorDockHostCursorKind cursorType) { switch (cursorType) { case Widgets::UIEditorDockHostCursorKind::ResizeEW: return WindowingDomain::WindowCursorType::ResizeEW; case Widgets::UIEditorDockHostCursorKind::ResizeNS: return WindowingDomain::WindowCursorType::ResizeNS; case Widgets::UIEditorDockHostCursorKind::Arrow: default: return WindowingDomain::WindowCursorType::Arrow; } } WindowingDomain::WindowCaptureDemand BuildCaptureDemand( const EditorShellRuntime& shellRuntime) { return WindowingDomain::WindowCaptureDemand{ .shellInteractive = shellRuntime.HasShellInteractiveCapture(), .hostedContent = shellRuntime.HasHostedContentCapture(), }; } WindowingDomain::WindowCursorType ResolveContentCursorType( const EditorShellRuntime& shellRuntime) { const WindowingDomain::WindowCursorType hostedCursorType = ToWindowCursorType(shellRuntime.GetHostedContentCursorType()); if (hostedCursorType != WindowingDomain::WindowCursorType::Arrow) { return hostedCursorType; } return ToWindowCursorType(shellRuntime.GetDockCursorType()); } void QueueWorkspaceTransferRequest( WindowWorkspaceTransferQueue& workspaceTransferQueue, WindowWorkspaceTransferRequest::Type type, std::string_view sourceWindowId, std::string sourceNodeId, std::string panelId, std::int32_t cursorScreenX, std::int32_t cursorScreenY) { WindowWorkspaceTransferRequest request = {}; request.type = type; request.sourceWindowId = std::string(sourceWindowId); request.sourceNodeId = std::move(sourceNodeId); request.panelId = std::move(panelId); request.screenX = cursorScreenX; request.screenY = cursorScreenY; request.hasScreenPoint = true; workspaceTransferQueue.QueueWorkspaceTransferRequest(request); } } // namespace EditorWindowContentUpdateResult EditorWindowFrameOrchestrator::UpdateAndAppend( EditorContext& editorContext, std::string_view sourceWindowId, EditorUtilityWindowResultState& utilityWindowResultState, EditorUtilityWindowRequestSink& utilityRequestSink, WindowWorkspaceTransferQueue& workspaceTransferQueue, UIEditorWorkspaceController& workspaceController, EditorShellRuntime& shellRuntime, const ::XCEngine::UI::UIRect& workspaceBounds, const std::vector& frameEvents, std::string_view captureStatusText, bool primary, bool globalTabDragActive, bool useDetachedTitleBarTabStrip, std::int32_t cursorScreenX, std::int32_t cursorScreenY, bool hasCursorScreenPoint, UIDrawData& drawData) const { LogInputTrace( editorContext, workspaceController, shellRuntime.GetShellInteractionState(), frameEvents); utilityRequestSink.ConfigureFrameSource( sourceWindowId, cursorScreenX, cursorScreenY, hasCursorScreenPoint); shellRuntime.Update( editorContext, utilityWindowResultState, utilityRequestSink, workspaceController, workspaceBounds, frameEvents, captureStatusText, primary ? EditorShellVariant::Primary : EditorShellVariant::DetachedWindow, useDetachedTitleBarTabStrip, useDetachedTitleBarTabStrip ? kBorderlessTitleBarHeightDips : 0.0f, primary ? 0.0f : kBorderlessTitleBarHeightDips); const UIEditorShellInteractionFrame& shellFrame = shellRuntime.GetShellFrame(); const UIEditorDockHostInteractionState& dockHostInteractionState = shellRuntime.GetShellInteractionState().workspaceInteractionState.dockHostInteractionState; LogFrameInteractionTrace(workspaceController, frameEvents, shellFrame); EditorWindowContentUpdateResult updateResult = {}; updateResult.contentOutput.captureDemand = BuildCaptureDemand(shellRuntime); updateResult.contentOutput.cursorType = ResolveContentCursorType(shellRuntime); updateResult.contentOutput.titleBarMode = HasUIEditorSingleVisibleRootTab(workspaceController) ? WindowingDomain::WindowTitleBarMode::DetachedTabStrip : WindowingDomain::WindowTitleBarMode::SystemChrome; AppendWorkspaceTransferRequests( sourceWindowId, globalTabDragActive, dockHostInteractionState, shellFrame, cursorScreenX, cursorScreenY, hasCursorScreenPoint, workspaceTransferQueue); for (const WorkspaceTraceEntry& entry : shellRuntime.GetTraceEntries()) { AppendUIEditorRuntimeTrace(entry.channel, entry.message); } shellRuntime.Append(drawData); return updateResult; } void EditorWindowFrameOrchestrator::AppendInvalidFrame( EditorContext& editorContext, UIDrawList& drawList) const { drawList.AddText( UIPoint(28.0f, 28.0f), "Editor shell asset invalid.", kShellTextColor, 16.0f); drawList.AddText( UIPoint(28.0f, 54.0f), editorContext.GetValidationMessage().empty() ? std::string("Unknown validation error.") : editorContext.GetValidationMessage(), kShellMutedTextColor, 12.0f); } std::string EditorWindowFrameOrchestrator::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(); } void EditorWindowFrameOrchestrator::LogInputTrace( EditorContext& editorContext, const UIEditorWorkspaceController& workspaceController, const UIEditorShellInteractionState& shellInteractionState, const std::vector& frameEvents) const { if (frameEvents.empty() || !IsVerboseRuntimeTraceEnabled()) { return; } AppendUIEditorRuntimeTrace( "input", DescribeInputEvents(frameEvents) + " | " + editorContext.DescribeWorkspaceState( workspaceController, shellInteractionState)); } void EditorWindowFrameOrchestrator::LogFrameInteractionTrace( const UIEditorWorkspaceController& workspaceController, 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=" << workspaceController.GetWorkspace().activePanelId << " message=" << shellFrame.result.workspaceResult.dockHostResult.layoutResult.message; AppendUIEditorRuntimeTrace("frame", frameTrace.str()); } void EditorWindowFrameOrchestrator::AppendWorkspaceTransferRequests( std::string_view sourceWindowId, bool globalTabDragActive, const UIEditorDockHostInteractionState& dockHostInteractionState, const UIEditorShellInteractionFrame& shellFrame, std::int32_t cursorScreenX, std::int32_t cursorScreenY, bool hasCursorScreenPoint, WindowWorkspaceTransferQueue& workspaceTransferQueue) const { if (!globalTabDragActive && !dockHostInteractionState.activeTabDragNodeId.empty() && !dockHostInteractionState.activeTabDragPanelId.empty() && hasCursorScreenPoint) { QueueWorkspaceTransferRequest( workspaceTransferQueue, WindowWorkspaceTransferRequest::Type::StartGlobalTabDrag, sourceWindowId, dockHostInteractionState.activeTabDragNodeId, dockHostInteractionState.activeTabDragPanelId, cursorScreenX, cursorScreenY); } if (shellFrame.result.workspaceResult.dockHostResult.detachRequested && hasCursorScreenPoint) { QueueWorkspaceTransferRequest( workspaceTransferQueue, WindowWorkspaceTransferRequest::Type::DetachPanel, sourceWindowId, shellFrame.result.workspaceResult.dockHostResult.detachedNodeId, shellFrame.result.workspaceResult.dockHostResult.detachedPanelId, cursorScreenX, cursorScreenY); } } } // namespace XCEngine::UI::Editor::App