diff --git a/docs/plan/Editor_Window_Architecture_Refactor_Plan_2026-04-25.md b/docs/used/Editor_Window_Architecture_Refactor_Plan_2026-04-25.md similarity index 100% rename from docs/plan/Editor_Window_Architecture_Refactor_Plan_2026-04-25.md rename to docs/used/Editor_Window_Architecture_Refactor_Plan_2026-04-25.md diff --git a/editor/AGENT.md b/editor/AGENT.md index 57162beb..cf54a80c 100644 --- a/editor/AGENT.md +++ b/editor/AGENT.md @@ -21,7 +21,7 @@ ## 2. 分层边界 -推荐按下面四层理解和修改: +推荐按下面五层理解和修改: 1. `editor/src` / `editor/include/XCEditor` XCEditor UI framework。这里应保持通用,不依赖具体项目、场景、Win32 宿主或 D3D12 实现。 @@ -29,20 +29,43 @@ 2. `editor/app/Composition`、`Features`、`Project`、`Scene`、`State`、`Commands` Editor app core。这里装配 panel、项目运行时、场景运行时、选择状态、命令路由和 shell frame。 -3. `editor/app/Platform/Win32` - Win32 host。这里负责原生窗口、消息分发、窗口生命周期、输入收集、窗口内容装载和 title bar / chrome。 +3. `editor/app/Windowing` + Editor host seam。这里放平台无关的窗口内容 contract、workspace/utility content assembly、frame orchestration 和 host-facing transfer request。 -4. `editor/app/Rendering` +4. `editor/app/Platform/Win32` + Win32 host。这里负责原生窗口、消息分发、窗口生命周期、输入收集、title bar / chrome 和 surface/presenter。 + +5. `editor/app/Rendering` Editor rendering host。这里负责 D3D12 UI 渲染、窗口 swapchain、viewport 离屏资源、editor 图标纹理和 scene viewport pass。 依赖方向尽量保持: ```text -app/platform + app/rendering -> app/core -> XCEditor framework -> engine UI primitives +app/platform + app/rendering -> app/windowing -> app/core -> XCEditor framework -> engine UI primitives ``` 不要让 `editor/src` 反向依赖 `editor/app`。 +### 2.1 长期目标:把语义分层落成真实模块边界 + +当前 `editor/` 已经有语义上的四层划分,但还没有真正的编译/依赖边界。窗口相关代码最重要的长期目标,不是继续细拆 `Win32` 目录里的类,而是把下面这条关系变成代码和构建系统都能强制执行的事实: + +```text +XCEditor framework + <- editor app core + <- editor host seam / app/Windowing + <- Win32 host + D3D12 host +``` + +收敛要求: + +- `editor/app/Platform/Win32` 只负责 native window、message pump、input translation、chrome、lifecycle 和 surface/presenter 等宿主职责。 +- workspace mutation、utility window routing、shell composition、window content assembly 这类产品语义应逐步收敛到平台无关层,而不是继续堆进 Win32 host。 +- `editor/src` / `editor/include/XCEditor` 不允许反向依赖 `editor/app`;公开头文件不应暴露 `App::*` 类型。 +- 最终要通过独立 CMake target 和受限 include dirs 落实,而不是只靠目录约定。 + +后续凡是修改窗口系统,优先朝“缩小 `Platform/Win32` 对 `Composition`、`Features`、`UtilityWindows`、`Rendering` 的直接依赖面”这个方向收敛。如果一个新需求只能通过继续把业务语义塞进 Win32 host 才能完成,应先停下来评估是否缺少平台无关的 host seam。 + ## 3. 顶层启动流程 入口: @@ -128,7 +151,7 @@ utility window 的内容控制器是 `EditorUtilityWindowContentController`, 窗口外观/宿主策略当前最少包括: - `EditorWindowChromePolicy` - 当前用于控制 detached title bar tab strip 和 frame stats。 + 当前用于控制 detached title bar tab strip、frame stats,以及 utility window 的 topmost pin 按钮/默认置顶策略。 - `EditorWindowNativeStylePolicy` 当前用于控制扩展窗口样式和是否复用 host 默认 style。 @@ -137,6 +160,7 @@ utility window 的内容控制器是 `EditorUtilityWindowContentController`, - workspace window 允许 detached title bar tab strip。 - utility window 不允许 detached title bar tab strip。 - utility window 不显示 frame stats。 +- utility window 顶栏显示 topmost pin 按钮,且默认置顶。 - utility window 使用 `WS_EX_TOOLWINDOW`。 ## 6. Workspace / Utility 协调器边界 @@ -218,11 +242,12 @@ Application::Run ## 9. 修改规范 -- 先判断改动属于 framework、app core、Win32 host 还是 rendering host。 +- 先判断改动属于 framework、app core、windowing seam、Win32 host 还是 rendering host。 - 小改动优先贴近现有模式,不顺手做跨层重构。 - 新增通用 widget 放 `editor/src` / `editor/include/XCEditor`。 - 新增具体 editor 功能放 `editor/app/Features/`,再由 composition 装配。 -- 新增窗口行为放 `editor/app/Platform/Win32`,通过 content/controller/policy/binding 与 app core 通信。 +- 新增 host-facing window content、frame request、content assembly 放 `editor/app/Windowing`。 +- 新增 Win32 宿主行为放 `editor/app/Platform/Win32`,通过 seam/controller/policy 与 app core 通信。 - 新增 viewport 渲染资源或 pass 放 `editor/app/Rendering/Viewport` 或 `editor/resources/shaders/scene-viewport`。 - draw append 只负责绘制,不修改 editor runtime 或 scene/project state。 - D3D12 host、Win32 host、panel 之间避免双向强耦合。 @@ -231,11 +256,11 @@ Application::Run 这轮重构完成后,仍有以下债务: -- `editor/CMakeLists.txt` 还没有把 framework、app core、Win32 host、D3D12 host 拆成独立 target。 +- `editor/CMakeLists.txt` 还没有把 framework、app core、windowing seam、Win32 host、D3D12 host 拆成独立 target。 - `EditorContext` 仍然过重,混合了全局状态、service locator、command bridge、session sync 和状态输出。 - `EditorShellRuntime` 仍然过重,既持有 panel,又持有 viewport host、shell frame、draw composer 和 interaction coordinator。 - panel 仍缺少统一 feature 接口,`HierarchyPanel`、`ProjectPanel`、`InspectorPanel` 只是模式相似。 -- Win32 host 和 utility host 还没有拆到独立目录层级,例如 `Windowing/Core`、`Windowing/Workspace`、`Windowing/Utility`。 +- `app/Windowing` 目前只是首刀 ownership 调整,workspace / utility window orchestration 仍主要停留在 Win32 coordinator。 这些债务后续要分步处理,不要一次性重写整个窗口系统。 @@ -257,11 +282,11 @@ Application::Run - `editor/app/Bootstrap/Application.*` - `editor/app/Composition/EditorContext.*` - `editor/app/Composition/EditorShellRuntime.*` -- `editor/app/Platform/Win32/Content/EditorWindowContentController.h` +- `editor/app/Windowing/Content/EditorWindowContentController.h` +- `editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.*` - `editor/app/Platform/Win32/Windowing/EditorWindow.*` - `editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.*` - `editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.*` - `editor/app/UtilityWindows/EditorUtilityWindowRegistry.*` - `editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h` - `editor/include/XCEditor/Shell/UIEditorShellInteraction.h` - diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index d47d74e3..3123644a 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -239,17 +239,20 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Scene/EditorSceneBridge.cpp ) + set(XCUI_EDITOR_APP_WINDOWING_SOURCES + app/Windowing/Content/EditorUtilityWindowContentController.cpp + app/Windowing/Content/EditorWorkspaceWindowContentController.cpp + app/Windowing/Frame/EditorWindowFrameOrchestrator.cpp + ) + set(XCUI_EDITOR_APP_PLATFORM_SOURCES app/Platform/Win32/Windowing/EditorWindow.cpp app/Platform/Win32/Windowing/EditorFloatingWindowPlacement.cpp app/Platform/Win32/Windowing/EditorWindowSession.cpp - app/Platform/Win32/Content/EditorUtilityWindowContentController.cpp app/Platform/Win32/Chrome/EditorWindowChromeController.cpp app/Platform/Win32/Runtime/EditorWindowFrameDriver.cpp - app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.cpp app/Platform/Win32/Runtime/EditorWindowInputController.cpp app/Platform/Win32/Runtime/EditorWindowRuntimeController.cpp - app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp app/Platform/Win32/System/Win32SystemInteractionHost.cpp app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp @@ -273,6 +276,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ${XCUI_EDITOR_HOST_PLATFORM_SOURCES} ${XCUI_EDITOR_HOST_RENDERING_SOURCES} ${XCUI_EDITOR_APP_CORE_SOURCES} + ${XCUI_EDITOR_APP_WINDOWING_SOURCES} ${XCUI_EDITOR_APP_PLATFORM_SOURCES} ) diff --git a/editor/app/Bootstrap/Application.cpp b/editor/app/Bootstrap/Application.cpp index 9db9cf45..88ee02d6 100644 --- a/editor/app/Bootstrap/Application.cpp +++ b/editor/app/Bootstrap/Application.cpp @@ -4,7 +4,7 @@ #include "Composition/EditorContext.h" #include "Platform/Win32/Windowing/EditorWindowManager.h" #include "Platform/Win32/Windowing/EditorWindow.h" -#include "Platform/Win32/Content/EditorWorkspaceWindowContentController.h" +#include "Windowing/Content/EditorWorkspaceWindowContentController.h" #include "Platform/Win32/System/Win32SystemInteractionHost.h" #include "Support/EnvironmentFlags.h" #include "Support/ExecutablePath.h" diff --git a/editor/app/Platform/Win32/Chrome/BorderlessWindowChrome.cpp b/editor/app/Platform/Win32/Chrome/BorderlessWindowChrome.cpp index c2b6d20f..9161523d 100644 --- a/editor/app/Platform/Win32/Chrome/BorderlessWindowChrome.cpp +++ b/editor/app/Platform/Win32/Chrome/BorderlessWindowChrome.cpp @@ -24,6 +24,7 @@ bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) { BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout( const UIRect& titleBarRect, float leadingOccupiedRight, + bool includePinButton, const BorderlessWindowChromeMetrics& metrics) { BorderlessWindowChromeLayout layout = {}; layout.titleBarRect = titleBarRect; @@ -32,18 +33,30 @@ BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout( } const float buttonWidth = (std::max)(metrics.buttonWidth, 0.0f); - const float buttonX3 = titleBarRect.x + titleBarRect.width - metrics.buttonInsetX - buttonWidth; - const float buttonX2 = buttonX3 - buttonWidth; - const float buttonX1 = buttonX2 - buttonWidth; + const float rightEdge = titleBarRect.x + titleBarRect.width - metrics.buttonInsetX; + const float closeButtonX = rightEdge - buttonWidth; + const float maximizeButtonX = closeButtonX - buttonWidth; + const float minimizeButtonX = maximizeButtonX - buttonWidth; + const float pinButtonX = minimizeButtonX - buttonWidth; - layout.minimizeButtonRect = UIRect(buttonX1, titleBarRect.y, buttonWidth, titleBarRect.height); - layout.maximizeRestoreButtonRect = UIRect(buttonX2, titleBarRect.y, buttonWidth, titleBarRect.height); - layout.closeButtonRect = UIRect(buttonX3, titleBarRect.y, buttonWidth, titleBarRect.height); + if (includePinButton) { + layout.pinButtonRect = UIRect(pinButtonX, titleBarRect.y, buttonWidth, titleBarRect.height); + } + layout.minimizeButtonRect = + UIRect(minimizeButtonX, titleBarRect.y, buttonWidth, titleBarRect.height); + layout.maximizeRestoreButtonRect = + UIRect(maximizeButtonX, titleBarRect.y, buttonWidth, titleBarRect.height); + layout.closeButtonRect = + UIRect(closeButtonX, titleBarRect.y, buttonWidth, titleBarRect.height); const float dragLeft = (std::max)(titleBarRect.x, leadingOccupiedRight + metrics.dragPaddingLeft); + const float buttonClusterLeft = + includePinButton + ? layout.pinButtonRect.x + : layout.minimizeButtonRect.x; const float dragRight = - (std::max)(dragLeft, layout.minimizeButtonRect.x - metrics.dragPaddingRight); + (std::max)(dragLeft, buttonClusterLeft - metrics.dragPaddingRight); layout.dragRect = UIRect( dragLeft, titleBarRect.y, @@ -64,6 +77,9 @@ BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome( if (IsPointInsideRect(layout.minimizeButtonRect, point)) { return BorderlessWindowChromeHitTarget::MinimizeButton; } + if (IsPointInsideRect(layout.pinButtonRect, point)) { + return BorderlessWindowChromeHitTarget::PinButton; + } if (IsPointInsideRect(layout.dragRect, point)) { return BorderlessWindowChromeHitTarget::DragRegion; } @@ -175,10 +191,45 @@ void AppendCloseGlyph( thickness); } +void AppendPinGlyph( + UIDrawList& drawList, + const UIRect& rect, + const UIColor& color, + float thickness) { + const float centerX = rect.x + rect.width * 0.5f; + const float centerY = rect.y + rect.height * 0.5f; + const float iconSize = ResolveGlyphBoxSize(rect, 0.34f, 10.0f); + const float headRadius = iconSize * 0.18f; + const float headCenterY = centerY - iconSize * 0.30f; + const float collarTopY = headCenterY + headRadius; + const float collarBottomY = collarTopY + iconSize * 0.12f; + const float shoulderHalfWidth = iconSize * 0.34f; + const float bodyBottomY = centerY + iconSize * 0.06f; + const float needleBottomY = centerY + iconSize * 0.44f; + + drawList.AddFilledCircle(UIPoint(centerX, headCenterY), headRadius, color); + drawList.AddLine( + UIPoint(centerX, collarTopY), + UIPoint(centerX, collarBottomY), + color, + thickness); + drawList.AddFilledTriangle( + UIPoint(centerX - shoulderHalfWidth, collarBottomY), + UIPoint(centerX + shoulderHalfWidth, collarBottomY), + UIPoint(centerX, bodyBottomY), + color); + drawList.AddLine( + UIPoint(centerX, bodyBottomY), + UIPoint(centerX, needleBottomY), + color, + thickness); +} + UIColor ResolveButtonFillColor( BorderlessWindowChromeHitTarget target, const BorderlessWindowChromeState& state, - const BorderlessWindowChromePalette& palette) { + const BorderlessWindowChromePalette& palette, + bool topmostPinned) { const bool hovered = state.hoveredTarget == target; const bool pressed = state.pressedTarget == target; if (target == BorderlessWindowChromeHitTarget::CloseButton) { @@ -191,6 +242,11 @@ UIColor ResolveButtonFillColor( return kTransparentColor; } + if (target == BorderlessWindowChromeHitTarget::PinButton && topmostPinned && + !hovered && !pressed) { + return palette.buttonActiveColor; + } + if (pressed) { return palette.buttonPressedColor; } @@ -203,12 +259,17 @@ UIColor ResolveButtonFillColor( UIColor ResolveIconColor( BorderlessWindowChromeHitTarget target, const BorderlessWindowChromeState& state, - const BorderlessWindowChromePalette& palette) { + const BorderlessWindowChromePalette& palette, + bool topmostPinned) { if (target == BorderlessWindowChromeHitTarget::CloseButton && (state.hoveredTarget == target || state.pressedTarget == target)) { return palette.closeIconHoverColor; } + if (target == BorderlessWindowChromeHitTarget::PinButton && topmostPinned) { + return palette.iconActiveColor; + } + return palette.iconColor; } @@ -219,25 +280,34 @@ void AppendBorderlessWindowChrome( const BorderlessWindowChromeLayout& layout, const BorderlessWindowChromeState& state, bool maximized, + bool topmostPinned, const BorderlessWindowChromePalette& palette, const BorderlessWindowChromeMetrics& metrics) { const struct ButtonEntry { BorderlessWindowChromeHitTarget target; UIRect rect; } buttons[] = { + { BorderlessWindowChromeHitTarget::PinButton, layout.pinButtonRect }, { BorderlessWindowChromeHitTarget::MinimizeButton, layout.minimizeButtonRect }, { BorderlessWindowChromeHitTarget::MaximizeRestoreButton, layout.maximizeRestoreButtonRect }, { BorderlessWindowChromeHitTarget::CloseButton, layout.closeButtonRect } }; for (const ButtonEntry& button : buttons) { - const UIColor fill = ResolveButtonFillColor(button.target, state, palette); + if (button.rect.width <= 0.0f || button.rect.height <= 0.0f) { + continue; + } + + const UIColor fill = ResolveButtonFillColor(button.target, state, palette, topmostPinned); if (fill.a > 0.0f) { drawList.AddFilledRect(button.rect, fill); } - const UIColor iconColor = ResolveIconColor(button.target, state, palette); + const UIColor iconColor = ResolveIconColor(button.target, state, palette, topmostPinned); switch (button.target) { + case BorderlessWindowChromeHitTarget::PinButton: + AppendPinGlyph(drawList, button.rect, iconColor, metrics.iconThickness); + break; case BorderlessWindowChromeHitTarget::MinimizeButton: AppendMinimizeGlyph(drawList, button.rect, iconColor, metrics.iconThickness); break; diff --git a/editor/app/Platform/Win32/Chrome/BorderlessWindowChrome.h b/editor/app/Platform/Win32/Chrome/BorderlessWindowChrome.h index afd465e5..aa37aa3b 100644 --- a/editor/app/Platform/Win32/Chrome/BorderlessWindowChrome.h +++ b/editor/app/Platform/Win32/Chrome/BorderlessWindowChrome.h @@ -13,6 +13,7 @@ namespace XCEngine::UI::Editor::Host { enum class BorderlessWindowChromeHitTarget : std::uint8_t { None = 0, DragRegion, + PinButton, MinimizeButton, MaximizeRestoreButton, CloseButton @@ -31,12 +32,16 @@ struct BorderlessWindowChromePalette { ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor buttonPressedColor = ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor buttonActiveColor = + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); ::XCEngine::UI::UIColor closeButtonHoverColor = ::XCEngine::UI::UIColor(0.84f, 0.28f, 0.22f, 1.0f); ::XCEngine::UI::UIColor closeButtonPressedColor = ::XCEngine::UI::UIColor(0.72f, 0.20f, 0.16f, 1.0f); ::XCEngine::UI::UIColor iconColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + ::XCEngine::UI::UIColor iconActiveColor = + ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); ::XCEngine::UI::UIColor closeIconHoverColor = ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); }; @@ -49,6 +54,7 @@ struct BorderlessWindowChromeState { struct BorderlessWindowChromeLayout { ::XCEngine::UI::UIRect titleBarRect = {}; ::XCEngine::UI::UIRect dragRect = {}; + ::XCEngine::UI::UIRect pinButtonRect = {}; ::XCEngine::UI::UIRect minimizeButtonRect = {}; ::XCEngine::UI::UIRect maximizeRestoreButtonRect = {}; ::XCEngine::UI::UIRect closeButtonRect = {}; @@ -57,6 +63,7 @@ struct BorderlessWindowChromeLayout { BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout( const ::XCEngine::UI::UIRect& titleBarRect, float leadingOccupiedRight, + bool includePinButton = false, const BorderlessWindowChromeMetrics& metrics = {}); BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome( @@ -68,6 +75,7 @@ void AppendBorderlessWindowChrome( const BorderlessWindowChromeLayout& layout, const BorderlessWindowChromeState& state, bool maximized, + bool topmostPinned = false, const BorderlessWindowChromePalette& palette = {}, const BorderlessWindowChromeMetrics& metrics = {}); diff --git a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp index b7f0fef7..af4bd621 100644 --- a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp +++ b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp @@ -38,6 +38,20 @@ bool IsVerboseResizeTraceEnabled() { return s_enabled; } +bool IsChromeButtonTarget(Host::BorderlessWindowChromeHitTarget target) { + return target == Host::BorderlessWindowChromeHitTarget::PinButton || + target == Host::BorderlessWindowChromeHitTarget::MinimizeButton || + target == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || + target == Host::BorderlessWindowChromeHitTarget::CloseButton; +} + +float ResolveLeadingChromeButtonLeft( + const Host::BorderlessWindowChromeLayout& layout) { + return layout.pinButtonRect.width > 0.0f + ? layout.pinButtonRect.x + : layout.minimizeButtonRect.x; +} + float ResolveDetachedTabWidth( std::string_view text, const UIEditorTextMeasurer* textMeasurer) { @@ -54,7 +68,7 @@ float ResolveDetachedTabWidth( UIRect BuildDetachedTitleLogoRect(const Host::BorderlessWindowChromeLayout& layout) { const float availableLeft = layout.titleBarRect.x; - const float availableRight = layout.minimizeButtonRect.x; + const float availableRight = ResolveLeadingChromeButtonLeft(layout); const float centeredX = std::floor( layout.titleBarRect.x + layout.titleBarRect.width * 0.5f - kTitleBarLogoExtent * 0.5f); const float clampedX = (std::max)( @@ -227,6 +241,15 @@ const Host::BorderlessWindowChromeState& EditorWindowChromeController::GetChrome return m_chromeState; } +void EditorWindowChromeController::InitializeWindowChrome(EditorWindow& window) { + if (!window.GetChromePolicy().topmostByDefault) { + SetWindowTopmost(false); + return; + } + + ApplyWindowTopmost(window, true); +} + bool EditorWindowChromeController::HandleSystemCommand( EditorWindow& window, EditorContext& editorContext, @@ -463,10 +486,7 @@ bool EditorWindowChromeController::UpdateChromeHover(EditorWindow& window, LPARA } const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestChrome(window, lParam); - const Host::BorderlessWindowChromeHitTarget buttonTarget = - hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || - hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || - hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton + const Host::BorderlessWindowChromeHitTarget buttonTarget = IsChromeButtonTarget(hitTarget) ? hitTarget : Host::BorderlessWindowChromeHitTarget::None; if (GetHoveredChromeTarget() == buttonTarget) { @@ -486,6 +506,7 @@ bool EditorWindowChromeController::HandleChromeButtonDown(EditorWindow& window, const HWND hwnd = window.GetHwnd(); const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestChrome(window, lParam); switch (hitTarget) { + case Host::BorderlessWindowChromeHitTarget::PinButton: case Host::BorderlessWindowChromeHitTarget::MinimizeButton: case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: case Host::BorderlessWindowChromeHitTarget::CloseButton: @@ -525,9 +546,7 @@ bool EditorWindowChromeController::HandleChromeButtonUp( } const Host::BorderlessWindowChromeHitTarget pressedTarget = GetPressedChromeTarget(); - if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton && - pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton && - pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) { + if (!IsChromeButtonTarget(pressedTarget)) { return false; } @@ -692,7 +711,8 @@ Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLa return Host::BuildBorderlessWindowChromeLayout( UIRect(0.0f, 0.0f, clientWidthDips, kBorderlessTitleBarHeightDips), - leadingOccupiedRight); + leadingOccupiedRight, + window.GetChromePolicy().showTopmostButton); } bool EditorWindowChromeController::ShouldUseDetachedTitleBarTabStrip( @@ -828,7 +848,8 @@ void EditorWindowChromeController::AppendChrome( drawList, layout, GetChromeState(), - IsBorderlessWindowMaximized()); + IsBorderlessWindowMaximized(), + IsWindowTopmost()); } void EditorWindowChromeController::ApplyResizeCursorHoverPriority() { @@ -907,6 +928,35 @@ bool EditorWindowChromeController::ApplyPredictedWindowRectTransition( return true; } +void EditorWindowChromeController::SetWindowTopmost(bool topmost) { + m_runtimeState.SetWindowTopmost(topmost); +} + +bool EditorWindowChromeController::IsWindowTopmost() const { + return m_runtimeState.IsWindowTopmost(); +} + +bool EditorWindowChromeController::ApplyWindowTopmost(EditorWindow& window, bool topmost) { + const HWND hwnd = window.GetHwnd(); + if (hwnd == nullptr) { + return false; + } + + if (!SetWindowPos( + hwnd, + topmost ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE)) { + return false; + } + + SetWindowTopmost(topmost); + return true; +} + void EditorWindowChromeController::ToggleMaximizeRestore( EditorWindow& window, EditorContext& editorContext, @@ -949,6 +999,9 @@ void EditorWindowChromeController::ExecuteChromeAction( } switch (target) { + case Host::BorderlessWindowChromeHitTarget::PinButton: + ApplyWindowTopmost(window, !IsWindowTopmost()); + break; case Host::BorderlessWindowChromeHitTarget::MinimizeButton: ShowWindow(hwnd, SW_MINIMIZE); break; diff --git a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h index 74b4807d..e74570ea 100644 --- a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h +++ b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h @@ -74,6 +74,7 @@ public: void ResetChromeState(); bool IsChromeStateClear() const; const Host::BorderlessWindowChromeState& GetChromeState() const; + void InitializeWindowChrome(EditorWindow& window); bool HandleSystemCommand( EditorWindow& window, @@ -133,6 +134,9 @@ public: EditorContext& editorContext, bool globalTabDragActive, const RECT& targetRect); + void SetWindowTopmost(bool topmost); + bool IsWindowTopmost() const; + bool ApplyWindowTopmost(EditorWindow& window, bool topmost); void ToggleMaximizeRestore( EditorWindow& window, EditorContext& editorContext, diff --git a/editor/app/Platform/Win32/Chrome/HostRuntimeState.h b/editor/app/Platform/Win32/Chrome/HostRuntimeState.h index 1d5f81aa..52ae1a05 100644 --- a/editor/app/Platform/Win32/Chrome/HostRuntimeState.h +++ b/editor/app/Platform/Win32/Chrome/HostRuntimeState.h @@ -41,6 +41,7 @@ public: m_predictedClientPixelSize = {}; m_borderlessWindowPlacementState = {}; m_borderlessWindowDragRestoreState = {}; + m_windowTopmost = false; m_skipNextSteadyStateFrame = false; } @@ -213,6 +214,14 @@ public: return m_borderlessWindowDragRestoreState.initialScreenPoint; } + void SetWindowTopmost(bool topmost) { + m_windowTopmost = topmost; + } + + bool IsWindowTopmost() const { + return m_windowTopmost; + } + private: UINT m_windowDpi = 96u; bool m_inInteractiveResize = false; @@ -220,6 +229,7 @@ private: PredictedClientPixelSize m_predictedClientPixelSize = {}; BorderlessWindowPlacementState m_borderlessWindowPlacementState = {}; BorderlessWindowDragRestoreState m_borderlessWindowDragRestoreState = {}; + bool m_windowTopmost = false; bool m_skipNextSteadyStateFrame = false; }; diff --git a/editor/app/Platform/Win32/Runtime/EditorWindowFrameDriver.h b/editor/app/Platform/Win32/Runtime/EditorWindowFrameDriver.h index d005cb94..905ddb11 100644 --- a/editor/app/Platform/Win32/Runtime/EditorWindowFrameDriver.h +++ b/editor/app/Platform/Win32/Runtime/EditorWindowFrameDriver.h @@ -1,6 +1,6 @@ #pragma once -#include "Platform/Win32/Windowing/EditorWindowTransferRequests.h" +#include "Windowing/Frame/EditorWindowTransferRequests.h" namespace XCEngine::UI::Editor::App { diff --git a/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h b/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h index 881ca11e..fc684206 100644 --- a/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h +++ b/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h @@ -4,7 +4,7 @@ #define NOMINMAX #endif -#include "Platform/Win32/Content/EditorWindowContentController.h" +#include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Runtime/EditorWindowScreenshotController.h" #include diff --git a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp b/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp index ef9a7ebc..f5d4be4c 100644 --- a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp @@ -2,9 +2,9 @@ #include "UtilityWindows/EditorUtilityWindowRegistry.h" #include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h" -#include "Platform/Win32/Content/EditorUtilityWindowContentController.h" +#include "Windowing/Content/EditorUtilityWindowContentController.h" #include "Platform/Win32/Windowing/EditorWindow.h" -#include "Platform/Win32/Content/EditorWindowContentController.h" +#include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" #include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" diff --git a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h b/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h index 1b2ebe5e..87411a82 100644 --- a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h +++ b/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h @@ -1,6 +1,6 @@ #pragma once -#include "Platform/Win32/Windowing/EditorWindowTransferRequests.h" +#include "Windowing/Frame/EditorWindowTransferRequests.h" #include diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp index da966419..f9e125a1 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp @@ -1,11 +1,11 @@ #include "Platform/Win32/Windowing/EditorWindow.h" #include "Bootstrap/EditorResources.h" #include "Platform/Win32/Chrome/EditorWindowChromeController.h" -#include "Platform/Win32/Content/EditorWindowContentController.h" +#include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Runtime/EditorWindowFrameDriver.h" #include "Platform/Win32/Windowing/EditorWindowSession.h" #include "Platform/Win32/Windowing/EditorWindowSupport.h" -#include "Platform/Win32/Runtime/EditorWindowFrameOrchestrator.h" +#include "Windowing/Frame/EditorWindowFrameOrchestrator.h" #include "Platform/Win32/Runtime/EditorWindowInputController.h" #include "Platform/Win32/Runtime/EditorWindowRuntimeController.h" #include "Composition/EditorContext.h" @@ -219,6 +219,7 @@ bool EditorWindow::Initialize( Host::RefreshBorderlessWindowDwmDecorations(m_session->GetHwnd()); m_chromeController->Reset(); m_chromeController->SetWindowDpi(QueryWindowDpi(m_session->GetHwnd())); + m_chromeController->InitializeWindowChrome(*this); m_runtime->SetDpiScale(GetDpiScale()); std::ostringstream dpiTrace = {}; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp index b246c051..b8ac50ef 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp @@ -4,7 +4,7 @@ #include "Composition/EditorContext.h" #include "Platform/Win32/Chrome/EditorWindowChromeController.h" #include "Platform/Win32/Windowing/EditorWindow.h" -#include "Platform/Win32/Content/EditorWindowContentController.h" +#include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Runtime/EditorWindowFrameDriver.h" #include "Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h" #include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowManager.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowManager.cpp index 83add784..7cf57f53 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowManager.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowManager.cpp @@ -1,7 +1,7 @@ #include "Platform/Win32/Windowing/EditorWindowManager.h" #include "Platform/Win32/Windowing/EditorWindow.h" -#include "Platform/Win32/Content/EditorWindowContentController.h" +#include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" #include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" #include "Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h" diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp index 2e40e78b..fcc8468c 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp @@ -160,7 +160,8 @@ bool EditorWindowMessageDispatcher::TryHandleChromeHoverConsumption( const Host::BorderlessWindowChromeHitTarget chromeHitTarget = chromeController.HitTestChrome(context.window, lParam); - if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || + if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::PinButton || + chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || chromeHitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton) { outResult = 0; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.h b/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.h index d506a727..03a3436d 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.h @@ -4,7 +4,7 @@ #define NOMINMAX #endif -#include "Platform/Win32/Windowing/EditorWindowTransferRequests.h" +#include "Windowing/Frame/EditorWindowTransferRequests.h" #include diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowSession.h b/editor/app/Platform/Win32/Windowing/EditorWindowSession.h index 95a5065a..bfd73176 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowSession.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowSession.h @@ -1,7 +1,7 @@ #pragma once #include "Platform/Win32/Windowing/EditorWindowState.h" -#include "Platform/Win32/Windowing/EditorWindowTransferRequests.h" +#include "Windowing/Frame/EditorWindowTransferRequests.h" #include #include diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowState.h b/editor/app/Platform/Win32/Windowing/EditorWindowState.h index bd9abbd7..f7c7d97b 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowState.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowState.h @@ -41,6 +41,8 @@ inline std::string_view GetEditorWindowCategoryName( struct EditorWindowChromePolicy { bool allowDetachedTitleBarTabStrip = true; bool showFrameStats = true; + bool showTopmostButton = false; + bool topmostByDefault = false; }; struct EditorWindowNativeStylePolicy { diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp index 6cba00ed..4d439e1c 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp @@ -3,7 +3,7 @@ #include "Composition/EditorContext.h" #include "Platform/Win32/Windowing/EditorWindow.h" #include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h" -#include "Platform/Win32/Content/EditorWorkspaceWindowContentController.h" +#include "Windowing/Content/EditorWorkspaceWindowContentController.h" #include "Platform/Win32/Runtime/EditorWindowRuntimeController.h" #include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" #include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h b/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h index d61ffc78..d35cb9eb 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h @@ -5,7 +5,7 @@ #endif #include "Composition/EditorWindowWorkspaceStore.h" -#include "Platform/Win32/Windowing/EditorWindowTransferRequests.h" +#include "Windowing/Frame/EditorWindowTransferRequests.h" #include diff --git a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp index f0496d6c..67fd1eff 100644 --- a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp +++ b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp @@ -12,6 +12,8 @@ using ::XCEngine::UI::UISize; constexpr EditorWindowChromePolicy kUtilityWindowChromePolicy = { .allowDetachedTitleBarTabStrip = false, .showFrameStats = false, + .showTopmostButton = true, + .topmostByDefault = true, }; constexpr EditorWindowNativeStylePolicy kUtilityWindowNativeStylePolicy = { @@ -26,8 +28,8 @@ constexpr EditorUtilityWindowDescriptor kColorPickerUtilityWindowDescriptor = { .title = L"Color Picker", .chromePolicy = kUtilityWindowChromePolicy, .nativeStylePolicy = kUtilityWindowNativeStylePolicy, - .preferredOuterSize = UISize(352.0f, 500.0f), - .minimumOuterSize = UISize(320.0f, 460.0f), + .preferredOuterSize = UISize(400.0f, 620.0f), + .minimumOuterSize = UISize(360.0f, 560.0f), }; constexpr EditorUtilityWindowDescriptor kAddComponentUtilityWindowDescriptor = { diff --git a/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.cpp b/editor/app/Windowing/Content/EditorUtilityWindowContentController.cpp similarity index 97% rename from editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.cpp rename to editor/app/Windowing/Content/EditorUtilityWindowContentController.cpp index f7dd5759..563221f6 100644 --- a/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.cpp +++ b/editor/app/Windowing/Content/EditorUtilityWindowContentController.cpp @@ -1,4 +1,4 @@ -#include "Platform/Win32/Content/EditorUtilityWindowContentController.h" +#include "Windowing/Content/EditorUtilityWindowContentController.h" #include "UtilityWindows/EditorUtilityWindowPanel.h" #include "UtilityWindows/EditorUtilityWindowRegistry.h" diff --git a/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.h b/editor/app/Windowing/Content/EditorUtilityWindowContentController.h similarity index 95% rename from editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.h rename to editor/app/Windowing/Content/EditorUtilityWindowContentController.h index bb3dd3e0..1b2d2d11 100644 --- a/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.h +++ b/editor/app/Windowing/Content/EditorUtilityWindowContentController.h @@ -1,6 +1,6 @@ #pragma once -#include "Platform/Win32/Content/EditorWindowContentController.h" +#include "Windowing/Content/EditorWindowContentController.h" #include diff --git a/editor/app/Platform/Win32/Content/EditorWindowContentController.h b/editor/app/Windowing/Content/EditorWindowContentController.h similarity index 98% rename from editor/app/Platform/Win32/Content/EditorWindowContentController.h rename to editor/app/Windowing/Content/EditorWindowContentController.h index ca6e196c..b3c229f1 100644 --- a/editor/app/Platform/Win32/Content/EditorWindowContentController.h +++ b/editor/app/Windowing/Content/EditorWindowContentController.h @@ -1,6 +1,6 @@ #pragma once -#include "Platform/Win32/Windowing/EditorWindowTransferRequests.h" +#include "Windowing/Frame/EditorWindowTransferRequests.h" #include #include diff --git a/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp similarity index 98% rename from editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp rename to editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp index a85566c0..c2233224 100644 --- a/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp @@ -1,4 +1,4 @@ -#include "Platform/Win32/Content/EditorWorkspaceWindowContentController.h" +#include "Windowing/Content/EditorWorkspaceWindowContentController.h" #include diff --git a/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.h b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h similarity index 96% rename from editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.h rename to editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h index a74add20..5cd0c388 100644 --- a/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.h +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h @@ -1,8 +1,8 @@ #pragma once #include "Composition/EditorShellRuntime.h" -#include "Platform/Win32/Content/EditorWindowContentController.h" -#include "Platform/Win32/Runtime/EditorWindowFrameOrchestrator.h" +#include "Windowing/Content/EditorWindowContentController.h" +#include "Windowing/Frame/EditorWindowFrameOrchestrator.h" #include diff --git a/editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.cpp b/editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.cpp similarity index 99% rename from editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.cpp rename to editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.cpp index e739873c..47148c5f 100644 --- a/editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.cpp +++ b/editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.cpp @@ -1,4 +1,4 @@ -#include "Platform/Win32/Runtime/EditorWindowFrameOrchestrator.h" +#include "Windowing/Frame/EditorWindowFrameOrchestrator.h" #include "Composition/EditorContext.h" #include "Composition/EditorShellRuntime.h" diff --git a/editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.h b/editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.h similarity index 97% rename from editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.h rename to editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.h index eb080305..b23c7d55 100644 --- a/editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.h +++ b/editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.h @@ -1,6 +1,6 @@ #pragma once -#include "Platform/Win32/Windowing/EditorWindowTransferRequests.h" +#include "Windowing/Frame/EditorWindowTransferRequests.h" #include diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowTransferRequests.h b/editor/app/Windowing/Frame/EditorWindowTransferRequests.h similarity index 100% rename from editor/app/Platform/Win32/Windowing/EditorWindowTransferRequests.h rename to editor/app/Windowing/Frame/EditorWindowTransferRequests.h