fix(new_editor/window): move detached root tab into title bar

This commit is contained in:
2026-04-15 12:38:45 +08:00
parent 59941626a7
commit d501e895eb
6 changed files with 159 additions and 36 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -181,6 +181,7 @@ private:
EditorContext& editorContext,
bool globalTabDragActive,
const RECT& targetRect);
bool ShouldUseDetachedTitleBarTabStrip() const;
void ToggleBorderlessWindowMaximizeRestore(
EditorContext& editorContext,
bool globalTabDragActive);

View File

@@ -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,

View File

@@ -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 =

View File

@@ -2,20 +2,107 @@
#include "Platform/Win32/EditorWindowConstants.h"
#include "Platform/Win32/EditorWindowStyle.h"
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Panels/UIEditorPanelRegistry.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Layout/UITabStripLayout.h>
#include <algorithm>
#include <cmath>
#include <string_view>
#include <windowsx.h>
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,