#pragma once #include "XCUIBackend/XCUIPanelCanvasHost.h" #include #include #include #include namespace XCEngine { namespace Editor { namespace XCUIBackend { namespace detail { inline std::string CopyCanvasLabel(const char* text) { return text != nullptr ? std::string(text) : std::string(); } inline ::XCEngine::UI::UIColor NativeCanvasPlaceholderFillColor() { return ::XCEngine::UI::UIColor(18.0f / 255.0f, 24.0f / 255.0f, 32.0f / 255.0f, 1.0f); } inline ::XCEngine::UI::UIColor NativeCanvasPlaceholderStrokeColor() { return ::XCEngine::UI::UIColor(54.0f / 255.0f, 72.0f / 255.0f, 94.0f / 255.0f, 1.0f); } inline ::XCEngine::UI::UIColor NativeCanvasPrimaryTextColor() { return ::XCEngine::UI::UIColor(191.0f / 255.0f, 205.0f / 255.0f, 224.0f / 255.0f, 1.0f); } inline ::XCEngine::UI::UIColor NativeCanvasSecondaryTextColor() { return ::XCEngine::UI::UIColor(132.0f / 255.0f, 147.0f / 255.0f, 170.0f / 255.0f, 1.0f); } inline XCUIPanelCanvasSession NormalizeNativeCanvasSession( const XCUIPanelCanvasRequest& request, const XCUIPanelCanvasSession* configuredSession) { if (configuredSession == nullptr) { return BuildPassiveXCUIPanelCanvasSession(request); } XCUIPanelCanvasSession session = *configuredSession; const float fallbackHostHeight = request.height > 0.0f ? request.height : 0.0f; if (session.hostRect.height <= 0.0f && fallbackHostHeight > 0.0f) { session.hostRect.height = fallbackHostHeight; } if (session.canvasRect.width <= 0.0f || session.canvasRect.height <= 0.0f) { const float topInset = request.topInset > 0.0f ? request.topInset : 0.0f; const float clampedTopInset = session.hostRect.height > 0.0f ? (std::min)(topInset, session.hostRect.height) : 0.0f; session.canvasRect = ::XCEngine::UI::UIRect( session.hostRect.x, session.hostRect.y + clampedTopInset, session.hostRect.width, (std::max)(0.0f, session.hostRect.height - clampedTopInset)); } session.validCanvas = session.canvasRect.width > 1.0f && session.canvasRect.height > 1.0f; if (!session.validCanvas) { session.hovered = false; } return session; } inline void RecordPlaceholder( ::XCEngine::UI::UIDrawList& drawList, const ::XCEngine::UI::UIRect& rect, const std::string& title, const std::string& subtitle) { if (rect.width <= 1.0f || rect.height <= 1.0f) { return; } drawList.AddFilledRect(rect, NativeCanvasPlaceholderFillColor(), 8.0f); drawList.AddRectOutline(rect, NativeCanvasPlaceholderStrokeColor(), 1.0f, 8.0f); if (!title.empty()) { drawList.AddText( ::XCEngine::UI::UIPoint(rect.x + 14.0f, rect.y + 14.0f), title, NativeCanvasPrimaryTextColor()); } if (!subtitle.empty()) { drawList.AddText( ::XCEngine::UI::UIPoint(rect.x + 14.0f, rect.y + 36.0f), subtitle, NativeCanvasSecondaryTextColor()); } } inline void RecordBadge( ::XCEngine::UI::UIDrawList& drawList, const ::XCEngine::UI::UIRect& canvasRect, const std::string& title, const std::string& subtitle) { if (title.empty()) { return; } const ::XCEngine::UI::UIRect badgeRect( canvasRect.x + 10.0f, canvasRect.y + 10.0f, 290.0f, 42.0f); drawList.AddFilledRect( badgeRect, ::XCEngine::UI::UIColor(16.0f / 255.0f, 22.0f / 255.0f, 30.0f / 255.0f, 216.0f / 255.0f), 8.0f); drawList.AddRectOutline(badgeRect, NativeCanvasPlaceholderStrokeColor(), 1.0f, 8.0f); drawList.AddText( ::XCEngine::UI::UIPoint(badgeRect.x + 10.0f, badgeRect.y + 8.0f), title, NativeCanvasPrimaryTextColor()); if (!subtitle.empty()) { drawList.AddText( ::XCEngine::UI::UIPoint(badgeRect.x + 10.0f, badgeRect.y + 24.0f), subtitle, NativeCanvasSecondaryTextColor()); } } } // namespace detail class NativeXCUIPanelCanvasHost final : public IXCUIPanelCanvasHost { public: const char* GetDebugName() const override { return "NativeXCUIPanelCanvasHost"; } void SetCanvasSession(const XCUIPanelCanvasSession& session) { m_configuredSession = session; m_hasConfiguredSession = true; } void ClearCanvasSession() { m_configuredSession = {}; m_hasConfiguredSession = false; } bool HasConfiguredSession() const { return m_hasConfiguredSession; } const XCUIPanelCanvasSession& GetConfiguredSession() const { return m_configuredSession; } XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override { m_currentFrame = {}; m_currentFrame.childId = ResolveXCUIPanelCanvasChildId(request, "NativeXCUIPanelCanvasHost"); m_currentFrame.session = detail::NormalizeNativeCanvasSession( request, m_hasConfiguredSession ? &m_configuredSession : nullptr); m_currentFrame.bordered = request.bordered; m_currentFrame.drawPreviewFrame = request.drawPreviewFrame; m_currentFrame.showingSurfaceImage = request.showSurfaceImage && request.surfaceImage.IsValid(); m_currentFrame.placeholderTitle = detail::CopyCanvasLabel(request.placeholderTitle); m_currentFrame.placeholderSubtitle = detail::CopyCanvasLabel(request.placeholderSubtitle); m_currentFrame.badgeTitle = detail::CopyCanvasLabel(request.badgeTitle); m_currentFrame.badgeSubtitle = detail::CopyCanvasLabel(request.badgeSubtitle); m_currentFrame.surfaceImage = request.surfaceImage; m_overlayDrawList = nullptr; m_clipDepth = 0u; if (m_currentFrame.session.validCanvas) { const bool shouldRecordPlaceholder = !m_currentFrame.showingSurfaceImage && (!m_currentFrame.placeholderTitle.empty() || !m_currentFrame.placeholderSubtitle.empty()); if (shouldRecordPlaceholder) { ::XCEngine::UI::UIDrawList& drawList = EnsureOverlayDrawList(); detail::RecordPlaceholder( drawList, m_currentFrame.session.canvasRect, m_currentFrame.placeholderTitle, m_currentFrame.placeholderSubtitle); } if (m_currentFrame.showingSurfaceImage) { EnsureOverlayDrawList().AddImage( m_currentFrame.session.canvasRect, m_currentFrame.surfaceImage.texture, ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f), m_currentFrame.surfaceImage.uvMin, m_currentFrame.surfaceImage.uvMax); } if (request.drawPreviewFrame) { DrawOutlineRect( m_currentFrame.session.canvasRect, detail::NativeCanvasPlaceholderStrokeColor(), 1.0f, 8.0f); } if (!m_currentFrame.badgeTitle.empty()) { ::XCEngine::UI::UIDrawList& drawList = EnsureOverlayDrawList(); detail::RecordBadge( drawList, m_currentFrame.session.canvasRect, m_currentFrame.badgeTitle, m_currentFrame.badgeSubtitle); } } return m_currentFrame.session; } void DrawFilledRect( const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIColor& color, float rounding = 0.0f) override { if (rect.width <= 0.0f || rect.height <= 0.0f) { return; } EnsureOverlayDrawList().AddFilledRect(rect, color, rounding); } void DrawOutlineRect( const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIColor& color, float thickness = 1.0f, float rounding = 0.0f) override { if (rect.width <= 0.0f || rect.height <= 0.0f) { return; } EnsureOverlayDrawList().AddRectOutline(rect, color, thickness, rounding); } void DrawText( const ::XCEngine::UI::UIPoint& position, std::string_view text, const ::XCEngine::UI::UIColor& color, float fontSize = 0.0f) override { if (text.empty()) { return; } EnsureOverlayDrawList().AddText(position, std::string(text), color, fontSize); } void EndCanvas() override { if (m_overlayDrawList != nullptr) { while (m_clipDepth > 0u) { m_overlayDrawList->PopClipRect(); --m_clipDepth; } } m_overlayDrawList = nullptr; } bool TryGetLatestFrameSnapshot(XCUIPanelCanvasFrameSnapshot& outSnapshot) const override { outSnapshot = m_currentFrame; return !outSnapshot.childId.empty(); } private: ::XCEngine::UI::UIDrawList& EnsureOverlayDrawList() { if (m_overlayDrawList == nullptr) { ::XCEngine::UI::UIDrawList& drawList = m_currentFrame.overlayDrawData.EmplaceDrawList(m_currentFrame.childId + ".overlay"); drawList.PushClipRect(m_currentFrame.session.canvasRect, true); m_overlayDrawList = &drawList; m_clipDepth = 1u; } return *m_overlayDrawList; } bool m_hasConfiguredSession = false; XCUIPanelCanvasSession m_configuredSession = {}; XCUIPanelCanvasFrameSnapshot m_currentFrame = {}; ::XCEngine::UI::UIDrawList* m_overlayDrawList = nullptr; std::size_t m_clipDepth = 0u; }; inline std::unique_ptr CreateNativeXCUIPanelCanvasHost() { return std::make_unique(); } } // namespace XCUIBackend } // namespace Editor } // namespace XCEngine