Refactor editor window runtime ownership

This commit is contained in:
2026-04-26 20:40:32 +08:00
parent fa4fcbe95b
commit ee05558f86
18 changed files with 1056 additions and 398 deletions

View File

@@ -4,7 +4,7 @@
#include "Platform/Win32/Windowing/EditorWindowSession.h"
#include "Platform/Win32/Windowing/EditorWindowSupport.h"
#include "Platform/Win32/Runtime/EditorWindowInputController.h"
#include "Windowing/Runtime/EditorWindowRuntimeController.h"
#include "Windowing/Content/EditorWindowContentController.h"
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
@@ -80,27 +80,33 @@ EditorWindowScreenPoint EditorWindow::FromNativePoint(const POINT& screenPoint)
return point;
}
EditorWindow::EditorWindow(
std::string windowId,
std::wstring title,
EditorWindowCategory category,
EditorWindowChromePolicy chromePolicy,
bool primary,
std::unique_ptr<EditorWindowRuntimeController> runtimeController)
: m_session(std::make_unique<EditorWindowSession>(
std::move(windowId),
std::move(title),
category,
chromePolicy,
primary))
EditorWindow::EditorWindow(EditorHostWindow& owner)
: m_owner(owner)
, m_session(std::make_unique<EditorWindowSession>(
std::string(owner.GetWindowId()),
std::wstring(owner.GetTitle()),
owner.IsWorkspaceWindow()
? EditorWindowCategory::Workspace
: EditorWindowCategory::Utility,
owner.GetChromePolicy(),
owner.IsPrimary()))
, m_chromeController(std::make_unique<EditorWindowChromeController>())
, m_inputController(std::make_unique<EditorWindowInputController>())
, m_runtime(std::move(runtimeController)) {}
, m_inputController(std::make_unique<EditorWindowInputController>()) {}
EditorWindow::~EditorWindow() = default;
EditorWindow::~EditorWindow() {
m_owner.DetachNativePeer(*this);
}
EditorHostWindow& EditorWindow::GetOwner() {
return m_owner;
}
const EditorHostWindow& EditorWindow::GetOwner() const {
return m_owner;
}
std::string_view EditorWindow::GetWindowId() const {
return m_session->GetWindowId();
return m_owner.GetWindowId();
}
HWND EditorWindow::GetHwnd() const {
@@ -112,35 +118,37 @@ bool EditorWindow::HasHwnd() const {
}
EditorWindowCategory EditorWindow::GetCategory() const {
return m_session->GetCategory();
return m_owner.IsWorkspaceWindow()
? EditorWindowCategory::Workspace
: EditorWindowCategory::Utility;
}
const EditorWindowChromePolicy& EditorWindow::GetChromePolicy() const {
return m_session->GetChromePolicy();
return m_owner.GetChromePolicy();
}
EditorWindowLifecycleState EditorWindow::GetLifecycleState() const {
return m_session->GetLifecycleState();
return m_owner.GetLifecycleState();
}
bool EditorWindow::IsPrimary() const {
return m_session->IsPrimary();
return m_owner.IsPrimary();
}
bool EditorWindow::IsWorkspaceWindow() const {
return m_session->IsWorkspaceWindow();
return m_owner.IsWorkspaceWindow();
}
bool EditorWindow::IsUtilityWindow() const {
return m_session->IsUtilityWindow();
return m_owner.IsUtilityWindow();
}
bool EditorWindow::IsClosing() const {
return m_session->IsClosing();
return m_owner.IsClosing();
}
bool EditorWindow::IsDestroyed() const {
return m_session->IsDestroyed();
return m_owner.IsDestroyed();
}
bool EditorWindow::HasLiveHostWindow() const {
@@ -148,74 +156,37 @@ bool EditorWindow::HasLiveHostWindow() const {
return hwnd != nullptr && IsWindow(hwnd);
}
bool EditorWindow::IsRenderReady() const {
return m_runtime->IsReady();
}
const std::wstring& EditorWindow::GetTitle() const {
return m_session->GetTitle();
return m_owner.GetTitle();
}
std::string_view EditorWindow::GetCachedTitleText() const {
return m_session->GetCachedTitleText();
return m_owner.GetCachedTitleText();
}
const EditorWorkspaceWindowProjection* EditorWindow::TryGetWorkspaceProjection() const {
const EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding();
return workspaceBinding != nullptr
? workspaceBinding->TryGetWorkspaceProjection()
: nullptr;
return m_owner.TryGetWorkspaceProjection();
}
EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() {
return m_runtime->TryGetDockHostBinding();
return m_owner.TryGetDockHostBinding();
}
const EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() const {
return m_runtime->TryGetDockHostBinding();
return m_owner.TryGetDockHostBinding();
}
void EditorWindow::AttachHwnd(HWND hwnd) {
m_session->AttachHwnd(hwnd);
m_owner.MarkNativeAttached();
}
void EditorWindow::MarkInitializing() {
m_session->MarkInitializing();
}
void EditorWindow::MarkRunning() {
m_session->MarkRunning();
}
void EditorWindow::MarkDestroyed() {
m_session->MarkDestroyed();
m_inputController->ResetWindowState();
}
void EditorWindow::MarkClosing() {
m_session->MarkClosing();
}
void EditorWindow::SetPrimary(bool primary) {
m_session->SetPrimary(primary);
}
void EditorWindow::SetTitle(std::wstring title) {
m_session->SetTitle(std::move(title));
}
void EditorWindow::ApplyHostWindowTitle() {
void EditorWindow::ApplyHostWindowTitle(const std::wstring& title) {
if (HasLiveHostWindow()) {
SetWindowTextW(m_session->GetHwnd(), GetTitle().c_str());
SetWindowTextW(m_session->GetHwnd(), title.c_str());
}
}
void EditorWindow::RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection) {
EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding();
assert(workspaceBinding != nullptr);
workspaceBinding->RefreshWorkspaceProjection(std::move(projection));
}
void EditorWindow::InvalidateHostWindow() const {
if (const HWND hwnd = m_session->GetHwnd();
hwnd != nullptr && IsWindow(hwnd)) {
@@ -223,75 +194,41 @@ void EditorWindow::InvalidateHostWindow() const {
}
}
bool EditorWindow::InitializeRuntime(
const EditorHostWindowRuntimeInitializationParams& params) {
if (m_session->GetHwnd() == nullptr) {
LogRuntimeTrace("app", "window initialize skipped: hwnd is null");
return false;
}
void* EditorWindow::GetNativeWindowHandle() const {
return m_session->GetHwnd();
}
void EditorWindow::PrepareRuntimeInitialization(EditorHostWindow& window) {
(void)window;
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 = {};
dpiTrace << "initial dpi=" << m_chromeController->GetWindowDpi()
<< " scale=" << GetDpiScale();
LogRuntimeTrace("window", dpiTrace.str());
MarkInitializing();
UINT clientWidth = 0u;
UINT clientHeight = 0u;
if (!QueryCurrentClientPixelSize(clientWidth, clientHeight)) {
clientWidth = 1u;
clientHeight = 1u;
}
const bool initialized = m_runtime->Initialize(
Rendering::Host::EditorWindowRenderRuntimeSurface{
.nativeWindowHandle = m_session->GetHwnd(),
.widthPixels = clientWidth,
.heightPixels = clientHeight,
},
params.repoRoot,
params.captureRoot,
params.autoCaptureOnStartup);
if (initialized) {
MarkRunning();
} else {
m_session->MarkNativeAttached();
}
return initialized;
}
void EditorWindow::Shutdown() {
void EditorWindow::ShutdownNativeInteraction() {
std::ostringstream trace = {};
trace << "EditorWindow::Shutdown begin windowId='" << GetWindowId()
trace << "EditorWindow::ShutdownNativeInteraction begin windowId='" << GetWindowId()
<< "' hwnd=0x" << std::hex << std::uppercase
<< reinterpret_cast<std::uintptr_t>(GetHwnd())
<< std::dec
<< " primary=" << (IsPrimary() ? 1 : 0)
<< " lifecycle=" << GetEditorWindowLifecycleStateName(GetLifecycleState())
<< " runtimeReady=" << (m_runtime->IsReady() ? 1 : 0);
<< " lifecycle=" << GetEditorWindowLifecycleStateName(GetLifecycleState());
LogRuntimeTrace("window-close", trace.str());
ForceReleasePointerCapture();
if (m_runtime->IsReady()) {
m_runtime->Shutdown();
}
m_inputController->ClearPendingEvents();
m_chromeController->Reset();
LogRuntimeTrace(
"window-close",
"EditorWindow::Shutdown end windowId='" + std::string(GetWindowId()) + "'");
"EditorWindow::ShutdownNativeInteraction end windowId='" +
std::string(GetWindowId()) + "'");
}
void EditorWindow::ResetInteractionState() {
void EditorWindow::ResetNativeInteractionState() {
ForceReleasePointerCapture();
m_inputController->ResetInteractionState();
m_runtime->ResetInteractionState();
m_chromeController->ResetChromeState();
m_chromeController->EndBorderlessResize();
m_chromeController->EndBorderlessWindowDragRestore();
@@ -302,13 +239,15 @@ void EditorWindow::ResetInteractionState() {
}
bool EditorWindow::ApplyWindowResize(UINT width, UINT height) {
if (!m_runtime->IsReady() || width == 0u || height == 0u) {
if (!m_owner.IsRenderReady() || width == 0u || height == 0u) {
return false;
}
return m_runtime->ApplyResize(width, height);
return m_owner.ApplyResize(width, height);
}
bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const {
bool EditorWindow::QueryCurrentClientPixelSize(
std::uint32_t& outWidth,
std::uint32_t& outHeight) const {
outWidth = 0u;
outHeight = 0u;
const HWND hwnd = m_session->GetHwnd();
@@ -327,13 +266,19 @@ bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight)
return false;
}
outWidth = static_cast<UINT>(width);
outHeight = static_cast<UINT>(height);
outWidth = static_cast<std::uint32_t>(width);
outHeight = static_cast<std::uint32_t>(height);
return true;
}
bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const {
if (m_chromeController->TryGetPredictedClientPixelSize(outWidth, outHeight)) {
bool EditorWindow::ResolveRenderClientPixelSize(
std::uint32_t& outWidth,
std::uint32_t& outHeight) const {
UINT nativeWidth = 0u;
UINT nativeHeight = 0u;
if (m_chromeController->TryGetPredictedClientPixelSize(nativeWidth, nativeHeight)) {
outWidth = nativeWidth;
outHeight = nativeHeight;
return true;
}
@@ -378,7 +323,7 @@ bool EditorWindow::TryResolveDockTabDragHotspot(
std::string_view panelId,
const EditorWindowScreenPoint& screenPoint,
EditorWindowScreenPoint& outHotspot) const {
const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding();
const EditorWindowDockHostBinding* dockHostBinding = m_owner.TryGetDockHostBinding();
if (dockHostBinding == nullptr) {
outHotspot = {};
return false;
@@ -404,7 +349,7 @@ bool EditorWindow::TryResolveDockTabDragHotspot(
bool EditorWindow::TryResolveDockTabDropTarget(
const EditorWindowScreenPoint& screenPoint,
UIEditorDockHostTabDropTarget& outTarget) const {
const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding();
const EditorWindowDockHostBinding* dockHostBinding = m_owner.TryGetDockHostBinding();
if (dockHostBinding == nullptr) {
outTarget = {};
return false;
@@ -492,10 +437,10 @@ void EditorWindow::OnEnterSizeMove() {
bool EditorWindow::OnExitSizeMove() {
m_chromeController->EndInteractiveResize();
m_chromeController->ClearPredictedClientPixelSize();
UINT width = 0u;
UINT height = 0u;
std::uint32_t width = 0u;
std::uint32_t height = 0u;
if (QueryCurrentClientPixelSize(width, height)) {
ApplyWindowResize(width, height);
ApplyWindowResize(static_cast<UINT>(width), static_cast<UINT>(height));
return true;
}
return false;
@@ -503,7 +448,7 @@ bool EditorWindow::OnExitSizeMove() {
void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
m_chromeController->SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi);
m_runtime->SetDpiScale(GetDpiScale());
m_owner.SetDpiScale(GetDpiScale());
if (const HWND hwnd = m_session->GetHwnd();
hwnd != nullptr) {
const LONG windowWidth = suggestedRect.right - suggestedRect.left;
@@ -516,10 +461,10 @@ void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
windowWidth,
windowHeight,
SWP_NOZORDER | SWP_NOACTIVATE);
UINT clientWidth = 0u;
UINT clientHeight = 0u;
std::uint32_t clientWidth = 0u;
std::uint32_t clientHeight = 0u;
if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) {
ApplyWindowResize(clientWidth, clientHeight);
ApplyWindowResize(static_cast<UINT>(clientWidth), static_cast<UINT>(clientHeight));
}
Host::RefreshBorderlessWindowDwmDecorations(hwnd);
}
@@ -549,7 +494,7 @@ using ::XCEngine::UI::UIRect;
namespace {
std::optional<EditorWindowScreenPoint> QueryCursorScreenPoint() {
std::optional<EditorWindowScreenPoint> QueryNativeCursorScreenPoint() {
POINT screenPoint = {};
if (!GetCursorPos(&screenPoint)) {
return std::nullopt;
@@ -617,47 +562,6 @@ std::uint8_t ResolveExpectedShellCaptureButtons(
} // namespace
EditorWindowFrameTransferRequests EditorWindow::RenderHostFrame(
bool globalTabDragActive) {
if (!m_runtime->IsReady() || m_session->GetHwnd() == nullptr) {
return {};
}
UINT pixelWidth = 0u;
UINT pixelHeight = 0u;
if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) {
return {};
}
const float width = PixelsToDips(static_cast<float>(pixelWidth));
const float height = PixelsToDips(static_cast<float>(pixelHeight));
const UIRect workspaceBounds = ResolveWorkspaceBounds(width, height);
UIDrawData drawData = {};
UIDrawList& backgroundDrawList = drawData.EmplaceDrawList("XCEditorWindow.Surface");
backgroundDrawList.AddFilledRect(
UIRect(0.0f, 0.0f, width, height),
kShellSurfaceColor);
EditorWindowFrameTransferRequests transferRequests = {};
if (m_runtime->IsEditorContextValid()) {
transferRequests =
RenderRuntimeFrame(globalTabDragActive, workspaceBounds, drawData);
} else {
UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid");
m_runtime->AppendInvalidFrame(invalidDrawList);
}
UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome");
m_chromeController->AppendChrome(*this, windowChromeDrawList, width);
const auto presentResult = m_runtime->Present(drawData);
if (!presentResult.warning.empty()) {
LogRuntimeTrace("present", presentResult.warning);
}
return transferRequests;
}
void EditorWindow::ValidateHostFrame() const {
if (const HWND hwnd = m_session->GetHwnd();
hwnd != nullptr && IsWindow(hwnd)) {
@@ -687,8 +591,11 @@ bool EditorWindow::ConsumeSkipNextSteadyStateFrame() {
return m_chromeController->ConsumeSkipNextSteadyStateFrame();
}
UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const {
if (m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this)) {
UIRect EditorWindow::ResolveWorkspaceBounds(
const EditorHostWindow& window,
float clientWidthDips,
float clientHeightDips) const {
if (ShouldUseDetachedTitleBarTabStrip(window)) {
return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips);
}
@@ -700,43 +607,24 @@ UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientH
(std::max)(0.0f, clientHeightDips - titleBarHeight));
}
EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame(
bool globalTabDragActive,
const UIRect& workspaceBounds,
UIDrawData& drawData) {
SyncShellCapturedPointerButtonsFromSystemState();
std::vector<UIInputEvent> frameEvents = m_inputController->TakePendingEvents();
const bool useDetachedTitleBarTabStrip =
m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this);
m_runtime->PrepareEditorContext();
const auto frameContext = m_runtime->BeginFrame();
if (!frameContext.warning.empty()) {
LogRuntimeTrace("viewport", frameContext.warning);
}
const EditorWindowFrameTransferRequests transferRequests =
m_runtime->UpdateAndAppend(
workspaceBounds,
frameEvents,
QueryCursorScreenPoint(),
m_session->IsPrimary(),
globalTabDragActive,
useDetachedTitleBarTabStrip,
drawData);
if (frameContext.canRenderViewports) {
m_runtime->RenderRequestedViewports(frameContext.renderContext);
}
ApplyShellRuntimePointerCapture();
ApplyCurrentCursor();
return transferRequests;
bool EditorWindow::ShouldUseDetachedTitleBarTabStrip(
const EditorHostWindow& window) const {
return m_chromeController->ShouldUseDetachedTitleBarTabStrip(window);
}
void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() {
std::vector<UIInputEvent> EditorWindow::TakePendingInputEvents() {
return m_inputController->TakePendingEvents();
}
std::optional<EditorWindowScreenPoint> EditorWindow::QueryCursorScreenPoint() const {
return QueryNativeCursorScreenPoint();
}
void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState(
const UIEditorShellInteractionState& shellState) {
m_inputController->SyncInputModifiersFromSystemState();
const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons(
m_runtime->GetShellInteractionState());
const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons(shellState);
if (expectedButtons == 0u ||
m_inputController->HasPendingPointerStateReconciliationEvent()) {
return;
@@ -750,9 +638,9 @@ void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() {
QueueSyntheticPointerStateSyncEvent(modifiers);
}
void EditorWindow::ApplyShellRuntimePointerCapture() {
void EditorWindow::ApplyShellRuntimePointerCapture(const EditorHostWindow& window) {
const EditorWindowInputFeedbackBinding* inputFeedbackBinding =
m_runtime->TryGetInputFeedbackBinding();
window.TryGetInputFeedbackBinding();
if (inputFeedbackBinding != nullptr &&
inputFeedbackBinding->HasShellInteractiveCapture()) {
AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell);
@@ -774,6 +662,13 @@ void EditorWindow::ApplyShellRuntimePointerCapture() {
}
}
void EditorWindow::AppendChrome(
const EditorHostWindow& window,
UIDrawList& drawList,
float clientWidthDips) const {
m_chromeController->AppendChrome(window, drawList, clientWidthDips);
}
} // namespace XCEngine::UI::Editor::App
namespace XCEngine::UI::Editor::App {
@@ -820,7 +715,7 @@ bool EditorWindow::ApplyCurrentCursor() const {
bool EditorWindow::HasInteractiveCaptureState() const {
const EditorWindowInputFeedbackBinding* inputFeedbackBinding =
m_runtime->TryGetInputFeedbackBinding();
m_owner.TryGetInputFeedbackBinding();
return (inputFeedbackBinding != nullptr && inputFeedbackBinding->HasInteractiveCapture()) ||
m_chromeController->IsBorderlessWindowDragRestoreArmed() ||
m_chromeController->IsBorderlessResizeActive() ||
@@ -855,7 +750,7 @@ void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) {
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam));
if (!ShouldStartImmediateUIEditorShellPointerCapture(
m_runtime->GetShellFrame(),
m_owner.GetShellFrame(),
clientPoint)) {
return;
}
@@ -949,7 +844,7 @@ bool EditorWindow::IsPointerInsideClientArea() const {
LPCWSTR EditorWindow::ResolveCurrentCursorResource() const {
const EditorWindowInputFeedbackBinding* inputFeedbackBinding =
m_runtime->TryGetInputFeedbackBinding();
m_owner.TryGetInputFeedbackBinding();
const Host::BorderlessWindowResizeEdge borderlessResizeEdge =
m_chromeController->IsBorderlessResizeActive()
? m_chromeController->GetBorderlessResizeEdge()