diff --git a/new_editor/app/Composition/EditorShellRuntime.h b/new_editor/app/Composition/EditorShellRuntime.h index 7b9aa7c1..469893cb 100644 --- a/new_editor/app/Composition/EditorShellRuntime.h +++ b/new_editor/app/Composition/EditorShellRuntime.h @@ -40,7 +40,9 @@ public: const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, std::string_view captureText, - EditorShellVariant shellVariant = EditorShellVariant::Primary); + EditorShellVariant shellVariant = EditorShellVariant::Primary, + bool useDetachedTitleBarTabStrip = false, + float detachedTitleBarTabHeight = 0.0f); void RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext); void Append(::XCEngine::UI::UIDrawList& drawList) const; diff --git a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp index 3be5d9ce..73baeb61 100644 --- a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp @@ -105,8 +105,15 @@ void EditorShellRuntime::Update( const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, std::string_view captureText, - EditorShellVariant shellVariant) { - const auto& metrics = ResolveUIEditorShellInteractionMetrics(); + EditorShellVariant shellVariant, + bool useDetachedTitleBarTabStrip, + float detachedTitleBarTabHeight) { + UIEditorShellInteractionMetrics metrics = ResolveUIEditorShellInteractionMetrics(); + if (useDetachedTitleBarTabStrip && detachedTitleBarTabHeight > 0.0f) { + metrics.shellMetrics.dockHostMetrics.tabStripMetrics.layoutMetrics.headerHeight = + detachedTitleBarTabHeight; + } + context.SyncSessionFromWorkspace(workspaceController); UIEditorShellInteractionDefinition definition = context.BuildShellDefinition(workspaceController, captureText, shellVariant); diff --git a/new_editor/app/Platform/Win32/EditorWindow.h b/new_editor/app/Platform/Win32/EditorWindow.h index caad3d31..e42b57f4 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.h +++ b/new_editor/app/Platform/Win32/EditorWindow.h @@ -181,6 +181,7 @@ private: EditorContext& editorContext, bool globalTabDragActive, const RECT& targetRect); + bool ShouldUseDetachedTitleBarTabStrip() const; void ToggleBorderlessWindowMaximizeRestore( EditorContext& editorContext, bool globalTabDragActive); diff --git a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp index 45d9b709..7578636f 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp @@ -80,6 +80,10 @@ UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientH 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, diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameRuntime.cpp b/new_editor/app/Platform/Win32/EditorWindowFrameRuntime.cpp index 08f4a473..36110256 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrameRuntime.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrameRuntime.cpp @@ -38,6 +38,7 @@ void EditorWindow::RenderRuntimeFrame( } editorContext.AttachTextMeasurer(m_render.renderer); + const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(); m_composition.shellRuntime.Update( editorContext, m_composition.workspaceController, @@ -46,7 +47,9 @@ void EditorWindow::RenderRuntimeFrame( BuildCaptureStatusText(), m_window.primary ? EditorShellVariant::Primary - : EditorShellVariant::DetachedWindow); + : EditorShellVariant::DetachedWindow, + useDetachedTitleBarTabStrip, + useDetachedTitleBarTabStrip ? kBorderlessTitleBarHeightDips : 0.0f); const UIEditorShellInteractionFrame& shellFrame = m_composition.shellRuntime.GetShellFrame(); const UIEditorDockHostInteractionState& dockHostInteractionState = diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp index e8f3fb98..15fc4162 100644 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp @@ -2,20 +2,107 @@ #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) { @@ -38,9 +125,14 @@ Host::BorderlessWindowChromeHitTarget EditorWindow::HitTestBorderlessWindowChrom 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), - 0.0f); + leadingOccupiedRight); } void EditorWindow::AppendBorderlessWindowChrome( @@ -52,40 +144,54 @@ void EditorWindow::AppendBorderlessWindowChrome( const Host::BorderlessWindowChromeLayout layout = ResolveBorderlessWindowChromeLayout(clientWidthDips); - const float iconExtent = 16.0f; - const float iconInsetLeft = 8.0f; - const float iconTextGap = 8.0f; - const float iconX = layout.titleBarRect.x + iconInsetLeft; - const float iconY = - layout.titleBarRect.y + - (std::max)(0.0f, (layout.titleBarRect.height - iconExtent) * 0.5f); - 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_render.titleBarLogoIcon.IsValid()) { - drawList.AddImage( - UIRect(iconX, iconY, iconExtent, iconExtent), - m_render.titleBarLogoIcon, - UIColor(1.0f, 1.0f, 1.0f, 1.0f)); + 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); } - const std::string& titleText = - m_window.titleText.empty() ? std::string("XCEngine Editor") : m_window.titleText; - drawList.AddText( - UIPoint( - iconX + (m_render.titleBarLogoIcon.IsValid() ? (iconExtent + iconTextGap) : 4.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 - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)), - titleText, - kShellTextColor, - kBorderlessTitleBarFontSize); + (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,