#include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" #include "Platform/Win32/EditorWindowStyle.h" #include #include #include #include #include #include #include #include namespace XCEngine::UI::Editor::App { using namespace EditorWindowSupport; using ::XCEngine::UI::Layout::MeasureUITabStripHeaderWidth; using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; namespace { constexpr float kTitleBarLogoExtent = 16.0f; constexpr float kTitleBarLogoInsetLeft = 8.0f; constexpr float kTitleBarLogoTextGap = 8.0f; std::string ResolveDetachedTitleTabText(const EditorWindow& window) { const auto& workspaceController = window.GetWorkspaceController(); const std::string_view activePanelId = workspaceController.GetWorkspace().activePanelId; if (!activePanelId.empty()) { if (const UIEditorPanelDescriptor* descriptor = FindUIEditorPanelDescriptor( workspaceController.GetPanelRegistry(), activePanelId); descriptor != nullptr && !descriptor->defaultTitle.empty()) { return descriptor->defaultTitle; } } return std::string("Panel"); } float ResolveDetachedTabWidth(std::string_view text) { const Widgets::UIEditorTabStripMetrics& metrics = ResolveUIEditorTabStripMetrics(); Widgets::UIEditorTabStripItem item = {}; item.title = std::string(text); const float desiredLabelWidth = Widgets::ResolveUIEditorTabStripDesiredHeaderLabelWidth(item, metrics); return MeasureUITabStripHeaderWidth(desiredLabelWidth, metrics.layoutMetrics); } bool IsRootPanelVisible( const UIEditorWorkspaceController& controller, std::string_view panelId) { const UIEditorPanelSessionState* panelState = FindUIEditorPanelSessionState(controller.GetSession(), panelId); return panelState != nullptr && panelState->open && panelState->visible; } bool HasSingleVisibleRootTab(const UIEditorWorkspaceController& controller) { const UIEditorWorkspaceNode& root = controller.GetWorkspace().root; if (root.kind != UIEditorWorkspaceNodeKind::TabStack) { return false; } std::size_t visiblePanelCount = 0u; for (const UIEditorWorkspaceNode& child : root.children) { if (child.kind != UIEditorWorkspaceNodeKind::Panel || !IsRootPanelVisible(controller, child.panel.panelId)) { continue; } ++visiblePanelCount; if (visiblePanelCount > 1u) { return false; } } return visiblePanelCount == 1u; } UIRect BuildDetachedTitleLogoRect(const Host::BorderlessWindowChromeLayout& layout) { const float availableLeft = layout.titleBarRect.x; const float availableRight = layout.minimizeButtonRect.x; const float centeredX = std::floor( layout.titleBarRect.x + layout.titleBarRect.width * 0.5f - kTitleBarLogoExtent * 0.5f); const float clampedX = (std::max)( availableLeft, (std::min)(centeredX, availableRight - kTitleBarLogoExtent)); const float logoY = layout.titleBarRect.y + (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); return UIRect(clampedX, logoY, kTitleBarLogoExtent, kTitleBarLogoExtent); } } // namespace bool EditorWindow::ShouldUseDetachedTitleBarTabStrip() const { return !m_window.primary && HasSingleVisibleRootTab(m_composition.workspaceController); } Host::BorderlessWindowChromeHitTarget EditorWindow::HitTestBorderlessWindowChrome( LPARAM lParam) const { if (!IsBorderlessWindowEnabled() || m_window.hwnd == nullptr) { return Host::BorderlessWindowChromeHitTarget::None; } RECT clientRect = {}; if (!GetClientRect(m_window.hwnd, &clientRect)) { return Host::BorderlessWindowChromeHitTarget::None; } const float clientWidthDips = PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); const Host::BorderlessWindowChromeLayout layout = ResolveBorderlessWindowChromeLayout(clientWidthDips); return Host::HitTestBorderlessWindowChrome( layout, ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); } Host::BorderlessWindowChromeLayout EditorWindow::ResolveBorderlessWindowChromeLayout( float clientWidthDips) const { float leadingOccupiedRight = 0.0f; if (ShouldUseDetachedTitleBarTabStrip()) { leadingOccupiedRight = ResolveDetachedTabWidth(ResolveDetachedTitleTabText(*this)); } return Host::BuildBorderlessWindowChromeLayout( UIRect(0.0f, 0.0f, clientWidthDips, kBorderlessTitleBarHeightDips), leadingOccupiedRight); } void EditorWindow::AppendBorderlessWindowChrome( UIDrawList& drawList, float clientWidthDips) const { if (!IsBorderlessWindowEnabled()) { return; } const Host::BorderlessWindowChromeLayout layout = ResolveBorderlessWindowChromeLayout(clientWidthDips); const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(); if (!useDetachedTitleBarTabStrip) { drawList.AddFilledRect(layout.titleBarRect, kShellSurfaceColor); drawList.AddLine( UIPoint(layout.titleBarRect.x, layout.titleBarRect.y + layout.titleBarRect.height), UIPoint( layout.titleBarRect.x + layout.titleBarRect.width, layout.titleBarRect.y + layout.titleBarRect.height), kShellBorderColor, 1.0f); } if (!m_window.primary) { if (m_render.titleBarLogoIcon.IsValid()) { drawList.AddImage( BuildDetachedTitleLogoRect(layout), m_render.titleBarLogoIcon, UIColor(1.0f, 1.0f, 1.0f, 1.0f)); } } else { const float iconX = layout.titleBarRect.x + kTitleBarLogoInsetLeft; const float iconY = layout.titleBarRect.y + (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); if (m_render.titleBarLogoIcon.IsValid()) { drawList.AddImage( UIRect(iconX, iconY, kTitleBarLogoExtent, kTitleBarLogoExtent), m_render.titleBarLogoIcon, UIColor(1.0f, 1.0f, 1.0f, 1.0f)); } const std::string& titleText = m_window.titleText.empty() ? std::string("XCEngine Editor") : m_window.titleText; drawList.AddText( UIPoint( iconX + (m_render.titleBarLogoIcon.IsValid() ? (kTitleBarLogoExtent + kTitleBarLogoTextGap) : 4.0f), layout.titleBarRect.y + (std::max)( 0.0f, (layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)), titleText, kShellTextColor, kBorderlessTitleBarFontSize); } Host::AppendBorderlessWindowChrome( drawList, layout, m_chrome.chromeState, IsBorderlessWindowMaximized()); } } // namespace XCEngine::UI::Editor::App